iOS URL編碼(百分號(hào)編碼)研究

URL編碼(URL encoding)也稱為百分號(hào)編碼(Percent-encoding), 是特定上下文統(tǒng)一資源定位符(URI)的編碼機(jī)制. 實(shí)際上也使用與統(tǒng)一資源標(biāo)志符(URI)的編碼.

對(duì)于URI, 具體的結(jié)構(gòu)如下:

foo://example.com:8042/over/there?name=ferret#nose

   \_/ \______________/ \________/\_________/ \__/

    |         |              |         |        |

  scheme     authority      path      query   fragment

我們能夠看到:/?#[]@是用來分隔URI的不同的組件的.

混亂的URL編碼

具體參考: 阮一峰: 關(guān)于URL編碼.

URI的字符類型

URI所允許的字符分成保留未保留. 保留字符是那些具有特殊含義的字符. 例如, /字符用于URL不同部分的分節(jié)符(https://www.baidu.com/news). 未保留字符沒有這些特殊含義. 百分號(hào)編碼把保留字符表示為特殊字符序列, 根據(jù)URI的版本的不同略有變化, 下面是 RFC 3986保留字符, 與未保留字符:

  • 保留: ! * ' ( ) ; : @ & = + $ , / ? # [ ]

  • 未保留: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 - _ . ~

除此之外, URI中的其它字符必須用百分號(hào)編碼.

URI的保留字符的百分號(hào)編碼

前面我們知道, URI的保留字符有特殊含義(reserved purpose), 并且URI中必須使用該字符用于其他目的, 那么該字符必須百分號(hào)編碼. 這里所說的其他目的, 我們可以這樣理解, 在URL中?key1=val1&key2=val2中的val1中如果有保留字符&或者*,那么這里保留字符用于其他目的, 需要編碼.

百分號(hào)編碼一個(gè)保留字符, 首先需要把該字符的ASCII的值表示為兩個(gè)16進(jìn)制的數(shù)字, 然后在其前面放置轉(zhuǎn)義字符("%"), 置入U(xiǎn)RI中的相應(yīng)位置. (對(duì)于非ASCII字符, 需要轉(zhuǎn)換為UTF-8字節(jié)序, 然后每個(gè)字節(jié)按照上述方式表示.)

!   #   $   &   '   (   )   *   +   ,   /   :   ;   =   ?   @   [   ]

%21 %23 %24 %26 %27 %28 %29 %2A %2B %2C %2F %3A %3B %3D %3F %40 %5B %5D

在特定上下文中沒有特殊含義的保留字符也可以被百分號(hào)編碼, 在語(yǔ)義上與不百分號(hào)編碼的該字符沒有差別(這個(gè)特點(diǎn)非常重要, 如果我們不知道該字符是否需要被百分號(hào)編碼, 那么最好用百分號(hào)編碼一下).

在URI的查詢部分(?字符后的部分)中, 例如/仍然是保留字符但是沒有特殊含義, 除非一個(gè)特定的URI有其它規(guī)定. 該/字符在沒有特殊含義時(shí)不需要百分號(hào)編碼.例如https://www.baidu.com/news?name=p/p&age=13, 其中name=p/p中的/保留字符但是沒有特殊含義, 在實(shí)際使用中可以不用給它進(jìn)行百分號(hào)編碼.

如果保留字符具有特殊含義, 那么該保留字符用百分號(hào)編碼的URI與該保留字符僅用其自身表示的URI具有不同的語(yǔ)義.

URI中百分號(hào)編碼未保留字符

未保留字符不需要百分號(hào)編碼.

兩個(gè)URI的差別如果僅在于未保留字符是用百分號(hào)編碼還是字符本身表示, 那么這兩個(gè)URI具有等價(jià)意義. 雖然是這樣規(guī)定, 但是很多瀏覽器沒有這樣去設(shè)定.因此實(shí)際開發(fā)中, 我們建議, 盡量不要將未保留字符進(jìn)行百分號(hào)編碼, 防止不同的實(shí)現(xiàn)導(dǎo)致不同的結(jié)果.

其他不安全字符也需要百分號(hào)編碼

這些字符, 當(dāng)被直接放在URL中的時(shí)候, 能會(huì)引起解析程序的歧義救欧。這些字符被視為不安全字符,原因有很多:

  • 空格: URL在傳輸?shù)倪^程,或者用戶在排版的過程,或者文本處理程序在處理URL的過程,都有可能引入無(wú)關(guān)緊要的空格刁岸,或者將那些有意義的空格給去掉独郎。
  • 引號(hào)以及<>: 引號(hào)和尖括號(hào)通常用于在普通文本中起到分隔Url的作用
  • #: 通常用于表示書簽或者錨點(diǎn)
  • %: 百分號(hào)本身用作對(duì)不安全字符進(jìn)行編碼時(shí)使用的特殊字符,因此本身需要編碼
  • " {}|^[]`~ ": 某一些網(wǎng)關(guān)或者傳輸代理會(huì)篡改這些字符.

