AFNetworking源碼——設計思路

AFNetworking是一個非常簡潔的框架,關于基本架構,可以看看這篇文章,本文主要闡述AFNetworking在設計上是如何對NSURLSession封裝的。本文大致分為兩個部分,第一個部分為NSURLSession的設計,第二個部分為AFNetworking的封裝設計

一、NSURLSession設計

NSURLSession主要由這幾個部分組成:

  • NSURLSession
  • NSURLSessionTask(擁有三種子類)
  • NSURLSessionConfiguration
  • 代理方法

首先我們通過一段Session的使用代碼來看各部分之間的關系:

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; // #1
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
                                                      delegate:self
                                                 delegateQueue:[NSOperationQueue mainQueue]]; // #2
NSURLSessionDataTask *task = [session dataTaskWithURL:[[NSURL alloc]initWithString:@""]]; // #3
[task resume];

為了方便理解這幾個部分之間的關系,這段代碼采用了delegate進行回調處理

  • #1:創(chuàng)建了NSURLSessionConfiguration對象,該對象的工廠模式方法提供了三種對象
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration;
@property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration;
#endif
 + (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0);

以上有三種方法,下面簡要介紹下每種類型的特點

  • defaultSessionConfiguration:默認的配置,和NSURLConnection的配置類似,使用硬盤來緩存數據(不同的是NSURLConnection的配置是全局的)
  • ephemeralSessionConfiguration:不會將Cookie、緩存等存儲到磁盤,而是放在內存中,程序退出時數據會消失(可以用于私密瀏覽)
  • backgroundSessionConfigurationWithIdentifier:可以在應用程序掛起、退出、崩潰的情況下運行下載和上傳任務,會在后臺另外開啟一個線程,但是系統(tǒng)會根據負載程度去調度這個線程的操作,可能會造成速度緩慢或者超時

三種工廠提供了三種對象具有不同的特點,除此之外,NSURLSessionConfiguration擁有很多的屬性可以進行配置



這里列出一些常用屬性:

  • @property NSTimeInterval timeoutIntervalForRequest;:請求超時
  • @property NSTimeInterval timeoutIntervalForResource;:資源超時
  • @property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;:請求頭,配置如下
configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
                                            @"Accept-Language": @"en",
                                            @"Accept-Encoding": @"",
                                            @"Authorization": @"",
                                            @"Connection": @"",
                                            @"User-Agent": @""};

可以發(fā)現字典中的key都是標準的HTTP請求頭的key,可以通過這種方式對請求頭進行自定義配置

  • #2:創(chuàng)建了NSURLSession對象,依賴于三個參數

    • NSURLSessionConfiguration:配置
    • Delegate:代理對象
    • DelegateQueue:代理隊列,在NSURLConnection中往往需要指定代理隊列,代表回調方法在哪個線程中執(zhí)行,NSURLSession提供了類初始化方法,省略了代理隊列的指定,默認為主線程的主隊列

    除了這種方式之外,還可以通過單例模式提供的類方法創(chuàng)建,內部實現可能沒有設置代理和代理隊列,采用的是默認配置

@property (class, readonly, strong) NSURLSession *sharedSession;

這里的session使用了delegate回調,蘋果還提供了block回調的形式:

NSURLSessionDataTask *task = [session dataTaskWithURL:[[NSURL alloc]initWithString:@""]
                                    completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                        
                                    }];
  • #3:創(chuàng)建NSURLSessionTask對象,NSURLSession提供了這幾種task類型

    • NSURLSessionTask:超類,一般不具體使用
    • NSURLSessionDataTask:請求
    • NSURLSessionUploadTask:上傳
    • NSURLSessionDownloadTask:下載

    它們之間的繼承關系如下:


總結:NSURLSession的設計主要有三個部分,三個部分相互獨立又具有聯(lián)系,NSURLSessionConfiguration進行配置管理。NSURLSession將配置,代理,代理隊列等對象關聯(lián),用于創(chuàng)建任務。NSURLSessionTask是任務類,其對象具有操作該任務的各種方法,啟動,暫停等,同時任務的回調提供兩種方式,block和代理。

二、AFNetworking的封裝設計

這里先回憶一下在基本架構這篇文章中所提到的AFNetworking的使用代碼被分為兩個部分,第一個部分是初始化AFHTTPSessionManager對象,第二個部分是調用請求方法。
先來看第一個部分,我們再次回顧一下方法的調用棧

