解析URL字符串以获取键的值的最佳方法是什么?

86

我需要解析像这样的URL字符串:

&ad_eurl=http://www.youtube.com/video/4bL4FI1Gz6s&hl=it_IT&iv_logging_level=3&ad_flags=0&endscreen_module=http://s.ytimg.com/yt/swfbin/endscreen-vfl6o3XZn.swf&cid=241&cust_gender=1&avg_rating=4.82280613104
我需要将NSString拆分成像cid=241&avg_rating=4.82280613104这样的单个部分。我一直在使用substringWithRange:来做这个,但是值会以随机顺序返回,这会搞乱它。是否有任何类可以轻松解析,使您基本上可以将其转换为NSDictionary以便能够读取键的值(例如ValueForKey:cid应返回241)。还是有比使用NSMakeRange获取子字符串更容易解析的方法吗?
16个回答

174

我也在https://dev59.com/I2865IYBdhLWcg3wFqdV#26406478中回答了这个问题。

你可以在URLComponents中使用queryItems

当您获取此属性的值时,NSURLComponents类会解析查询字符串,并按照出现在原始查询字符串中的顺序返回一个NSURLQueryItem对象数组,每个对象表示单个键值对。

let url = "http://example.com?param1=value1&param2=param2"
let queryItems = URLComponents(string: url)?.queryItems
let param1 = queryItems?.filter({$0.name == "param1"}).first
print(param1?.value)

或者,您可以在URL上添加一个扩展名来使事情变得更加容易。

extension URL {
    var queryParameters: QueryParameters { return QueryParameters(url: self) }
}

class QueryParameters {
    let queryItems: [URLQueryItem]
    init(url: URL?) {
        queryItems = URLComponents(string: url?.absoluteString ?? "")?.queryItems ?? []
        print(queryItems)
    }
    subscript(name: String) -> String? {
        return queryItems.first(where: { $0.name == name })?.value
    }
}

您可以通过参数名称访问该参数。

let url = URL(string: "http://example.com?param1=value1&param2=param2")!
print(url.queryParameters["param1"])

126

编辑(2018年6月):这个答案更好。苹果在iOS 7中添加了NSURLComponents

我会创建一个字典,使用以下代码获取键值对数组:

NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] init];
NSArray *urlComponents = [urlString componentsSeparatedByString:@"&"];

然后填充字典:

for (NSString *keyValuePair in urlComponents)
{
    NSArray *pairComponents = [keyValuePair componentsSeparatedByString:@"="];
    NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
    NSString *value = [[pairComponents lastObject] stringByRemovingPercentEncoding];

    [queryStringDictionary setObject:value forKey:key];
}

您可以使用以下查询:

[queryStringDictionary objectForKey:@"ad_eurl"];

这段代码未经测试,建议进行更多的错误测试。


13
这并没有对组件进行URL解码。在执行objectAtIndex:1之前,也没有对pairComponents数组进行边界检查(如果索引1处没有对象会怎样?)。 - Yetanotherjosh
8
在查询字符串中使用"componentsSeparatedByString"来拆分键和值是不正确的。因为等号"="常常出现在值中(例如,base64编码的令牌)。 - donleyp
1
稍作优化: NSArray *urlComponents = [urlString componentsSeparatedByString:@"&"]; NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] initWithCapacity: urlComponents.count]; - adnako
3
如果任何值包含字符"=",它将破坏填充逻辑。你可以通过以下代码替换部分代码:NSRange range = [keyValuePair rangeOfString:@"="]; NSString *key = [keyValuePair substringToIndex:range.location]; NSString *value = [keyValuePair substringFromIndex:range.location+1];。这将使其正常运行。 - Renato Silva Das Neves
1
这不会获取第一个项目。 - Keith Adler
显示剩余4条评论

47

虽然我有点晚了,但是目前为止提供的答案都不符合我的需求。您可以使用以下代码片段:

