分析 NSURL 查询属性

Parse NSURL query property

提问人:gabac 提问时间:10/22/2010 最后编辑:Rafał Srokagabac 更新时间:5/14/2020 访问量:68724

问:

我有一个 URL 像myApp://action/1?parameter=2&secondparameter=3

通过属性查询,我得到了以下部分我的URL

parameter=2&secondparameter=3

有什么办法可以很容易地把它放在一个或一个?NSDictionaryArray

非常感谢

iPhone Objective-C NSURL

评论


答:

14赞 cutsoy 10/22/2010 #1

试试这个;)!

NSString *query = @"parameter=2&secondparameter=3"; // replace this with [url query];
NSArray *components = [query componentsSeparatedByString:@"&"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
for (NSString *component in components) {
    NSArray *subcomponents = [component componentsSeparatedByString:@"="];
    [parameters setObject:[[subcomponents objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
                   forKey:[[subcomponents objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}

评论

1赞 dpjanes 4/10/2011
还要注意 -- 除了 BadPirate 指出的崩溃问题 -- (1) 字典键/值被颠倒了;(2)键/值仍然编码,所以你要做stringByDecodingURLFormat
0赞 Burf2000 9/25/2013
真的很好的方法
54赞 Benoît 10/22/2010 #2

类似的东西:

NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *param in [url componentsSeparatedByString:@"&"]) {
  NSArray *elts = [param componentsSeparatedByString:@"="];
  if([elts count] < 2) continue;
  [params setObject:[elts lastObject] forKey:[elts firstObject]];
}

注意:这是示例代码。不会管理所有错误情况。

评论

2赞 BadPirate 3/24/2011
如果查询格式不正确,此代码将崩溃。请参阅下面的安全解决方案。
2赞 Benoît 3/24/2011
好的,您只需添加对数组大小的检查!这段代码只是一个开始(一如既往地在 SO 上)。如果你想遵守所有URL,你有更多的工作来处理转义字符串,片段(#部分在末尾),...
0赞 BadPirate 4/1/2011
为您进行编辑。只有投了反对票,才能用答案引起您对这个问题的关注。一旦编辑完成,我就可以把它带回来。
1赞 Nick Snyder 9/19/2013
除了没有特殊字符的最简单的大小写值外,此代码甚至无法正确。
0赞 Laszlo 10/1/2015
你应该使用最后一个和第一个对象:它更安全[params setObject:[elts lastObject] forKey:[elts firstObject]];
55赞 BadPirate 3/24/2011 #3

我有理由为这种行为编写一些扩展,这些扩展可能会派上用场。首先是标题:

#import <Foundation/Foundation.h>

@interface NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat;
- (NSString *)stringByEncodingURLFormat;
- (NSMutableDictionary *)dictionaryFromQueryComponents;
@end

@interface NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents;
@end

@interface NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents;
@end

这些方法扩展了 NSString、NSURL 和 NSDictionary,以允许您在包含结果的查询组件字符串和字典对象之间进行转换。

现在相关的 .m 代码:

#import "XQueryComponents.h"

@implementation NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat
{
    NSString *result = [self stringByReplacingOccurrencesOfString:@"+" withString:@" "];
    result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    return result;
}

- (NSString *)stringByEncodingURLFormat
{
    NSString *result = [self stringByReplacingOccurrencesOfString:@" " withString:@"+"];
    result = [result stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    return result;
}

- (NSMutableDictionary *)dictionaryFromQueryComponents
{
    NSMutableDictionary *queryComponents = [NSMutableDictionary dictionary];
    for(NSString *keyValuePairString in [self componentsSeparatedByString:@"&"])
    {
        NSArray *keyValuePairArray = [keyValuePairString componentsSeparatedByString:@"="];
        if ([keyValuePairArray count] < 2) continue; // Verify that there is at least one key, and at least one value.  Ignore extra = signs
        NSString *key = [[keyValuePairArray objectAtIndex:0] stringByDecodingURLFormat];
        NSString *value = [[keyValuePairArray objectAtIndex:1] stringByDecodingURLFormat];
        NSMutableArray *results = [queryComponents objectForKey:key]; // URL spec says that multiple values are allowed per key
        if(!results) // First object
        {
            results = [NSMutableArray arrayWithCapacity:1];
            [queryComponents setObject:results forKey:key];
        }
        [results addObject:value];
    }
    return queryComponents;
}
@end

@implementation NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents
{
    return [[self query] dictionaryFromQueryComponents];
}
@end

@implementation NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents
{
    NSString *result = nil;
    for(__strong NSString *key in [self allKeys])
    {
        key = [key stringByEncodingURLFormat];
        NSArray *allValues = [self objectForKey:key];
        if([allValues isKindOfClass:[NSArray class]])
            for(__strong NSString *value in allValues)
            {
                value = [[value description] stringByEncodingURLFormat];
                if(!result)
                    result = [NSString stringWithFormat:@"%@=%@",key,value];
                else 
                    result = [result stringByAppendingFormat:@"&%@=%@",key,value];
            }
        else {
            NSString *value = [[allValues description] stringByEncodingURLFormat];
            if(!result)
                result = [NSString stringWithFormat:@"%@=%@",key,value];
            else 
                result = [result stringByAppendingFormat:@"&%@=%@",key,value];
        }
    }
    return result;
}
@end

评论

0赞 Dave DeLong 3/24/2011
两个问题:1)在将键和值保存到字典中之前,您必须对它们进行URL解码,2)根据URL的规则,您可以多次指定相同的键。IOW,它应该是映射到值数组的键,而不是映射到单个值的键。
0赞 Benoît 3/24/2011
好的,你只需添加一个对数组大小的检查(这需要对我的答案投反对票吗?这段代码只是一个开始(一如既往地在 SO 上)。如果你想遵守所有URL,你有更多的工作来处理转义字符串,片段(#部分在末尾),...
0赞 BadPirate 4/1/2011
戴夫 - 好点。我的需求不是必需的,但是由于我试图做出一个适用于每个访问这个常见问题的答案,因此我包含了上述功能......Benoit - 只是用你的答案提请你注意这个问题,我提交了一个编辑。可能不是最好的形式,在其他好的答案中引起人们对小问题的注意,我将来不会这样做。在编辑完成之前无法更改反对票:/
0赞 ThomasW 4/5/2011
在 stringByDecodingURLFormat 中,“result”的类型应为 NSString* 。
0赞 ThomasW 4/5/2011
刚刚注意到另一个问题。键和值混淆了,键应该是 objectAtIndex:0,值应该是 objectAtIndex:1。
-6赞 William Entriken 7/20/2012 #4

如果您使用 URL 将数据从 Web 应用程序传递到手机,并且想要传递数组、数字、字符串等,这是最强大的解决方案。

JSON 在 PHP 中对对象进行编码

header("Location: myAppAction://".urlencode(json_encode($YOUROBJECT)));

JSON 在 iOS 中解码结果

NSData *data = [[[request URL] host] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *packed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

评论

1赞 Michael Baldry 2/8/2013
这是一个可怕的想法。URL 有一个长度限制,如果您将 JSON 序列化到其中,则很容易超过该限制。
0赞 Michael Baldry 2/8/2013
它大约有 2000 个字符
9赞 Kristian Kraljic 10/18/2012 #5

所有以前的帖子都没有正确进行 url 编码。我建议采用以下方法:

+(NSString*)concatenateQuery:(NSDictionary*)parameters {
    if([parameters count]==0) return nil;
    NSMutableString* query = [NSMutableString string];
    for(NSString* parameter in [parameters allKeys])
        [query appendFormat:@"&%@=%@",[parameter stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet],[[parameters objectForKey:parameter] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]];
    return [[query substringFromIndex:1] copy];
}
+(NSDictionary*)splitQuery:(NSString*)query {
    if([query length]==0) return nil;
    NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
    for(NSString* parameter in [query componentsSeparatedByString:@"&"]) {
        NSRange range = [parameter rangeOfString:@"="];
        if(range.location!=NSNotFound)
            [parameters setObject:[[parameter substringFromIndex:range.location+range.length] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:[[parameter substringToIndex:range.location] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
        else [parameters setObject:[[NSString alloc] init] forKey:[parameter stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    }
    return [parameters copy];
}

评论

1赞 JRG-Developer 4/3/2013
if(!query||[query length] == 0)是多余的。您可以安全地检查,因为如果您尝试调用长度,它将返回值 。if([query length] == 0)query == nil0
0赞 slf 9/25/2013
这不应该使用 objectForKey 而不是 valueforkey 吗?
0赞 William Denniss 3/23/2014
不错的代码。我建议使用 in 来支持所有字符集:)此外,是一种较新的编码方式。NSUTF8StringEncodingsplitQuery[string stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]concatenateQuery
0赞 Glenn Howes 5/14/2014
我认为返回一个不可变的实例是很好的做法,所以也许你应该将最后一行更改为返回 [parameters copy];
0赞 Kristian Kraljic 6/13/2015
谢谢。我已经根据您的评论调整了代码!
0赞 ymutlu 7/10/2014 #6

这个类是 url 解析的一个很好的解决方案。

.h 文件

@interface URLParser : NSObject {
    NSArray *variables;
}

@property (nonatomic, retain) NSArray *variables;

- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;

@end

.m 文件

#import "URLParser.h"

@implementation URLParser
@synthesize variables;

- (id) initWithURLString:(NSString *)url{
    self = [super init];
    if (self != nil) {
        NSString *string = url;
        NSScanner *scanner = [NSScanner scannerWithString:string];
        [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
        NSString *tempString;
        NSMutableArray *vars = [NSMutableArray new];
        [scanner scanUpToString:@"?" intoString:nil];       //ignore the beginning of the string and skip to the vars
        while ([scanner scanUpToString:@"&" intoString:&tempString]) {
            [vars addObject:[tempString copy]];
        }
        self.variables = vars;
    }
    return self;
}

- (NSString *)valueForVariable:(NSString *)varName {
    for (NSString *var in self.variables) {
        if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:@"="]]) {
            NSString *varValue = [var substringFromIndex:[varName length]+1];
            return varValue;
        }
    }
    return nil;
}

@end
2赞 Amin Negm-Awad 7/15/2014 #7

我在麻省理工学院(MIT)下发布了一个简单的课程:

https://github.com/anegmawad/URLQueryToCocoa

有了它,您可以在查询中拥有数组和对象,它们被收集并粘合在一起

例如

users[0][firstName]=Amin&users[0][lastName]=Negm&name=Devs&users[1][lastName]=Kienle&users[1][firstName]=Christian

将成为:

@{
   name : @"Devs",
   users :
   @[
      @{
         firstName = @"Amin",
         lastName = @"Negm"
      },
      @{
         firstName = @"Christian",
         lastName = @"Kienle"
      }
   ]
 }

您可以将其视为 的 URL 查询对应项。NSJSONSerializer

评论

0赞 Anton Tropashko 1/12/2016
出色的工作。这似乎是唯一正确的答案(考虑数组和字典值的答案)。
7赞 mukaissi 10/14/2014 #8

这是 swift 中的扩展:

extension NSURL{
        func queryParams() -> [String:AnyObject] {
            var info : [String:AnyObject] = [String:AnyObject]()
            if let queryString = self.query{
                for parameter in queryString.componentsSeparatedByString("&"){
                    let parts = parameter.componentsSeparatedByString("=")
                    if parts.count > 1{
                        let key = (parts[0] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
                        let value = (parts[1] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
                        if key != nil && value != nil{
                            info[key!] = value
                        }
                    }
                }
            }
            return info
        }
    }
153赞 Onato 10/16/2014 #9

您可以在 中使用 queryItemsURLComponents

获取此属性的值时,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 = "http://example.com?param1=value1&param2=param2"
print(url.queryParameters["param1"])

评论

11赞 Onato 12/24/2014
从 iOS7 开始查询。queryItems 自 iOS8 开始。
0赞 xaphod 10/30/2015
谢谢你的回答。除此之外,我发现以下文章对于了解有关 NSURLComponents 的更多信息非常有帮助: nshipster.com/nsurl
0赞 Pedro 10/6/2017
奇怪的是,如果 URL 中有 # 字符,则 NSURLComponents 不起作用。像 queryItems 将为 nilhttp://example.com/abc#abc?param1=value1&param2=param2
0赞 Onato 10/6/2017
那是因为片段(#abc)属于末尾。
0赞 Michele Dall'Agata 7/11/2018
它与 Swift 4.2 也非常有效。我可以删除一个感觉非常不合适的 URL 扩展名。
5赞 Rafał Sroka 2/11/2015 #10

对于使用 Bolts Framework 的用户,您可以使用:

NSDictionary *parameters = [BFURL URLWithURL:yourURL].inputQueryParameters;

请记住导入:

#import <Bolts/BFURL.h>

如果您的项目中恰好有 Facebook SDK,那么您也有 Bolts。Facebook正在使用这个框架作为依赖项。

评论

0赞 JAL 10/22/2016
在 Swift with Bolts 中:BFURL(url: url)?.inputQueryParameters?["myKey"] as? String
8赞 Hendrik 2/17/2015 #11

根据 Onato 已经非常干净的答案,我在 Swift 中为 NSURL 编写了一个扩展,您可以在其中获得如下查询参数:

例如,URL 包含一对 param=some_value

let queryItem = url.queryItemForKey("param")
let value = queryItem.value // would get String "someValue"

扩展如下所示:

extension NSURL {

  var allQueryItems: [NSURLQueryItem] {
      get {
          let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
          let allQueryItems = components.queryItems!
          return allQueryItems as [NSURLQueryItem]
      }
  }

  func queryItemForKey(key: String) -> NSURLQueryItem? {

      let predicate = NSPredicate(format: "name=%@", key)!
      return (allQueryItems as NSArray).filteredArrayUsingPredicate(predicate).first as? NSURLQueryItem

  }
}

评论

0赞 Jamin Zhang 6/3/2015
这是最好的答案!他们为什么不检查api文档?
0赞 Paul Peelen 3/16/2015 #12

Hendrik 在这个问题中写了一个很好的扩展示例,但是我不得不重写它以不使用任何 objective-c 库方法。在 swift 中使用不是正确的方法。NSArray

这就是结果,一切都很快,更安全。Swift 1.2 的使用示例将减少代码行数。

public extension NSURL {
    /*
    Set an array with all the query items
    */
    var allQueryItems: [NSURLQueryItem] {
        get {
            let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
            if let allQueryItems = components.queryItems {
                return allQueryItems as [NSURLQueryItem]
            } else {
                return []
            }
        }
    }

    /**
    Get a query item form the URL query

    :param: key The parameter to fetch from the URL query

    :returns: `NSURLQueryItem` the query item
    */
    public func queryItemForKey(key: String) -> NSURLQueryItem? {
        let filteredArray = filter(allQueryItems) { $0.name == key }

        if filteredArray.count > 0 {
            return filteredArray.first
        } else {
            return nil
        }
    }
}

用法:

let queryItem = url.queryItemForKey("myItem")

或者,更详细的用法:

if let url = NSURL(string: "http://www.domain.com/?myItem=something") {
    if let queryItem = url.queryItemForKey("myItem") {
        if let value = queryItem.value {
            println("The value of 'myItem' is: \(value)")
        }
    }
}
4赞 Erik Tjernlund 3/17/2015 #13

斯威夫特 2.1

单行本:

"p1=v1&p2=v2".componentsSeparatedByString("&").map {
    $0.componentsSeparatedByString("=") 
}.reduce([:]) {
    (var dict: [String:String], p) in
    dict[p[0]] = p[1]
    return dict
}

// ["p1": "v1", "p2": "v2"]

用作 NSURL 上的扩展:

extension NSURL {

    /**
     * URL query string as dictionary. Empty dictionary if query string is nil.
     */
    public var queryValues : [String:String] {
        get {
            if let q = self.query {
                return q.componentsSeparatedByString("&").map {
                    $0.componentsSeparatedByString("=") 
                }.reduce([:]) {
                    (var dict: [String:String], p) in
                    dict[p[0]] = p[1]
                    return dict
                }
            } else {
                return [:]
            }
        }
    }

}

例:

let url = NSURL(string: "http://example.com?p1=v1&p2=v2")!
let queryDict = url.queryValues

// ["p1": "v1", "p2": "v2"]

请注意,如果使用 OS X 10.10 或 iOS 8(或更高版本),则最好使用 和 属性并直接创建字典。NSURLComponentsqueryItemsNSURLQueryItems

下面是一个基于扩展的解决方案:NSURLComponentsNSURL

extension NSURL {

    /// URL query string as a dictionary. Empty dictionary if query string is nil.
    public var queryValues : [String:String] {
        get {
            guard let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false) else {
                return [:]
            }

            guard let queryItems = components.queryItems else {
                return [:]
            }

            var result:[String:String] = [:]
            for q in queryItems {
                result[q.name] = q.value
            }
            return result
        }
    }

}

NSURL 扩展的一个脚注是,在 Swift 中,实际上可以给该属性指定与现有字符串属性相同的名称 — query。直到我尝试过我才知道,但 Swift 中的多态性只允许你在返回类型上有所不同。因此,如果扩展的 NSURL 属性是公共 var query: [String:String],则有效。我没有在示例中使用它,因为我觉得它有点疯狂,但它确实有效......

2赞 Gillsoft AB 8/19/2015 #14

看起来您正在使用它来处理来自另一个 iOS 应用程序的传入数据。如果是这样,这就是我用于相同目的的方法。

初始调用(例如在外部应用程序中):

UIApplication *application = [UIApplication sharedApplication];
NSURL *url = [NSURL URLWithString:@"myApp://action/1?parameter=2&secondparameter=3"];
if ([application canOpenURL:url]) {
    [application openURL:url];
    NSLog(@"myApp is installed");
} else {
    NSLog(@"myApp is not installed");
}

从 NSURL 中提取 QueryString 数据并另存为 NSDictionary 的方法:

-(NSDictionary *) getNSDictionaryFromQueryString:(NSURL *)url {
   NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
   NSRange needle = [url.absoluteString rangeOfString:@"?" options:NSCaseInsensitiveSearch];
   NSString *data = nil;

   if(needle.location != NSNotFound) {
       NSUInteger start = needle.location + 1;
       NSUInteger end = [url.absoluteString length] - start;
       data = [url.absoluteString substringWithRange:NSMakeRange(start, end)];
   }

   for (NSString *param in [data componentsSeparatedByString:@"&"]) {
       NSArray *keyvalue = [param componentsSeparatedByString:@"="];
       if([keyvalue count] == 2){
           [result setObject:[keyvalue objectAtIndex:1] forKey:[keyvalue objectAtIndex:0]];
       }
   }

  return result;
}

用法:

NSDictionary *result = [self getNSDictionaryFromQueryString:url];
6赞 William Denniss 9/14/2015 #15

现在,处理 URL 的首选方法是 NSURLComponents。特别是 queryItems 属性,它返回 of 参数。NSArray

如果你想在 a 中设置参数,这里有一个方法:NSDictionary

+(NSDictionary<NSString *, NSString *>*)queryParamsFromURL:(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;
}

注意:URL 可以有重复的参数,但字典将只包含任何重复参数的最后一个值。如果不希望这样做,请直接使用数组。queryItems

0赞 reza_khalafi 6/15/2017 #16

试试这个:

-(NSDictionary *)getUrlParameters:(NSString *)url{
    NSArray *justParamsArr = [url componentsSeparatedByString:@"?"];
    url = [justParamsArr lastObject];
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    for (NSString *param in [url componentsSeparatedByString:@"&"]) {
        NSArray *elts = [param componentsSeparatedByString:@"="];
        if([elts count] < 2) continue;
        [params setObject:[elts lastObject] forKey:[elts firstObject]];
    }
    return params;
}
0赞 possen 5/14/2020 #17

相当紧凑的方法:

    func stringParamsToDict(query: String) -> [String: String] {
        let params = query.components(separatedBy: "&").map {
            $0.components(separatedBy: "=")
        }.reduce(into: [String: String]()) { dict, pair in
            if pair.count == 2 {
                dict[pair[0]] = pair[1]
            }
        }
        return params
    }