iOS 使用 NSCharacterSet encode URL

JiandanDream發表於2018-04-23

寫在前面

當使用 GET 方式進行網路通訊時,引數會作為 URL 的一部分,此時需要對 URL 進行 Percent-Encoding,即把一些字元轉換成百分號形式,如空格用 %20 代替。

Encoding and Decoding

在 iOS 上,使用的處理方法,主要有以下2個:

Encoding

// 除保留字元外,其他的都變成 % 形式
- [NSString stringByAddingPercentEncodingWithAllowedCharacters:];
複製程式碼

Decoding

// 移除 % 形式
- [NSString stringByRemovingPercentEncoding];
複製程式碼

所以關鍵是保留哪些字元?

[NSURLCharacterSet URLHostAllowedCharacterSet]

先從 iOS SDK 中找起,在 NSURL.h 中找到了 URLHostAllowedCharacterSet 這個靜態方法。

本以為使用官方方法處理後,就搞定了。但使用它處理後,數字也變成了百分號編碼,這不符合當前專案的要求。

那麼 URLHostAllowedCharacterSet 到底代表了那些字元?

仔細找過其標頭檔案,並沒有發現能知道包括字元的介面,甚至找過私有介面,也沒有收穫。

唯一可疑的方法 - (NSData *) bitmapRepresentation;,但得到的是一個 NSData 物件,也沒能從中得到想要的資訊。

最終通過搜尋,在這裡找到了答案:

URLFragmentAllowedCharacterSet  "#%<>[\]^`{|} 
URLHostAllowedCharacterSet      "#%/<>?@\^`{|} 
URLPasswordAllowedCharacterSet  "#%/:<>?@[\]^`{|} 
URLPathAllowedCharacterSet      "#%;<>?[\]^`{|}
URLQueryAllowedCharacterSet     "#%<>[\]^`{|}
URLUserAllowedCharacterSet      "#%/:<>?@[\]^`
複製程式碼

但筆者還是好奇,這些字元是怎麼找出來的?

RFC 3986

既然 iOS SDK 走不通,那直接找到標準保留字元,不就可以了嗎? RFC 3986

reserved = gen-delims / sub-delims gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="

:/?#[]@!$&'()*+,;= 但使用這些字元後,仍然不能正常和服務端通訊。

最後,請教了服務端同事,到底應該以哪些字元為標準。

JavaScript encodeURI()

因為服務端使用了 JavaScript 的解碼方法,所以統一標準,也使用 js 的編碼方式,即 encodeURI()

文件中可以找到:

該方法不會對 ASCII 字母和數字進行編碼,也不會對這些 ASCII 標點符號進行編碼: - _ . ! ~ * ' ( ) 。 該方法的目的是對 URI 進行完整的編碼,因此對以下在 URI 中具有特殊含義的 ASCII 標點符號,encodeURI() 函式是不會進行轉義的:;/?:@&=+$,#

整理後即保留 -_.!~*;/?:@&=+$,# + 數字 + 字母。

翻譯成對應 Objc 程式碼:

NSMutableCharacterSet *lastSet = [[NSMutableCharacterSet alloc] init];
[lastSet formUnionWithCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@"-_.!~*;/?:@&=+$,#"]];
[lastSet formUnionWithCharacterSet:[NSCharacterSet letterCharacterSet]];
[lastSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
return [self stringByAddingPercentEncodingWithAllowedCharacters:lastSet];
複製程式碼

至此,解決問題。

相關文章