美文网首页iOS开发征服iOSiOS 大神之路
AFNetworking源码——设计思路

AFNetworking源码——设计思路

作者: 郑明明 | 来源:发表于2017-07-28 16:58 被阅读687次

    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形式进行暴露,更加简洁。

    相关文章

      网友评论

        本文标题:AFNetworking源码——设计思路

        本文链接:https://www.haomeiwen.com/subject/aphzkxtx.html