AFSecurityPolicy 類用於服務器 SSL 證書安全鏈接,不講解,自行觀看其實現。javascript
這裏包含了三個類和兩個函數:html
AFHTTPRequestSerializerjava
AFJSONRequestSerializergit
AFPropertyListRequestSerializergithub
NSString AFPercentEscapedStringFromString(NSString string)編程
NSString AFQueryStringFromParameters(NSString string)json
後面兩個函數一個用於請求字符串轉義,另外一個是生成編碼 URL 請求參數工具方法數組
AFHTTPRequestSerializer 自己繼承自 NSObject,實現了 AFURLRequestSerialization 協議,此協議是框架定義的,代碼以下安全
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying> - (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW; @end
就是根據 request 和請求參數構造 NSURLRequest 對象。
而後其中就是一系列成員變量用於存儲 HTTP 請求所需的各項參數,看到這裏你們應該也看出來了,這些類實際上不是真正存儲 HTTPRequest 的對象,而是構造出一個模型類,模型類實現了特定生成方法,而後生成方法根據類成員變量構造實際的 HTTPRequest。明白了這一點,代碼就很容易看懂了,由於徹底是根據 HTTP 協議所需構造的類模型。有 HTTPHeader,有 RequestBody。先來看初始化方法服務器
{ self = [super init]; if (!self) { return nil; } self.stringEncoding = NSUTF8StringEncoding; self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 NSMutableArray *acceptLanguagesComponents = [NSMutableArray array]; [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { float q = 1.0f - (idx * 0.1f); [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]]; *stop = q <= 0.5f; }]; [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"]; NSString *userAgent = nil; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" #if TARGET_OS_IOS // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]]; #elif TARGET_OS_WATCH // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]]; #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]]; #endif #pragma clang diagnostic pop if (userAgent) { if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) { NSMutableString *mutableUserAgent = [userAgent mutableCopy]; if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) { userAgent = mutableUserAgent; } } [self setValue:userAgent forHTTPHeaderField:@"User-Agent"]; } // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil]; self.mutableObservedChangedKeyPaths = [NSMutableSet set]; for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; } } return self; }
首先設置編碼類型爲 NSUTF8StringEncoding
,而後填充 Accept-Language
請求頭,設置 UserAgent,設置須要在 URI 中編碼的 HTTP 方法名稱。
AFJSONRequestSerializer 繼承自 AFHTTPRequestSerializer,相對於 AFHTTPRequestSerializer 只是將參數所有設置到 HTTPBody 了而已。其中 JSON 轉化使用的就是蘋果自家的 NSJSONSerialization。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { return [super requestBySerializingRequest:request withParameters:parameters error:error]; } NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]]; } return mutableRequest; }
AFPropertyListRequestSerializer 基本和 AFJSONRequestSerializer 差很少,只不過使用 NSPropertyListSerialization 將其轉化爲了 PropertyList 的 XML 格式罷了。
AFURLResponseSerialization 包含了
AFHTTPResponseSerializer
AFXMLParserResponseSerializer
AFPropertyListResponseSerializer
AFJSONResponseSerializer
AFImageResponseSerializer
AFCompoundResponseSerializer
其實和 AFURLRequestSerialization 的流程是同樣的,都是經過存儲屬性,使用協議方法編碼解碼相應的 Request 或者 Response。
下面是 AFHTTPRequestSerialization 的初始化方法
- (instancetype)init { self = [super init]; if (!self) { return nil; } self.stringEncoding = NSUTF8StringEncoding; self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; self.acceptableContentTypes = nil; return self; }
確實比請求對象的初始化方法簡單太多了,只有編碼類型、有效響應狀態碼(200 - 299)、可接受內容類型等成員變量初始化。
而後再看實現的協議方法
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { [self validateResponse:(NSHTTPURLResponse *)response data:data error:error]; return data; }
實際上就是調用了另外一個實例方法 validateResponse:data:
。
- (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error { BOOL responseIsValid = YES; NSError *validationError = nil; if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] && !([response MIMEType] == nil && [data length] == 0)) { if ([data length] > 0 && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]], NSURLErrorFailingURLErrorKey:[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy]; if (data) { mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; } validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError); } responseIsValid = NO; } if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode], NSURLErrorFailingURLErrorKey:[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy]; if (data) { mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; } validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError); responseIsValid = NO; } } if (error && !responseIsValid) { *error = validationError; } return responseIsValid; }
看起來代碼雖然不少,可是不要慌,仔細看下去,實際上就只有兩項檢查:檢查 Content-Type
、檢查 HTTPStatusCode
。
AFJSONResponseSerializer 繼承於 AFHTTPResponseSerializer。相比於父類,這個類纔是真正開始使用返回的數據,首先在初始化函數中添加以下代碼
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
也就是說接受 JSON 數據而且轉義。而後就是協議方法
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } id responseObject = nil; NSError *serializationError = nil; // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization. // See https://github.com/rails/rails/issues/1742 BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]]; if (data.length > 0 && !isSpace) { responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError]; } else { return nil; } if (self.removesKeysWithNullValues && responseObject) { responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions); } if (error) { *error = AFErrorWithUnderlyingError(serializationError, *error); } return responseObject; }
除了驗證返回的數據外,還使用 NSJSONSerialization 將數據轉義成字典或者數組。除此之外,還根據 removesKeysWithNullValues 的值決定是否移除 [NSNull null]
類型,AFJSONObjectByRemovingKeysWithNullValues 函數使用遞歸的方法,最終構造出無 null 的對象。這點確實能夠借鑑一二。
這個類是最大的類,AFURLSessionManager 繼承自 NSObject,實現 <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
協議,用過蘋果網絡通訊的朋友應該知道,NSURLSession 使用 block 或者 delegate 傳遞各種狀態,這裏其實是使用了 delegate。
每一個 AFURLSessionManager 都持有 NSURLSession、NSOperationQueue、id<AFURLResponseSerialization>、AFSecurityPolicy、AFNetworkReachabilityManager、NSArray<NSURLSessionTask >、NSArray<NSURLSessionDataTask >、NSArray<NSURLSessionUploadTask >、NSArray<NSURLSessionDownloadTask > 等成員變量,這裏基本上也能看出 AFURLSessionManager 到底作了什麼,先來看初始化函數
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super init]; if (!self) { return nil; } if (!configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } self.sessionConfiguration = configuration; self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 1; self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; self.responseSerializer = [AFJSONResponseSerializer serializer]; self.securityPolicy = [AFSecurityPolicy defaultPolicy]; #if !TARGET_OS_WATCH self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; #endif self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init]; self.lock = [[NSLock alloc] init]; self.lock.name = AFURLSessionManagerLockName; [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { for (NSURLSessionDataTask *task in dataTasks) { [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil]; } for (NSURLSessionUploadTask *uploadTask in uploadTasks) { [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil]; } for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil]; } }]; return self; }
這裏傳入了一個參數 NSURLSessionConfiguration,熟悉網絡編程的朋友應該知道,NSURLSession 有三種配置類型 defaultSessionConfiguration
、ephemeralSessionConfiguration
、backgroundSessionConfiguration
,三者區別以下:
Default session 使用持久磁盤 cache,用戶憑證存儲在鑰匙串中。
Ephemeral session 不存儲任何數據到磁盤中。
Background session 和 Default session 相似,可是在單獨進程中運行
通常狀況下是 Default session configuration 類型。operationQueue 用於異步回調使用 self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
則很顯而易見的看出來 session 建立的過程。同時建立 AFJSONResponseSerializer 做爲響應序列化器,建立 securityPolicy 和 reachabilityManager。
最後使用了很巧妙的方法,getTasksWithCompletionHandler 函數返回全部的 task,而後存儲到各自的數組中。
對於子類調用的 dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
其實是調用了 url_session_manager_create_task_safely
block,實際上調用的是 [NSURLSession dataTaskWithRequest]
,而後調用 - [AFURLSessionManager addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler:]
將 task 的回調設置爲自身,你們能夠注意到,代碼中有不少地方使用了鎖機制,由於回調自己是放到 GCD 多線程中的,因此須要注意競爭問題致使的資源搶佔。
AFNetworking 實際上只是對蘋果自己的 NSURLSession 作了封裝,幫助開發者更容易得到各種信息,好比進度條、信息編碼解碼,而真正想要使用好網絡通訊,蘋果自身提供的 NSURLSession 纔是須要仔細研究的。筆者認爲,AFNetworking 其實是開源項目中可貴的容易閱讀,代碼也很少的項目,很是值得做爲第一個入手的源碼閱讀項目。