NSMutableDictionary *queryStrings = [[NSMutableDictionary alloc] init];
for (NSString *qs in [url.query componentsSeparatedByString:@"&"]) {
    // Get the parameter name
    NSString *key = [[qs componentsSeparatedByString:@"="] objectAtIndex:0];
    // Get the parameter value
    NSString *value = [[qs componentsSeparatedByString:@"="] objectAtIndex:1];
    value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
    value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

    queryStrings[key] = value;
}

url是您要解析的URL。您可以在queryStrings可变字典中获取所有查询字符串,已进行转义处理。

编辑: Swift版本:

var queryStrings = [String: String]()
if let query = url.query {
    for qs in query.componentsSeparatedByString("&") {
        // Get the parameter name
        let key = qs.componentsSeparatedByString("=")[0]
        // Get the parameter value
        var value = qs.componentsSeparatedByString("=")[1]
        value = value.stringByReplacingOccurrencesOfString("+", withString: " ")
        value = value.stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!

        queryStrings[key] = value
    }
}

2
这是一个不错的答案,但不是最好的。当参数是类似base64编码的字符串时,你应该小心。正如@donleyp所说,'='字符可能是正常值。例如,base64编码的字符串需要使用'='字符进行填充。 - khcpietro
@Aigori 如果我错了,请纠正我,但在我看来,这个答案支持base64。用于填充的=直到最后解码才会出现为=。然而,它不支持没有值的查询参数。 - Boris
例如,使用答案代码解码此URL http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==。它将返回 ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ 而不是 ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==。 当然,一些base64解码器可以正确解码,但是例如NSData initWithBase64EncodedString:options:返回空字符串而不带填充的 =。 因此,在处理具有答案代码的base64字符串时应该小心。 - khcpietro
@Aigori 是的,你说得对,但在这种情况下,URL 不会是 http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==,而应该是 http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ%3D%3D,因为值应该进行 URL 编码。 - Boris

10

对于使用iOS8及以上版本的情况,可使用NSURLComponents

+(NSDictionary<NSString *, NSString *> *)queryParametersFromURL:(NSURL *)url {
    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
    NSMutableDictionary<NSString *, NSString *> *queryParams = [NSMutableDictionary<NSString *, NSString *> new];
    for (NSURLQueryItem *queryItem in [urlComponents queryItems]) {
        if (queryItem.value == nil) {
            continue;
        }
        [queryParams setObject:queryItem.value forKey:queryItem.name];
    }
    return queryParams;
}

iOS 8及以下版本:

+(NSDictionary<NSString *, NSString *> *)queryParametersFromURL:(NSURL *)url    
    NSMutableDictionary<NSString *, NSString *> * parameters = [NSMutableDictionary<NSString *, NSString *> new];
    [self enumerateKeyValuePairsFromQueryString:url.query completionblock:^(NSString *key, NSString *value) {
        parameters[key] = value;
    }];
    return parameters.copy;
}

- (void)enumerateKeyValuePairsFromQueryString:(NSString *)queryString completionBlock:(void (^) (NSString *key, NSString *value))block {
    if (queryString.length == 0) {
        return;
    }
    NSArray *keyValuePairs = [queryString componentsSeparatedByString:@"&"];
    for (NSString *pair in keyValuePairs) {
        NSRange range = [pair rangeOfString:@"="];
        NSString *key = nil;
        NSString *value = nil;

        if (range.location == NSNotFound) {
            key = pair;
            value = @"";
        }
        else {
            key = [pair substringToIndex:range.location];
            value = [pair substringFromIndex:(range.location + range.length)];
        }

        key = [self decodedStringFromString:key];
        key = key ?: @"";

        value = [self decodedStringFromString:value];
        value = value ?: @"";

        block(key, value);
    }
}

+ (NSString *)decodedStringFromString:(NSString *)string {
    NSString *input = shouldDecodePlusSymbols ? [string stringByReplacingOccurrencesOfString:@"+" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, string.length)] : string;
    return [input stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}

