您现在的位置是:首页 >学无止境 >【iOS】AFNetworking源码解析网站首页学无止境

【iOS】AFNetworking源码解析

zdsey 2023-07-17 00:00:04
简介【iOS】AFNetworking源码解析

我们做APP发起网络请求,都离不开一个非常有用的框架AFNetworking,可以说这个框架的知名度已经超过了苹果的底层网络请求部分,很多人可能不知道苹果底层是如何发起网络请求的,但是一定知道AFNetworking,接下来我们就一起详细的解析一下这个框架。

简单介绍

AFNetworking是iOS、macOS、watchOS和tvOS的一个令人愉快的网络库。它建立在基础URL加载系统之上,扩展了构建到Cocoa中的强大的网络高级抽象。它有一个模块化的体系结构,具有设计良好、功能丰富的API,使用起来很愉快。

然而,也许最重要的特点是,每天都在使用AFNetworking并为其做出贡献的开发人员组成了一个令人惊叹的社区。AFNetworking为iPhone、iPad和Mac上一些最受欢迎、广受好评的应用提供了动力。

GitHub地址:AFNetworking

方法实现

GET请求

get请求的代码在AFN中还是十分的简洁明了的,这可能也是为什么相比起苹果原生网络请求,人们更愿意使用AFNetworking。

AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];
    [sessionManager GET:@"https://blog.csdn.net/zdsey?spm=1000.2115.3001.5343" parameters:nil headers:nil progress:^(NSProgress * _Nonnull downloadProgress) {
        NSLog(@"获取过程中的处理");
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"获取成功时的处理");
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"获取失败时的处理");
    }];

接口的调用

// 不需要进度回调
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                      success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                      failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    return [self GET:URLString parameters:parameters progress:nil success:success failure:failure];
}

// 需要进度的回调
- (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;
}

这里面五个参数,很好理解,请求的URL、参数、进度block、成功block和失败block。

这里我们看一下这个类NSURLSessionDataTask

/*
 * An NSURLSessionDataTask does not provide any additional
 * functionality over an NSURLSessionTask and its presence is merely
 * to provide lexical differentiation from download and upload tasks.
 */
@interface NSURLSessionDataTask : NSURLSessionTask

@end

NSURLSessionDataTask不提供NSURLSessionTask的任何附加功能,它的存在仅仅是为了提供下载和上载任务的词汇区分。

NSURLSessionDataTask的实例化

下面我们接着看进一步的调用

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

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

这里没用到上传,所以uploadProgress参数为nil,这种调用方式大家是不是很熟悉,感觉很好,对了,SDWebImage下载图像的接口就是这么调用的,最后走的都是同一个方法,只是个别参数为nil或0,最后在这个参数最全的方法里面做一些差别化的处理。大神都是这么写代码,不仅代码逻辑清晰,而且调用和查看代码也很方便。

这里做了两个方面的工作:

  • 实例化NSMutableURLRequest请求对象。
  • 实例化NSURLSessionDataTask对象,并调用下面方法返回该对象。
