讀AFNetworking 3.0 源碼記錄

知名的iOS網(wǎng)絡(luò)框架 AFNetworking 3.0 發(fā)布一段時(shí)間了,現(xiàn)在來(lái)閱讀記錄一下。(注:我目前閱讀的版本是3.1.0)

AFNetworking 3.0 的改動(dòng)


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

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

所以, AFURLConnectionOperation, AFHTTPRequestOperation , AFHTTPRequestOperationManager 這幾個(gè)2.0版本中極為關(guān)鍵的幾個(gè)類已被全部移除。

AFURLSessionManager


AFNetworking 3.0 中關(guān)鍵的幾個(gè)類:AFURLSessionManagerAFHTTPSessionManager,先來(lái)看看 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;
}
  • 創(chuàng)建NSURLSession時(shí)需要設(shè)置NSURLSessionConfiguration,NSURLSessionConfiguration 有三種模式:

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

  • 創(chuàng)建NSURLSession時(shí)可以設(shè)置相應(yīng)的 OperationQueue, 決定請(qǐng)求過(guò)程中的一系列事件在哪個(gè) OperationQueue 回調(diào),這里是設(shè)置了最大并發(fā)量為1的隊(duì)列,也就相當(dāng)于串行隊(duì)列了。(AFNetworing 2.0 版本是設(shè)置了一條常駐線程來(lái)響應(yīng)所有網(wǎng)絡(luò)請(qǐng)求的delegate事件)

2 接著 AFURLSessionManager 當(dāng)然實(shí)現(xiàn)了 NSURLSession Delegate的各個(gè)接口,挺容易看的,還是看一看它暴露的創(chuàng)建請(qǐng)求任務(wù)的方法吧:


- (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以下如果并發(fā)創(chuàng)建NSURLSessionTask時(shí)會(huì)出現(xiàn)的bug(源碼里有對(duì)應(yīng)的鏈接)。這時(shí)候獲取的 NSURLSessionDataTask 是出于掛起狀態(tài)的,還不會(huì)發(fā)起網(wǎng)絡(luò)請(qǐng)求。NSURLSessionTask 有三個(gè)職能不同的子類,

    • NSURLSessionDataTask: 用于一般的請(qǐng)求資源,以NSData對(duì)象的方式返回服務(wù)器響應(yīng)的數(shù)據(jù),它不支持backround session;
    • NSURLSessionUploadTask: 用于上傳,支持backround session;
    • NSURLSessionDownloadTask: 用于下載數(shù)據(jù)到文件中,也支持backround session。
  • 我們?cè)倏纯蠢锩嬖O(shè)置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;
}

針對(duì)于每個(gè)data task均有對(duì)應(yīng)的delegate,這個(gè)一對(duì)一關(guān)系是保存在一個(gè)字典中,以task的唯一標(biāo)志作為 key,并且取值賦值刪除的時(shí)候均要上鎖,確保線程安全;
再看一看自定義的 AFURLSessionManagerTaskDelegate 代理對(duì)象,它實(shí)現(xiàn)了不少功能:
1, 通過(guò)KVO的方式監(jiān)聽(tīng)上傳下載的進(jìn)度并回調(diào)出去;
2, 請(qǐng)求接收的數(shù)據(jù)不斷追加到 mutableData :

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

3, 設(shè)置當(dāng)前task完成后的回調(diào):

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

它是在NSURLSession的代理方法中被調(diào)用的,

- (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);
    }
}

一開(kāi)始容易混淆這兩個(gè)回調(diào),其實(shí)是當(dāng)task完成數(shù)據(jù)傳輸后,會(huì)回調(diào)上面的方法,然后根據(jù)task從字典中取出對(duì)應(yīng)的 AFURLSessionManagerTaskDelegate 對(duì)象,然后 AFURLSessionManagerTaskDelegate 對(duì)象再進(jìn)行調(diào)用完成的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, 這里有個(gè)技巧,將可變的數(shù)據(jù)拷貝后,馬上將mutableData置為nil來(lái)釋放回收內(nèi)存,特別是處理大文件的時(shí)候效果就會(huì)出來(lái)了。具體描述請(qǐng)看這里.
2,它的流程是當(dāng)請(qǐng)求沒(méi)有出錯(cuò)時(shí),異步調(diào)用 block 處理序列化task的響應(yīng)對(duì)象,然后把數(shù)據(jù)對(duì)象再通過(guò)group異步回調(diào)出去.


其它:
a. NSStringFromSelector(_cmd)
_cmd表示當(dāng)前方法的selector,正如self代表了當(dāng)前方法調(diào)用的對(duì)象

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

b. Method Swizzle
在AFURLSessionManager文件中,有一段代碼用方法混寫的方式修復(fù)了iOS7 iOS8 NSURLSessionTask 出現(xiàn)的問(wèn)題,方法混寫的知識(shí)已經(jīng)爛大街了,就不寫了,這個(gè)問(wèn)題具體再看源碼鏈接吧。

AFHTTPSessionManager


1, AFHTTPSessionManager 實(shí)現(xiàn)類就幾百行代碼,比較容易看,來(lái)看看它提供的GET請(qǐng)求方法吧:

- (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;
}

這里就是根據(jù)請(qǐng)求參數(shù)序列化創(chuàng)建請(qǐng)求對(duì)象設(shè)置進(jìn)度以及請(qǐng)求成功或失敗的回調(diào),然后返回dataTask給你并啟動(dòng),開(kāi)始真正的網(wǎng)絡(luò)請(qǐng)求了。

2,按照 AFNetworking 3.0 給出的遷移文檔中,可以簡(jiǎn)單使用 AFHTTPSessionManager 來(lái)發(fā)起GET請(qǐng)求:

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后調(diào)用GET方法,但是來(lái)看看AFHTTPSessionManager 類的manager 方法:

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

問(wèn)題來(lái)了,如果這么用的話,AFHTTPSessionManager對(duì)象調(diào)用請(qǐng)求GET方法后,一直沒(méi)有被釋放,因?yàn)樗恢睆?qiáng)引用著session即NSURLSession對(duì)象,而session一直被session的delegate強(qiáng)引用著(上面有提到),這樣就造成了循環(huán)引用導(dǎo)致內(nèi)存泄漏。這是個(gè)坑啊??。這個(gè)問(wèn)題很早以前就有人在Github上提過(guò)了,@mattt當(dāng)時(shí)也回復(fù)了這里.
然后我看了AFNetworking 3.0 的示例Demo,發(fā)現(xiàn)它是這么用的,創(chuàng)建一個(gè)繼承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 來(lái)用的話,得注意一下這個(gè)問(wèn)題咯。另外swift版本的 Alamofire 的實(shí)現(xiàn)是不一樣的,有點(diǎn)差別,(呵呵):

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

        return Manager(configuration: configuration)
 }()

最后


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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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