因此這些不安全的字符最好也進(jìn)行百分號(hào)編碼.

URI中百分號(hào)編碼的非標(biāo)準(zhǔn)實(shí)現(xiàn)

有一些不符合標(biāo)準(zhǔn)的把Unicode字符在URI中表示為: %uxxxx, 其中xxxx是用4個(gè)十六進(jìn)制數(shù)字表示的Unicode的碼位值壳猜。

任何RFC都沒有這樣的字符表示方法勾徽,并且已經(jīng)被W3C拒絕。第三版的ECMA-262仍然包含函數(shù)escape(string)使用這種語(yǔ)法, 但也有函數(shù)encodeURI(uri)轉(zhuǎn)換字符到UTF-8字節(jié)序列并用百分號(hào)編碼每個(gè)字節(jié)统扳。

這里就涉及到 JS 的幾個(gè)百分號(hào)編碼函數(shù), 建議使用encodeURI(uri)

iOS中百分號(hào)編碼問題

在前序知識(shí)鋪墊以后. iOS里面如何處理百分號(hào)編碼的問題呢?

HTTP協(xié)議里面在URL中傳遞參數(shù),是在?后面使用key=value這種鍵值對(duì)方式, 如果有多個(gè)參數(shù)傳遞, 就需要用&進(jìn)行分割, 例如?key1=val1&key2=val2&key3=val3, 當(dāng)服務(wù)器收到請(qǐng)求以后, 會(huì)用&分割出每個(gè)key=value參數(shù), 然后用=分割出具體的鍵值.

現(xiàn)在如果在我們的參數(shù)key-value中就有=或者&怎么辦? 這樣后臺(tái)在解析參數(shù)的時(shí)候, 就會(huì)產(chǎn)生歧義. 因此解決方法就是對(duì)參數(shù)進(jìn)行百分號(hào)編碼!!!!

iOS 中,我們?cè)谡?qǐng)求的中經(jīng)常與百分號(hào)編碼相關(guān)的方法 -- stringByAddingPercentEscapesUsingEncoding:

Summary

Returns a representation of the receiver using a given encoding to determine the percent escapes necessary to convert the receiver into a legal URL string.
Declaration

- (NSString *)stringByAddingPercentEscapesUsingEncoding:(NSStringEncoding)enc;
Discussion

It may be difficult to use this function to "clean up" unescaped or partially escaped URL strings where sequences are unpredictable. See CFURLCreateStringByAddingPercentEscapes for more information.
Parameters

encoding    
The encoding to use for the returned string. If you are uncertain of the correct encoding you should use NSUTF8StringEncoding.
Returns

A representation of the receiver using encoding to determine the percent escapes necessary to convert the receiver into a legal URL string. Returns nil if encoding cannot encode a particular character.
Open in Developer Documentation

當(dāng)URL中有漢字時(shí)候, 用上面的方法, 會(huì)將漢字轉(zhuǎn)化成 unicode 編碼的結(jié)果, 但是對(duì)于復(fù)雜場(chǎng)景這個(gè)方法并不能滿足需求, 例如&符號(hào):