- (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

下面我们就一起看一下,这两个过程。

1. 调试小技巧

这里大家应该注意到有三行代码

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop

这个是做什么用的呢?在iOS开发过程中, 我们可能会碰到一些系统方法弃用, weak、循环引用、不能执行之类的警告。 它的作用其实就是忽略一些没用的警告用的,这里就是忽略?:条件表达式带来的警告。

例如下边就是忽略废弃方法产生的警告:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// 这里写出现警告的代码
#pragma clang diagnostic pop

//这样就消除了方法弃用的警告!

2. 实例化NSMutableURLRequest请求对象

下面我们就看一下该请求的实例化方法,对应下面这段代码。

/**
 Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` 
 are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters 
 for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies.
 使用`requestWithMethod:URLString:parameters:`&`multipartFormRequestWithMethod:URLString:parameters:constructBodyWithBlock:`
 创建的请求由一组使用此属性指定的参数序列化的默认标头构造而成。 默认情况下,它被设置为“AFHTTPRequestSerializer”的一个实例,
 该实例将GET,HEAD和DELETE请求的查询字符串参数序列化,或者URL形式编码HTTP消息体

 @warning `requestSerializer` must not be `nil`.
 */
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
/**
 The URL used to construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
 */
在requestWithMethod:URLString:parameters:和GET/POST等
便利方法中用于构造相对路径请求的URL。

@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

3. 实例化NSURLSessionDataTask对象,并获取和返回

  • 序列化是否错误的判断:

在实例化NSURLSessionDataTask对象之前,先判断请求的序列化是否有错误,对应的就是下边这段代码。

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

如果上面构建的请求序列化有错误,也就是serializationError不为nil,那么接着进行判断传入的failure是否为nil,如果不为nil,那么就在队列completionQueue中回调失败,这个很好理解,请求序列化都有错误,还能指望下载下来东西吗?直接进行错误的回调。默认在完成队列completionQueue中回调,如果该completionQueue队列为空,那么就在主队列进行回调,这里是一个三目运算符,failure回调第一个参数为nil,这里还没实例化NSURLSessionDataTask对象,当然为nil了,其实还是很好理解的。

/**
 The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
 */
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
  • NSURLSessionDataTask对象实例化:

下面就是该对象的实例化,主要对应下边这段代码。

__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;

这里可以看见,又调用了别的方法,并在回调中进行成功和失败的回调。

failure(dataTask, error);
success(dataTask, responseObject);

dataTaskWithRequest:…方法的调用

这里调用的自定义方法,如下所示:

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

这里我们一起来看一下都做了什么。

1. 实例化相关的iOS8的BUG

首先看一下函数,如下:

#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0

static void url_session_manager_create_task_safely(dispatch_block_t block) {
    if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
        // Fix of bug
        // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
        // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
        dispatch_sync(url_session_manager_creation_queue(), block);
    } else {
        block();
    }
}
static dispatch_queue_t url_session_manager_creation_queue() {
    static dispatch_queue_t af_url_session_manager_creation_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
    });

    return af_url_session_manager_creation_queue;
}

这里,首先判断如果NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug,那么就在串行队列中执行block。这里可能大家要问了,为什么要这么判断,有什么用?其实NSFoundationVersionNumber这个是获取系统版本的另外一种方式,这里标注这么做是因为iOS8出现的一个BUG。

这里写的很清晰了,就是为了防止iOS 8在并发队列上创建任务时,可能会调用错误的completionHandlers。当任务返回一个重复的taskIdentifier时,先前的completionHandler被清除并替换为新的。 如果第一个请求的数据在第二个请求的数据之前返回,那么将针对第二个completionHandler调用第一个响应。

我们在这个block里面回调做了什么?

dataTask = [self.session dataTaskWithRequest:request];

/* Creates a data task with the given request.  The request may have a body stream. */
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;

这个其实就是调用到苹果系统的方法里面了,根据request创建对应的任务NSURLSessionDataTask

2. 为指定的任务添加代理

下面我们就看一下为指定的任务NSURLSessionDataTask是如何添加代理的。

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

// 实现
- (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;
}

上面首先实例化AFURLSessionManagerTaskDelegate,并对改类内部的属性进行赋值,并调用下面的方法为task设置delegate

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

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}
  • 断言

这里首先要简单的说一下一中断言的形式NSParameterAssert(),它的作用就是括号里面参数不为nil就继续向下执行,如果为nil就触发断言崩溃。我用下面代码进行了测试。

NSParameterAssert(nil);

看一下输出结果

2018-02-28 09:46:30.336229+0800 JJWebImage[3893:1141549] *** Assertion failure in -[ViewController viewDidLoad], /Users/mac/Desktop/JJWebImage/JJWebImage/ViewController.m:42
2018-02-28 09:46:30.336755+0800 JJWebImage[3893:1141549] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: nil'

它其实是NSAssert的预编译,这样说明了为什么上面会输出那样的异常信息。

#define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
  • 加锁

由于多线程可能带来的数据不安全性,这里进行了加锁

[self.lock lock];
... ... 

[self.lock unlock];

需要保护的内容放在中间,让数据更安全。

首先,实例化一个可变字典,key为taskIdentifier,vlaue就是该任务的代理。

@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableTaskDelegatesKeyedByTaskIdentifier;

self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;

然后,就是代理为任务添加进度

[delegate setupProgressForTask:task];

最后,就是为任务添加通知观察

[self addNotificationObserverForTask:task];
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}

其实就是监听任务的开始和暂停,这个会在后面详述。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。