讀AFNetworking 3.0 源碼記錄

知名的iOS網絡框架 AFNetworking 3.0 發布一段時間了,現在來閱讀記錄一下。(注:我目前閱讀的版本是3.1.0)

AFNetworking 3.0 的改動


AFNetworking 3.0 拋棄了基于 NSURLConnection 的API,全力支持 NSURLSession, 在Xcode7中,蘋果明確說明廢棄了 NSURLConnection, 建議全面使用 NSURLSession:

/*** DEPRECATED: The NSURLConnection class should no longer be used.  NSURLSession is the replacement for NSURLConnection ***/

所以, AFURLConnectionOperation, AFHTTPRequestOperation , AFHTTPRequestOperationManager 這幾個2.0版本中極為關鍵的幾個類已被全部移除。

AFURLSessionManager


AFNetworking 3.0 中關鍵的幾個類:AFURLSessionManagerAFHTTPSessionManager,先來看看 AFURLSessionManager 文件:

  1. AFURLSessionManager 的初始化,在 initWithSessionConfiguration 方法:
- (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];

    .....省略......
    return self;
}
  • 創建NSURLSession時需要設置NSURLSessionConfiguration,NSURLSessionConfiguration 有三種模式:

    1. 一般模式(default):工作模式類似于原來的NSURLConnection,可以使用緩存的Cache,Cookie,鑒權。
    2. 及時模式(ephemeral):不保存任何數據到磁盤,不使用緩存的Cache,Cookie,鑒權。
    3. 后臺模式(background):支持在后臺完成上傳下載 (需要iOS 8以上)
  • 創建NSURLSession時還需要設置 delegate , delegate 用來處理請求中的各種事件,可以設置為nil使用系統提供的delegate,但是要想支持后臺傳輸數據必須提供自定義實現的delegate;另外,NSURLSession對象是強引用了delegate,如果app最終沒有調用 invalidateAndCancel 方法 來invalidate 該session的話,則會造成內存泄漏。

  • 創建NSURLSession時可以設置相應的 OperationQueue, 決定請求過程中的一系列事件在哪個 OperationQueue 回調,這里是設置了最大并發量為1的隊列,也就相當于串行隊列了。(AFNetworing 2.0 版本是設置了一條常駐線程來響應所有網絡請求的delegate事件)

2 接著 AFURLSessionManager 當然實現了 NSURLSession Delegate的各個接口,挺容易看的,還是看一看它暴露的創建請求任務的方法吧:


- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}
  • 這里使用 url_session_manager_create_task_safely 的方法,是為了解決iOS8以下如果并發創建NSURLSessionTask時會出現的bug(源碼里有對應的鏈接)。這時候獲取的 NSURLSessionDataTask 是出于掛起狀態的,還不會發起網絡請求。NSURLSessionTask 有三個職能不同的子類,

    • NSURLSessionDataTask: 用于一般的請求資源,以NSData對象的方式返回服務器響應的數據,它不支持backround session;
    • NSURLSessionUploadTask: 用于上傳,支持backround session;
    • NSURLSessionDownloadTask: 用于下載數據到文件中,也支持backround session。
  • 我們再看看里面設置task代理的方法:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

針對于每個data task均有對應的delegate,這個一對一關系是保存在一個字典中,以task的唯一標志作為 key,并且取值賦值刪除的時候均要上鎖,確保線程安全;
再看一看自定義的 AFURLSessionManagerTaskDelegate 代理對象,它實現了不少功能:
1, 通過KVO的方式監聽上傳下載的進度并回調出去;
2, 請求接收的數據不斷追加到 mutableData :

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    [self.mutableData appendData:data];
}

3, 設置當前task完成后的回調:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;

它是在NSURLSession的代理方法中被調用的,

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    // delegate may be nil when completing a task in the background
    if (delegate) {
        [delegate URLSession:session task:task didCompleteWithError:error];

        [self removeDelegateForTask:task];
    }
    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

一開始容易混淆這兩個回調,其實是當task完成數據傳輸后,會回調上面的方法,然后根據task從字典中取出對應的 AFURLSessionManagerTaskDelegate 對象,然后 AFURLSessionManagerTaskDelegate 對象再進行調用完成的callback:

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{.......省略部分.....
    __strong AFURLSessionManager *manager = self.manager;
    __block id responseObject = nil;
    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }
    .........省略............
    
    if (error) {
        .......省略........
    } else {
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }
            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }
            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }

            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, serializationError);
                }
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
}

1, 這里有個技巧,將可變的數據拷貝后,馬上將mutableData置為nil來釋放回收內存,特別是處理大文件的時候效果就會出來了。具體描述請看這里.
2,它的流程是當請求沒有出錯時,異步調用 block 處理序列化task的響應對象,然后把數據對象再通過group異步回調出去.


其它:
a. NSStringFromSelector(_cmd)
_cmd表示當前方法的selector,正如self代表了當前方法調用的對象

// AFURLSessionManager中的方法:
- (NSArray *)tasks {
    //這里的NSStringFromSelector(_cmd) 與 NSStringFromSelector(@selector(tasks)) 相等
    return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}

b. Method Swizzle
在AFURLSessionManager文件中,有一段代碼用方法混寫的方式修復了iOS7 iOS8 NSURLSessionTask 出現的問題,方法混寫的知識已經爛大街了,就不寫了,這個問題具體再看源碼鏈接吧。

AFHTTPSessionManager


1, AFHTTPSessionManager 實現類就幾百行代碼,比較容易看,來看看它提供的GET請求方法吧:

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];
    [dataTask resume];
    return dataTask;
}

這里就是根據請求參數序列化創建請求對象設置進度以及請求成功或失敗的回調,然后返回dataTask給你并啟動,開始真正的網絡請求了。

2,按照 AFNetworking 3.0 給出的遷移文檔中,可以簡單使用 AFHTTPSessionManager 來發起GET請求:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"http://example.com/resources.json" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
    NSLog(@"JSON: %@", responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
    NSLog(@"Error: %@", error);
}];

很熟悉是吧,跟2.0版本一樣,獲取manager后調用GET方法,但是來看看AFHTTPSessionManager 類的manager 方法:

+ (instancetype)manager {
    return [[[self class] alloc] initWithBaseURL:nil];
}

問題來了,如果這么用的話,AFHTTPSessionManager對象調用請求GET方法后,一直沒有被釋放,因為它一直強引用著session即NSURLSession對象,而session一直被session的delegate強引用著(上面有提到),這樣就造成了循環引用導致內存泄漏。這是個坑啊??。這個問題很早以前就有人在Github上提過了,@mattt當時也回復了這里.
然后我看了AFNetworking 3.0 的示例Demo,發現它是這么用的,創建一個繼承AFHTTPSessionManager的類,提供獲取單例的方法:

+ (instancetype)sharedClient {
    static AFAppDotNetAPIClient *_sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedClient = [[AFAppDotNetAPIClient alloc] initWithBaseURL:[NSURL URLWithString:AFAppDotNetAPIBaseURLString]];
        _sharedClient.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
    });
    return _sharedClient;
}

那么以后再封裝AFNetworking 3.0 來用的話,得注意一下這個問題咯。另外swift版本的 Alamofire 的實現是不一樣的,有點差別,(呵呵):

public static let sharedInstance: Manager = {
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders

        return Manager(configuration: configuration)
 }()

最后


先寫到這里吧,有空再補充。以上均個人見解,歡迎交流。另外,個人簡單封裝了一下AFNetworking 3.0以便快捷安全使用,地址如下:Githud.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容