NSString *queryWord = @"漢字&ss";
NSString *urlString = [NSString stringWithFormat:@"https://www.baidu.com/s?ie=UTF-8&wd=%@", queryWord];
NSString *escapedString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"%@", escapedString); // https://www.baidu.com/s?ie=UTF-8&wd=%E6%B1%89%E5%AD%97&ss

這個(gè)實(shí)例在開發(fā)中很常見(我們項(xiàng)目中是將某個(gè)人的昵稱當(dāng)做參數(shù)傳遞給后臺(tái)), 后臺(tái)在收到這種被轉(zhuǎn)義以后的URL取得的參數(shù)如下:

["ie": "UTF-8", "wd" : "漢字", "ss": nil]

即使我們做如下處理, 在請(qǐng)求前將每個(gè)參數(shù)都轉(zhuǎn)義, 再使用&拼接參數(shù)也無(wú)效:

NSString *queryWord = @"漢字&ss";
NSString *escapedQueryWord = [queryWord stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *urlString = [NSString stringWithFormat:@"https://www.baidu.com/s?ie=UTF-8&wd=%@", escapedQueryWord];
NSLog(@"%@", urlString); // https://www.baidu.com/s?ie=UTF-8&wd=%E6%B1%89%E5%AD%97&ss

這是因?yàn)?code>stringByAddingPercentEscapesUsingEncoding方法并不會(huì)對(duì)&字符進(jìn)行百分號(hào)編碼!!!!

iOS中正確的使用百分號(hào)編碼

如果要想自己控制哪些內(nèi)容被編碼, 哪些內(nèi)容不會(huì)被編碼, iOS提供了另外一個(gè)方法 -- stringByAddingPercentEncodingWithAllowedCharacters:.

這個(gè)方法會(huì)對(duì)字符串進(jìn)行更徹底的轉(zhuǎn)義捂蕴,但是需要傳遞一個(gè)參數(shù): 這個(gè)參數(shù)是一個(gè)字符集,表示: 在進(jìn)行轉(zhuǎn)義過程中闪幽,不會(huì)對(duì)這個(gè)字符集中包含的字符進(jìn)行轉(zhuǎn)義, 而保持原樣保留下來啥辨。

NSString *queryWord = @"漢字&ss";
NSString *escapedQueryWord = [queryWord stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet letterCharacterSet]];
NSLog(@"%@", escapedQueryWord); // %E6%B1%89%E5%AD%97%26ss
NSString *urlString = [NSString stringWithFormat:@"https://www.baidu.com/s?ie=UTF-8&wd=%@", escapedQueryWord];
NSLog(@"%@", urlString); // https://www.baidu.com/s?ie=UTF-8&wd=%E6%B1%89%E5%AD%97%26ss

在上面的例子中傳遞參數(shù)[NSCharacterSet letterCharacterSet]來保證字母不被轉(zhuǎn)義。所以被轉(zhuǎn)義之后的參數(shù)值是:%E6%B1%89%E5%AD%97%26ss盯腌,這樣&就能夠正確被百分號(hào)編碼.

但是如果實(shí)際場(chǎng)景中, 可能出現(xiàn)如下情況:

https://www.baidu.com/s?person[contact]=13801001234&person[address]=北京&habit[]=游泳&habit[]=騎行

此時(shí), 需要自己構(gòu)建 AllowedCharacters, 因?yàn)槠渲械?code>[和]是不需要轉(zhuǎn)意的.

NSMutableCharacterSet *mutableCharSet = [[NSMutableCharacterSet alloc] init];
[mutableCharSet addCharactersInString:@"[]"]; // 允許'['和']'不被轉(zhuǎn)義
NSCharacterSet *charSet = mutableCharSet.copy;

NSMutableString *mutableString = [NSMutableString string];
for (unit in queryString) {
    NSString *escapedField = [unit.field stringByAddingPercentEncodingWithAllowedCharacters:charSet];
    NSString *escapedValue = [unit.value stringByAddingPercentEncodingWithAllowedCharacters:charSet];
    [mutableString addFormat:@"%@=%@", escapedField, escapedValue];
}

