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:创建了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:可以在应用程序挂起、退出、崩溃的情况下运行下载和上传任务,会在后台另外开启一个线程,但是系统会根据负载程度去调度这个线程的操作,可能会造成速度缓慢或者超时

三种工厂提供了三种对象具有不同的特点,除此之外,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": @""};

可以发现字典中的key都是标准的HTTP请求头的key,可以通过这种方式对请求头进行自定义配置

  • #2:创建了NSURLSession对象,依赖于三个参数

    • NSURLSessionConfiguration:配置
    • Delegate:代理对象
    • DelegateQueue:代理队列,在NSURLConnection中往往需要指定代理队列,代表回调方法在哪个线程中执行,NSURLSession提供了类初始化方法,省略了代理队列的指定,默认为主线程的主队列

    除了这种方式之外,还可以通过单例模式提供的类方法创建,内部实现可能没有设置代理和代理队列,采用的是默认配置

@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:创建NSURLSessionTask对象,NSURLSession提供了这几种task类型

    • NSURLSessionTask:超类,一般不具体使用
    • NSURLSessionDataTask:请求
    • NSURLSessionUploadTask:上传
    • NSURLSessionDownloadTask:下载

    它们之间的继承关系如下:


总结:NSURLSession的设计主要有三个部分,三个部分相互独立又具有联系,NSURLSessionConfiguration进行配置管理。NSURLSession将配置,代理,代理队列等对象关联,用于创建任务。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] 

我们顺着方法调用栈,看方法具体实现细节,从而理解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:设置一个队列,并设置为串行(最大并发为1),在&3中创建session的时候作为参数(由于AFNetworking将AFURLSessionManager类作为了NSURLSession的代理,所以这里另外添加一个在子线程中的操作队列)
&3:创建NSURLSession对象(原生调用)
&4:设置AFNetworking中的序列化和安全策略(AF自己的模块封装)
&5:为每个task添加一个AF封装的Delegate,后文会提到

  • 总结第一部分
    从上面的代码细节来看,AFHTTPSessionManager的初始化操作就是做了这些事情:

    • 获得了NSURLSessionConfiguration对象
    • 创建了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:创建了NSURLRequest,可以发现AF内部是通过Request的方式创建的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来创建task对象,然后返回。到这里,就已经能明白关于task对象是如何被封装返回的
&2:按照名字来看,这里好像是为task对象添加代理方法的,我们继续往下研究

  • #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:看样子还需要进一步查看设置代理的细节,这一个方法传入了代理对象和task对象,我们继续看
&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去创建NSURLSessionTask对象。同时呢,AFURLSessionManager作为了NSURLSession的代理,AF内部自定义了一个AFURLSessionManagerTaskDelegate代理类,该类具有很多block属性。并且,AF在内部实现了NSURLSession的代理方法,方法实现中实现block赋值,代理类的block属性对外暴露,在合适的地方回调。AF将很多NSRULSession中的代理方法都变成block形式进行暴露,更加简洁。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容