在和web服務進行交互時,我們經常需要對URL中的特定字符和傳輸的表單數據進行百分號編碼。例如,’&’在百分號編碼時會變成’%26’。搞清楚 URL中哪部分的哪些字符應該進行百分號編碼了并不是件易事。最好的資料好像是RFC 3986和W3C HTML5。出于興趣和教育目的,我創建了swift的String的擴展(和作為對比的Objective-C的分類)。
RFC3986 編碼查詢字符串
在 RFC3986 的第2.3節列出了你不需要百分號編碼的字符,因為它們在URL中沒有特殊的含義。
ALPHA / DIGIT / “-” / “.” / “_” / “~”
α/數字/”-”/”.”/”_”
第3.4節也解釋了因為查詢往往會本身包含一個URL,最好不要百分號編碼斜杠(“/”)和問號(“?”)。這也是受歡迎的iOS HTTP網絡庫Alamofire采取的方法,這給了我信心。
因此,用RFC 3986編碼一個兼容性的查詢,我們可以百分號編碼如上所述以外的所有字符。這很簡單,如果我們首先構建一組允許的字符,然后用stringByAddingPercentEncodingWithAllowedCharacters去編碼剩余的。
注意:蘋果已經在iOS 9中棄用了stringByAddingPercentEscapesUsingEncoding或CFURLCreateStringByAddingPercentEscapes這兩個方法。
Swift
首先,swift String extension:extension?String?{
func?stringByAddingPercentEncodingForRFC3986()?->?String??{
let?unreserved?=?"-._~/?"
let?allowed?=?NSMutableCharacterSet.alphanumericCharacterSet()
allowed.addCharactersInString(unreserved)
return?stringByAddingPercentEncodingWithAllowedCharacters(allowed)
}
}
Object-C
我們可以用Object-C的NSString的分類來做相同的事。@implementation?NSString?(URLEncoding)
-?(nullable?NSString?*)stringByAddingPercentEncodingForRFC3986?{
NSString?*unreserved?=?@"-._~/?";
NSMutableCharacterSet?*allowed?=?[NSMutableCharacterSet?alphanumericCharacterSet];
[allowed?addCharactersInString:unreserved];
return?[self?stringByAddingPercentEncodingWithAllowedCharacters:?allowed];
}
@end
用例//?Swift
let?query?=?"one&two?=three"
let?encoded?=?query.stringByAddingPercentEncodingForRFC3986()
//?"one%26two%20%3Dthree"
//?Objective-C
NSString?*query?=?@"one&two?=three";
NSString?*encoded?=?[query?stringByAddingPercentEncodingForRFC3986];
//?"one%26two%20%3Dthree"
對x-www-form-urlencoded進行編碼
推薦W3C HTML5 對表單數據編碼是相似的,但是和RFC 3986有一點不同。在第4.10.22.5節中告訴我們下列字符是不應該百分號編碼:
ALPHA / DIGIT / “*” / “-” / “.” / “_”
α/數字/”-”/”.”/”_”
你應該用“+”(0x2B)代替空格(“ ”)。它和RFC 3986 的不同在 Stack Overflow answer 里有描述。波浪號(“~”)被百分號編碼了,但是星號(“*”)沒有。該建議很好地總結了這種情況:這種編碼的表單數據在很多方面是異常的,多年來的實踐的問題和折中解決導致了互通性的一系列必要操作。但是絕不代表好的設計實踐。
Swift
給String extension添加一個新的方法public?func?stringByAddingPercentEncodingForFormData(plusForSpace:?Bool=false)?->?String??{
let?unreserved?=?"*-._"
let?allowed?=?NSMutableCharacterSet.alphanumericCharacterSet()
allowed.addCharactersInString(unreserved)
if?plusForSpace?{
allowed.addCharactersInString("?")
}
var?encoded?=?stringByAddingPercentEncodingWithAllowedCharacters(allowed)
if?plusForSpace?{
encoded?=?encoded?.stringByReplacingOccurrencesOfString("?",
withString:?"+")
}
return?encoded
}
注意,由于很多 web服務好像不關心我用“+”或者百分號編碼將空格做了可選的編碼。
Object-C
Object-C的方法缺少一個可選參數-?(nullable?NSString?*)stringByAddingPercentEncodingForFormData:(BOOL)plusForSpace
{
NSString?*unreserved?=?@"*-._";
NSMutableCharacterSet?*allowed?=?[NSMutableCharacterSet?????????????????????????????????????alphanumericCharacterSet];
[allowed?addCharactersInString:unreserved];
if?(plusForSpace)?{
[allowed?addCharactersInString:@"?"];
}
NSString?*encoded?=?[self?stringByAddingPercentEncodingWithAllowedCharacters:allowed];
if?(plusForSpace)?{
encoded?=?[encoded?stringByReplacingOccurrencesOfString:@"?"?withString:@"+"];
}
return?encoded;
}
用例://?Swift
let?query?=?"one?two"
let?space?=?query.stringByAddingPercentEncodingForFormData()
//?"one%20two"
let?plus?=?query.stringByAddingPercentEncodingForFormData(true)
//?"one+two"
//?Objective-C
NSString?*query?=?@"one?two";
NSString?*encodedQuery?=?[query?stringByAddingPercentEncodingForFormData:YES];
//?"one+two"
源代碼
Swift代碼和一些測試用例你可以在我的Github代碼實例庫的Encode項目里找到,Object-C的分類和測試用例在TwitterSearch項目里。歡迎反饋和改進。
深入閱讀