準(zhǔn)確說, 步驟如下:

  1. 構(gòu)建AllowedCharactersNSCharacterSet
  2. 針對(duì)參數(shù)的k-v值, 進(jìn)行遍歷, 將針對(duì)keyvalue分別調(diào)用stringByAddingPercentEncodingWithAllowedCharacters進(jìn)行百分號(hào)編碼.
  3. @"?%@=%@&%@=%@"進(jìn)行kv參數(shù)拼接, 和不同參數(shù)的拼接.

AFNetworking中對(duì)百分號(hào)編碼的處理

對(duì)這種特定的字符進(jìn)行百分號(hào)編碼已經(jīng)能夠滿足基本需求, 但是如果我們傳遞的參數(shù)非常復(fù)雜, 我們應(yīng)該如何處理呢??

我們可以直接使用AFNetworking中的代碼, 實(shí)例如下:

NSDictionary *params = @{@"name": @"p&p",
                            @"nick_name": @"p&p[]@= =!",
                            @"father name": @"~!@#$%^&*(){}"
                            };
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
NSMutableURLRequest *request = [serializer requestWithMethod:@"GET" URLString:@"https://www.baidu.com" parameters:params error:nil];
NSString *urlString = request.URL.absoluteString;
NSLog(@"%@", urlString);

//https://www.baidu.com?father%20name=~%21%40%23%24%25%5E%26%2A%28%29%7B%7D&name=p%26p&nick_name=p%26p%5B%5D%40%3D%20%3D%21

具體的處理方式, 建議參考AFNetworking的源碼, 或者參考文章iOS. PercentEscape是錯(cuò)用的URLEncode溉知,看看AFN和Facebook吧.

AFNetworking源碼以及編碼過程

關(guān)于AFNetworking中是如何做的:

FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value);
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);


@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end

@implementation AFQueryStringPair
- (instancetype)initWithField:(id)field value:(id)value {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.field = field;
    self.value = value;

    return self;
}

- (NSString *)URLEncodedStringValue {
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        return AFPercentEscapedStringFromString([self.field description]);
    } else {
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
    }
}


/**
 傳入 Dict -> 返回對(duì)應(yīng)的查詢的參數(shù)String

 @param parameters Dict內(nèi)部是 key-value
 @return 查詢的參數(shù)String
 */
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    // 生成一組 AFQueryStringPair 數(shù)組
    NSArray<AFQueryStringPair *> *pairs = AFQueryStringPairsFromDictionary(parameters);

    // 遍歷數(shù)組, 將每個(gè) AFQueryStringPair 生成 "key=value", 并將結(jié)果String加入到結(jié)果數(shù)組
    for (AFQueryStringPair *pair in pairs) {
        // 將封裝的 StringPair 進(jìn)行 URLEncode 核心代碼
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }
    // 將結(jié)果數(shù)組中每個(gè)字符通過 "&" 字符鏈接, 輸出 Query 結(jié)果
    return [mutablePairs componentsJoinedByString:@"&"];
}


/**
 將dict 轉(zhuǎn)化成  NSArray<AFQueryStringPair *>
 */
NSArray<AFQueryStringPair *> * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

/**
 key - value 核心方法

 @param key key
 @param value value -- 可能是常用的集合類 -- NSDictionary, NSArray, NSSet,
                        以及非集合類 -- 普通的 key - value
 @return 返回NSArray<AFQueryStringPair *> *
 */