- [AFHTTPSessionManager initWithBaseURL:]
    - [AFHTTPSessionManager initWithBaseURL:sessionConfiguration:] // #1
        - [AFURLSessionManager initWithSessionConfiguration:] // #2
            - [NSURLSession sessionWithConfiguration:delegate:delegateQueue:]
            - [AFJSONResponseSerializer serializer] 
            - [AFSecurityPolicy defaultPolicy] 
            - [AFNetworkReachabilityManager sharedManager] 
        - [AFHTTPRequestSerializer serializer] 
        - [AFJSONResponseSerializer serializer] 

我們順著方法調用棧,看方法具體實現細節(jié),從而理解AFNetworking是如何封裝NSURLSession的

  • #1
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration]; // &1
    if (!self) {
        return nil;
    }
    // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }
    self.baseURL = url;  // &2
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    return self;
}

&1:這里調用了父類AFURLSessionManager的初始化方法
&2:設置了baseURL

  • #2
 - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; // &1
    }
    self.sessionConfiguration = configuration;
    // &2
    self.operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue.maxConcurrentOperationCount = 1;
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; // &3
    // &4
    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;
    // &5
    [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;
}

&1:確定配置對象(原生調用)
&2:設置一個隊列,并設置為串行(最大并發(fā)為1),在&3中創(chuàng)建session的時候作為參數(由于AFNetworking將AFURLSessionManager類作為了NSURLSession的代理,所以這里另外添加一個在子線程中的操作隊列)
&3:創(chuàng)建NSURLSession對象(原生調用)
&4:設置AFNetworking中的序列化和安全策略(AF自己的模塊封裝)
&5:為每個task添加一個AF封裝的Delegate,后文會提到

  • 總結第一部分
    從上面的代碼細節(jié)來看,AFHTTPSessionManager的初始化操作就是做了這些事情:

    • 獲得了NSURLSessionConfiguration對象
    • 創(chuàng)建了NSURLSession對象,并設置自身為代理類,添加了操作隊列
    • 設置了AF自己封裝的序列化和安全策略

    這樣一來,就很容易看出,其實就是在原生的初始化操作上添加了一些AF自己封裝的策略對象

接下來再看看第二部分,同樣回顧一下調用棧
使用GET:parameters:process:success:failure:方法作為例子來查看一下源碼實現

- [AFHTTPSessionManager GET:parameters:process:success:failure:] // #1
    - [AFHTTPSessionManager dataTaskWithHTTPMethod:parameters:uploadProgress:downloadProgress:success:failure:] // #2
        - [AFHTTPRequestSerializer requestWithMethod:URLString:parameters:error:] 
        - [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:]  // #3
            - [NSURLSession dataTaskWithRequest:]
            - [AFURLSessionManager addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler:] // #4
                - [AFURLSessionManagerTaskDelegate init]
                - [AFURLSessionManager setDelegate:forTask:] // #5
    - [NSURLSessionDataTask resume]
  • #1
- (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;
}

從表層能夠看出返回了一個NSURLSessionDataTask對象,并且調用了resume操作(和原生一樣),我們接著看下返回對象的方法是如何實現的

  • #2
 - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; // &1
    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }
    // &2
    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

&1:創(chuàng)建了NSURLRequest,可以發(fā)現AF內部是通過Request的方式創(chuàng)建的task,而不是URL
&2:調用另外一個方法返回task對象

  • #3
  - (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]; // &1
    });

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

    return dataTask;
}

&1:這部分就很熟悉了,利用原生的方式通過request來創(chuàng)建task對象,然后返回。到這里,就已經能明白關于task對象是如何被封裝返回的
&2:按照名字來看,這里好像是為task對象添加代理方法的,我們繼續(xù)往下研究

  • #4
  - (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
{
    // &1
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask]; // &2
    // &3
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

&1:新建了一個AFURLSessionManagerTaskDelegate對象,是AF自己封裝的代理對象
&2:看樣子還需要進一步查看設置代理的細節(jié),這一個方法傳入了代理對象和task對象,我們繼續(xù)看
&3:將block賦值給delegate的block屬性,方便回調

  • #5
  - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock]; // &1
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; // &2
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

&1:有個加鎖操作,保證線程安全
&2:用task的taskIdentifiier屬性為key,delegate為value來進行對應,到這里,可以知道AF是用字典將delegate和task一一對應的

  • 總結第二部分
    通過上面的分析,可以看出第二部分實際上就是利用NSURLRequest去創(chuàng)建NSURLSessionTask對象。同時呢,AFURLSessionManager作為了NSURLSession的代理,AF內部自定義了一個AFURLSessionManagerTaskDelegate代理類,該類具有很多block屬性。并且,AF在內部實現了NSURLSession的代理方法,方法實現中實現block賦值,代理類的block屬性對外暴露,在合適的地方回調。AF將很多NSRULSession中的代理方法都變成block形式進行暴露,更加簡潔。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容