8

Swift 5

extension URL {
    func queryParams() -> [String:String] {
        let queryItems = URLComponents(url: self, resolvingAgainstBaseURL: false)?.queryItems
        let queryTuples: [(String, String)] = queryItems?.compactMap{
            guard let value = $0.value else { return nil }
            return ($0.name, value)
        } ?? []
        return Dictionary(uniqueKeysWithValues: queryTuples)
    }
}

6

如果你想在Swift中做同样的事情,你可以使用一个扩展。

extension NSURL {
    func queryDictionary() -> [String:String] {
        let components = self.query?.componentsSeparatedByString("&")
        var dictionary = [String:String]()

        for pairs in components ?? [] {
            let pair = pairs.componentsSeparatedByString("=")
            if pair.count == 2 {
                dictionary[pair[0]] = pair[1]
            }
        }

        return dictionary
    }
}

5

自从iOS 8以来,您可以直接在NSURLQueryItem上使用属性namevalue

例如,如何解析URL并获取解析后键值对的特定值。

NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:@"someURL" resolvingAgainstBaseURL:false];
NSArray *queryItems = urlComponents.queryItems;
NSMutableArray *someIDs = [NSMutableArray new];
for (NSURLQueryItem *item in queryItems) {
    if ([item.name isEqualToString:@"someKey"]) {
        [someIDs addObject:item.value];
    }
}
NSLog(@"%@", someIDs);

4
如果您正在使用NSURLComponents,以下简洁的扩展也可以解决问题:
extension NSURLComponents {

    func getQueryStringParameter(name: String) -> String? {
        return (self.queryItems? as [NSURLQueryItem])
            .filter({ (item) in item.name == name }).first?
            .value()
    }

}

1
在Swift 2中,`func getQueryStringParameter(name: String) -> String? { return self.queryItems?.filter({ (item) in item.name == name }).first?.value }` - Jedidja

3
这段代码适用于以下三种情况:

1.http://www.youtube.com/watch?v=VWsl7C-y7EI&feature=youtu.be 2.http://youtu.be/lOvcFqQyaDY
3.http://www.youtube.com/watch?v=VWsl7C-y7EI

NSArray *arr = [youtubeurl componentsSeparatedByString:@"v="];
 NSString *youtubeID;
if([arr count]>0)
{
    if([arr count]==1){
        youtubeID= [[youtubeurl componentsSeparatedByString:@"/"] lastObject];

    }
    else{
        NSArray *urlComponents = [[arr objectAtIndex:1] componentsSeparatedByString:@"&"];
        youtubeID=[urlComponents objectAtIndex:0];
    }
}

1
我特别在寻找一种提取YouTube ID的解决方案,但我忘记考虑到有多种有效的YouTube URL变体。+1 谢谢! - Seoras

3
-(NSArray *)getDataOfQueryString:(NSString *)url{
    NSArray *strURLParse = [url componentsSeparatedByString:@"?"];
    NSMutableArray *arrQueryStringData = [[NSMutableArray alloc] init];
    if ([strURLParse count] < 2) {
        return arrQueryStringData;
    }
    NSArray *arrQueryString = [[strURLParse objectAtIndex:1] componentsSeparatedByString:@"&"];

    for (int i=0; i < [arrQueryString count]; i++) {
        NSMutableDictionary *dicQueryStringElement = [[NSMutableDictionary alloc]init];
        NSArray *arrElement = [[arrQueryString objectAtIndex:i] componentsSeparatedByString:@"="];
        if ([arrElement count] == 2) {
            [dicQueryStringElement setObject:[arrElement objectAtIndex:1] forKey:[arrElement objectAtIndex:0]];
        }
        [arrQueryStringData addObject:dicQueryStringElement];
    }

    return arrQueryStringData; 
}

这个函数只需要传入URL,就能获取查询字符串中的所有元素。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接