NSArray<AFQueryStringPair *> * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    //1. key-value 會(huì)重新排序 -- 升序進(jìn)行排序
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    //2. 根據(jù)當(dāng)前value內(nèi)容, 分辨進(jìn)行不同的處理. 實(shí)際場(chǎng)景中, 我們最常用的是 Dict - 內(nèi)部是key-value
    //   場(chǎng)景上來說

    /*
     2.1 value -> NSDictionary

     NSDictionary *dict = @{@"phone": @{@"mobile": @"xx", @"home": @"xx"};
     -> 會(huì)進(jìn)入第一個(gè)分支 - Dict分支
     phone[mobile]=xx&phone[home]=xx
     */
    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
        // 先將dictionarys 內(nèi)容排序
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                // 遞歸調(diào)用.
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }

        /*
         2.2 value -> NSArray

         NSDictionary *dict = @{"members": @[@"pp", @"brownfeng"]};
         -> Array分支
         members[]=pp&members[]=brownfeng
         */
    } else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
        /*
         2.3 value -> NSSet

         NSDictionary *dict = @{@"counts": [NSSet setWithObjects:@"1", @"2", nil]};
         -> NSSet分支
         counts=1&counts=2
         */
    } else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        /*
         2.4 value -> NSString

         普通的 key-value類型. 直接生成 AFQueryStringPair

         NSDictionary *dict = @{@"name": @"pp"};
         -> 其他分支
         name=p
         */
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

/**
 百分號(hào)編碼的核心代碼!!!!!

 Returns a percent-escaped string following RFC 3986 for a query string key or value.
 RFC 3986 states that the following characters are "reserved" characters.
 - General Delimiters: ":", "#", "[", "]", "@", "?", "/"   -> 常見的分隔符
 - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="  -> 其他分隔符

 In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
 query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
 should be percent-escaped in the query string.
 - parameter string: The string to be percent-escaped.
 - returns: The percent-escaped string.

 上面注釋寫的很清楚:

 "?"和"/"兩個(gè)符號(hào)在query必須進(jìn)行百分號(hào)編碼, 因?yàn)閝uery部分不允許包含URL!!!!
 */
NSString * AFPercentEscapedStringFromString(NSString *string) {
    // 需要被百分號(hào)編碼 - @":#[]@"
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
    // 需要被百分號(hào)編碼 - @"!$&'()*+,;="
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
    // "?", "/" - 沒有被百分號(hào)編碼!!!!

    // 1. 創(chuàng)建字符集 - URL Query 部分允許的字符集
    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    // 2. 從允許字符集中刪掉不允許的字符集, 因此編碼時(shí)候,
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

    // 解決iOS7,8中可能導(dǎo)致的crash問題

    // FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
    // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];

    static NSUInteger const batchSize = 50;

    NSUInteger index = 0;
    NSMutableString *escaped = @"".mutableCopy;

    while (index < string.length) {
        NSUInteger length = MIN(string.length - index, batchSize);
        NSRange range = NSMakeRange(index, length);

        // To avoid breaking up character sequences such as ????????
        range = [string rangeOfComposedCharacterSequencesForRange:range];

        NSString *substring = [string substringWithRange:range];
        NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
        [escaped appendString:encoded];

        index += range.length;
    }

    return escaped;
}


因此如果比較復(fù)雜的query內(nèi)容比如如下:

{
    NSDictionary *params = @{
                            @"name": @"小A", // 標(biāo)準(zhǔn) key-value, 漢字需要被編碼
                            @"phone": @{@"mobile": @"xx", @"home": @"xx"}, // key - Dict
                            @"families": @[@"father", @"mother"], // key - Array
                            @"nums": [NSSet setWithObjects:@"1", @"2", nil], // key - set
                            @"does_not_include": @"/?", // 不會(huì)被編碼    (注意: OC中的"\\"才能表示"\")
                            @"space": @" ", //需要被編碼  (空格)
                            @"GeneralDelimitersToEncode": @":#[]@", // 需要完全被編碼
                            @"SubDelimitersToEncode": @"!$&'()*+,;=", // 需要完全被編碼
                            };
    AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
    NSMutableURLRequest *request = [serializer requestWithMethod:@"GET" URLString:@"https://www.baidu.com" parameters:params error:nil];
    NSString *urlString = request.URL.absoluteString;
    NSLog(@"%@", urlString);
    /*
https://www.baidu.com?GeneralDelimitersToEncode=%3A%23%5B%5D%40&SubDelimitersToEncode=%21%24%26%27%28%29%2A%2B%2C%3B%3D&does_not_include=/?&families%5B%5D=father&families%5B%5D=mother&name=%E5%B0%8FA&nums=1&nums=2&phone%5Bhome%5D=xx&phone%5Bmobile%5D=xx&space=%20

https://www.baidu.com?GeneralDelimitersToEncode=:#[]@&SubDelimitersToEncode=!$&'()*+,;=&does_not_include=/?&families[]=father&families[]=mother&name=小A&nums=1&nums=2&phone[home]=xx&phone[mobile]=xx&space=
     */
}

AFNetworking中的參數(shù)解析過程如下:

第一塊, key-value的模式

@{
    @"name": @"小A",
    @"phone": @{@"mobile": @"xx", @"home": @"xx"},
    @"families": @[@"father", @"mother"],
    @"nums": [NSSet setWithObjects:@"1", @"2", nil],
};
->
@[
     field: @"name", value: @"小A",
     field: @"phone[mobile]", value: @"xx",
     field: @"phone[home]", value: @"xx",
     field: @"families[]", value: @"father",
     field: @"families[]", value: @"mother",
     field: @"nums", value: @"1",
     field: @"nums", value: @"2",
]
->
name=%E5%B0%8FA&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2

第二部分: 哪些內(nèi)容需要編碼

@{
    @"does_not_include": @"/?",
    @"space": @" ", 
    @"GeneralDelimitersToEncode": @":#[]@",
    @"SubDelimitersToEncode": @"!$&'()*+,;=",
};
->
@[
     field: @"does_not_include", value: @"/?",
     field: @"space", value: @" ",
     field: @"GeneralDelimitersToEncode", value: @":#[]@",
     field: @"SubDelimitersToEncode", value: @"!$&'()*+,;=",
]
->
https://www.baidu.com?GeneralDelimitersToEncode=%3A%23%5B%5D%40&SubDelimitersToEncode=%21%24%26%27%28%29%2A%2B%2C%3B%3D&does_not_include=/?&space=%20


-> URL Decode以后
https://www.baidu.com?GeneralDelimitersToEncode=:#[]@&SubDelimitersToEncode=!$&'()*+,;=&does_not_include=/?&space= 

參考文章

  1. 為什么要進(jìn)行URL編碼
  2. 阮一峰: 關(guān)于URL編碼
  3. wiki: 百分號(hào)編碼
  4. iOS. PercentEscape是錯(cuò)用的URLEncode,看看AFN和Facebook吧
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腕够,一起剝皮案震驚了整個(gè)濱河市级乍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帚湘,老刑警劉巖玫荣,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異大诸,居然都是意外死亡捅厂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門资柔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焙贷,“玉大人,你說我怎么就攤上這事贿堰≌奚郑” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵羹与,是天一觀的道長(zhǎng)故硅。 經(jīng)常有香客問我,道長(zhǎng)纵搁,這世上最難降的妖魔是什么吃衅? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮诡渴,結(jié)果婚禮上捐晶,老公的妹妹穿的比我還像新娘菲语。我一直安慰自己,他們只是感情好惑灵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布山上。 她就那樣靜靜地躺著,像睡著了一般英支。 火紅的嫁衣襯著肌膚如雪佩憾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天干花,我揣著相機(jī)與錄音妄帘,去河邊找鬼。 笑死池凄,一個(gè)胖子當(dāng)著我的面吹牛抡驼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肿仑,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼致盟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了尤慰?” 一聲冷哼從身側(cè)響起馏锡,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伟端,沒想到半個(gè)月后杯道,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡责蝠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年党巾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玛歌。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昧港,死狀恐怖擎椰,靈堂內(nèi)的尸體忽然破棺而出支子,到底是詐尸還是另有隱情,我是刑警寧澤达舒,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布值朋,位于F島的核電站,受9級(jí)特大地震影響巩搏,放射性物質(zhì)發(fā)生泄漏昨登。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一贯底、第九天 我趴在偏房一處隱蔽的房頂上張望丰辣。 院中可真熱鬧撒强,春花似錦、人聲如沸笙什。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琐凭。三九已至芽隆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間统屈,已是汗流浹背胚吁。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愁憔,地道東北人腕扶。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吨掌,于是被迫代替她去往敵國(guó)和親蕉毯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容