【iOS】—— AFNetworking源码学习

news2024/11/24 15:41:57

AFNetworking

文章目录

  • AFNetworking
    • AFHTTPSessionManager *sessionManager =[AFHTTPSessionManager manager];
    • 请求过程
      • 1、调用请求序列化类中的requestWithMethod方法进行序列化处理
      • 2、调用dataTaskWithRequest来生成一个datatask任务
    • AFURLSessionManger
    • AFHTTPSessionManager
    • UIKit+AFNetworking
      • AFNetworking4.0较AFNetworking3.0有什么变动?
      • 用AFNetworking第三方库去进行网络请求比原生的网络请求好在哪里?
      • AFSecurityPolicy怎么保证请求的安全性
      • AFNetworkReachabilityManager是干什么的

AFNetworking在之前写项目的时候也用到过,今天主要来学学它的源码,关于它的使用可以看看我之前的博客:【iOS】—— GET和POST以及AFNetworking框架

对于最简单的get请求,我们会有两段语句:

AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];

[sessionManager GET:@"https://img1.baidu.com/it/u=1966616150,2146512490&fm=253&fmt=auto&app=138&f=JPEG?w=751&h=500" 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(@"获取失败的处理");
    }];

就对于这两句最基本的语句我们来展开详解,先来看第一句:

AFHTTPSessionManager *sessionManager =[AFHTTPSessionManager manager];

先来看一张图:
请添加图片描述
这张图概括了这个方法的全部过程,我们接下来来细细分析一下:
首先来看首先调用的manager方法:

@interface AFHTTPSessionManager ()
@property (readwrite, nonatomic, strong) NSURL *baseURL;
@end

@implementation AFHTTPSessionManager
@dynamic responseSerializer;

//instancetype是实例类型的意思
//这个方法就是初始化manager时调用的方法(通过一层层函数调用实现了初始化)
+ (instancetype)manager {
    return [[[self class] alloc] initWithBaseURL:nil];
}

- (instancetype)init {
    return [self initWithBaseURL:nil];
}

- (instancetype)initWithBaseURL:(NSURL *)url {
    return [self initWithBaseURL:url sessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
//核心的方法
- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
	//这里很关键,调用了父类的同名方法
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }

    // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
    //确保baseURL路径的终端斜杠,以便NSURL+URLWithString:relativeToURL:按预期工作, 若没有/时
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }
	
	//初始化URL,请求序列化和序列化工具
    self.baseURL = url;

    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

AFHTTPSessionManager中的初始化方法在该类中做简单处理之后最终都会调用其父类AFURLSessionManager的initWitchSessionConfiguration初始化方法,我们来看看父类这个方法:

//初始化一个会话
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }
    //如果会话配置为nil,就对应初始化一个
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }

    self.sessionConfiguration = configuration;

    //吹初始化操作队列,并设置为串行队列 设置最大并发操作数
    self.operationQueue = [[NSOperationQueue alloc] init];
    //maxConcurrentOperationCount是最大并发操作数的意思
    self.operationQueue.maxConcurrentOperationCount = 1;

    //AFJSONResponseSerializer用来序列化HTTP响应
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    //初始化SSL所需要的AFSecurityPolicy 用来保证请求的安全性
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
    //reachabilityManager是可达性管理器的意思, 用来判断网络的连接情况
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
    //mutableTaskDelegatesKeyedByTaskIdentifier是可变任务委托者的意思 (初始化可变任务字典,task的id作为key,代理对象作为value)
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    //初始化锁 & 重名
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    //Completion Handler是完成处理程序的意思
    [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;
}

  • 初始化当前的会话配置,操作队列,锁,AFNetworkReachabilityManager,AFSecurityPolicy,请求序列化以及用来存储任务的可变任务字典等属性;
  • 获取当前session中所有未完成的task,给它们设置一遍代理;

这个方法有三点需要注意的地方:

  • 队列的最大并发操作数设置为1,这里的并发操作数值的是回调代理的线程并发数。至于为什么要串行队列,控制请求执行的顺序,避免并发操作导致的竞争和死锁等问题。同时,将最大并发操作数设置为1也可以减少内存使用,防止系统资源过度消耗,并且可以更好地管理后台任务。
  • self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];是用来将每一个请求任务和自定义的AFURLSessionManagerTaskDelegate来建立映射的;(需要深入研究,代理和这里的关系,以及利用KVO的思想实现的相关)
  • 在初始化的时候获取当前session中的所有task,为它们重新设置一遍代理;一般来说初始化的session中的task应该是为空的,这里这么做的主要目的是为了防止从后台回来的时候初始化session,对于一些后台之前的请求任务没有重设代理导致崩溃的问题;这里里边不同的任务调用不同的addDelegateForXXX方法来设置代理,看一下这些方法的实现:
//为data任务添加代理
- (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] initWithTask:dataTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    //调用setDelegate 存储datatask
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

//为upload任务添加代理
- (void)addDelegateForUploadTask:(NSURLSessionUploadTask *)uploadTask
                        progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
               completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:uploadTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    uploadTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:uploadTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
}

//为download任务添加代理
- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                          progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                       destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                 completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    if (destination) {
        delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
            return destination(location, task.response);
        };
    }

    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:downloadTask];

    delegate.downloadProgressBlock = downloadProgressBlock;
}

这三个方法都是为不同的任务设置代理,最终都会调用setDelegate设置代理并存储datatask任务;

//以key-value的形式存储task并且通过NSLock来保证不同线程的使用,避免出现线程竞争的问题
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    //NSLock来保证不同线程的使用,避免出现线程竞争的问题
    [self.lock lock];
    //向保存任务的可变字典中添加代理(task的id作为key,代理对象作为value)
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    //为仍无添加解析
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

这个方法主要是把代理和task建立映射关系,并且存储到事先声明好的字典当中;同时为当前task添加监听;在添加监听的方法中,taskDidResume和taskDidSuspend为接收到通知后的处理方法;用来恢复任务和暂停任务;至此,对于初始化的时候获取当前session中的所有task,已经为它们重新设置一遍代理。回到initWithSessionConfiguration方法中返回当前对象,向上返回,生成AFHTTPSessionManager *sessionManger对象;

请求过程

接下来,利用生成的sessionManager发起请求:来看看****sessionManager调用GET方法都做了哪些事,以下是图解流程,然后对比源码层面进行分析:
请添加图片描述
还是一样,点击这个方法进入:

/**
创建并运行带有“GET”请求的“NSURLSessionDataTask”。
@param URLString用于创建请求URL的URL字符串。
@param parameters根据客户端请求序列化程序编码的参数。
@param headers追加到此请求的默认头的头。
@param downloadProgress更新下载进度时要执行的块对象。注意:此块在会话队列上调用,而不是在主队列上调用。
@param success任务成功完成时要执行的块对象。此块没有返回值,并接受两个参数:数据任务和客户机响应序列化程序创建的响应对象。
@param failure任务未成功完成或成功完成但在分析响应数据时遇到错误时要执行的块对象。此块没有返回值,并接受两个参数:数据任务和描述网络的错误或发生的解析错误。
@请参阅-dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
*/
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(nullable id)parameters
                      headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                     progress:(nullable void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    //调用dataTaskWithHTTPMethod方法生成一个dataTask任务
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                          headers:headers
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];
    //调用resume,开始请求
    [dataTask resume];
    return dataTask;
}

这个函数里做了两件事情,第一件事是调用dataTaskWithHTTPMethod方法初始化了一个NSURLSessionDataTask *dataTask ,然后调用系统方法开启请求。
接下来我们来看dataTaskWithHTTPMethod方法:

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(nullable id)parameters
                                         headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                         failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
    //序列化错误
    NSError *serializationError = nil;
    //设置request相关属性&参数,(requestSerializer:请求序列化器)
    //调用请求序列化类中的requestWithMethod方法进行序列化处理
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    //设置请求头
    for (NSString *headerField in headers.keyEnumerator) {
        [request setValue:headers[headerField] forHTTPHeaderField:headerField];
    }
    //序列化失败的回调处理
    if (serializationError) {
        if (failure) {
            //completionQueue如果设置了这个GCD的queue,那么从这个completionQueue中回调结果就好,否则就从主队列回调
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }

        return nil;
    }

    //NSURLSessionManager创建一个datatask
    //调用dataTaskWithRequest来生成一个datatask任务
    __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;
}

这个方法里大致也是做了两点,第一点调用请求序列化类中的requestWithMethod方法进行序列化处理,第二点调用dataTaskWithRequest来生成一个datatask任务。

我们先来看第一个:

1、调用请求序列化类中的requestWithMethod方法进行序列化处理

通过requestSerializer来调用requestWithMethod对请求参数进行序列化,最终生成一个最终请求网络需要的request实例;这里提出一个问题:为什么要进行序列化?那这里,先看看requestWithMethod方法内部都做了些什么:

序列化处理是什么?
序列化处理是将对象转换为可存储或传输格式的过程,通常使用字节流进行表示。在序列化时,对象的属性被打包成一组字节,并可以在需要时重新构造成相同的对象。这种技术通常用于在网络上传递对象、将对象保存到磁盘或数据库中、以及在应用程序内部传递大型数据结构等场景中。一些常用的序列化格式包括JSON、XML和Protocol Buffers等。

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    //判断参数是否存在。 并进行URL转化
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);
    //利用URL创建NSMutableURLRequest 并这是http请求方法
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    //循环遍历mutableRequest
    //在self.mutableObservedChangedKeyPaths根据keypath取出对应的value值,存到创建的muatableRequest中
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        //mutableObservedChangedKeyPaths是可变观测变更路径的意思
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    //对mutableRequest参数做编码并且重新复制给mutableRequest
    //调用requestBySerializingRequest方法将传入的parameters参数进行编码,并添加到request中
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

	return mutableRequest;
}

在requestWitchMethod方法中,做了三件事情:

  • 创建mutableRequest并设置其请求方法;
  • 把当前类设置的一些属性设置给mutableRequest;(存在疑问)
  • 把需要传递的参数进行编码并且设置到mutableRequest当中;

最后面调用requestBySerializingRequest方法将传入的parameters参数进行编码,并添加到request中,我们来看看编码的方法:

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    //从自己持有的HTTPRequestHeaders中遍历,如果有值的话,就设置给mutableRequest的head
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];
    //把网络请求的参数转换为NSString类型
    NSString *query = nil;
    if (parameters) {
        //如果设置了构建query的块,就尝试调用块生成
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    //序列化 query参数,使用AFQueryStringFromParameters来进行编码
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    //判断该request中是否包含了GET、HEAD 、DELETE(都包含在HTTPMethodsEncodingParameetersInURI)
    //因为这几个method的quey是拼接到URL后面的,而POST、PUT是吧query拼接到http body中的
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        if (!query) {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        //stringEncoding:用于序列化参数的字符串编码,默认“NSUTF8StringEncoding”
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}
  • 从当前请求队列中拿到self.HTTPRequestHeaders中拿到设置的参数,赋值要请求的request中;(疑问点)
  • 把网络请求的参数转换为NSString类型,这一步是对参数进行转码;
  • 将请求方法拼接到url中;GET、HEAD、DELETE这几个method的quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的;

以下这几个方法就是我们在转码的时候所使用的方法,如何将请求参数进行转码:也就是通过递归调用并解析,直到解析的除了array,dic,set以外的元素,然后将最终得到的参数返回;

其中的转码的函数AFQueryStringFromParameters(parameters); 的实现如下:

FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value);

//下方的这三个函数依次调用,实现了对参数的转码
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    //把参数给AFQueryStringPairsFromDictioniary,拿到AF的一个类型的数据就一个key,value对象,在UELEncodedStringValue拼接key value,一个加到数组里
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }
    //拆分数组返回参数字符串,将数组传化为字符串
    return [mutablePairs componentsJoinedByString:@"&"];
}

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

//递归调用并解析key - value 直到解析的除了array,dic,set以外的元素,然后把最终得到的参数返回:
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    //根据需要排列的对象的description来进行升序排序,并且selector使用的是compare:
    //因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数
    //即@[@"foo", @"bar", @"bae"] ---> @[@"bae", @"bar", @"foo"]
    //sortDescriptor是排序描述符的意思
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
        //对字典键进行排序以确保查询字符串中的顺序一致,这在反序列化潜在的不明确序列(例如字典数组)时非常重要
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            //dic
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                //递归调用,第一次执行时key为nil,后续执行时key就有了实际的值,所以就可以按照刚才排好的序,去添加相应的键值对,且第一次传进来的value是容器形式,次for循环中的函数调用传的value是容器内部的元素形式,走的是最后的else,添加键值对
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    } else if ([value isKindOfClass:[NSArray class]]) {
        //array
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    } else if ([value isKindOfClass:[NSSet class]]) {
        //set
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

我们很清晰地看到这三个函数从上往下一个调用一个,最终实现了请求参数的转码

到这里序列化的部分结束,返回一个NSMutableURLRequest实例;紧接着设置请求头并且通过回调处理序列化失败的情况;

为什么要进行序列化?
在HTTP网络请求是基于字节流的网络传输,序列化是可以将一个对象转化成一段字节编码,以此方便在网络上传输或者做其他存储处理,使用的时候在对其做反序列化;简单的来说就是为了统一,我们可以用自己的方法来保存对象,但是在底层只提供一种保存对象状态的机制。因此我们在存储的时候需要进行序列化,以此来和提供的保持一致,在读取的时候对其进行反序列化,得到我们想要的形式;

2、调用dataTaskWithRequest来生成一个datatask任务

在调用完requestWithMethod: 方法之后,我们接着调用了另一个很重要的方法dataTaskWithRequest 来生成一个datatask任务

在调用的时候,将序列化后的request等参数传入,先看看dataTaskWithRequest方法里边做了什么事情吧:

//创建一个dataTask
- (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 {

    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
    //设置其delegate,包括进度、等
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

可以看到,生成一个datatask,并且调用addDelegateForXXX方法来设置代理,那么,设置代理这一块,和在初始化的时候为当前初始化session中的task设置代理的过程是一样的。(向上查看初始设置代理部分);
最终返回一个dataTask任务,创建完成之后通过回调返回进度以及comple情况,在dataTaskWithHTTPMethod方法中进行处理;这里回到dataTaskWithHTTPMethod方法中,做完上述处理之后,最终将生成的dataTask返回到GET方法当中,这样最终在GET中就拿到了我们可以发送请求的task,最后调用系统方法[dataTask resume];发起网络请求;

当然,在GET方法中有progress,success,failure回调,使用的时候直接根据回调来处理不同情况,不用像原生的方法那样,进行多次判断,确认哪一种情况,再去做处理;
回到我们的初始化方法,在调用AFURLSessionManagerinitWithSessionConfiguration方法初始化一个sessionManager的时候,在进行懒加载的时候,初始化session:

- (NSURLSession *)session {
    
    @synchronized (self) {
        if (!_session) {
            _session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
        }
    }
    return _session;
}

由此看到,在初始化session的时候,将AFURLSessionManager作为了所有task的delegate。因此当我们进行网络请求的时候,这些代理就会被执行。AFURLSessionManager遵守的代理包括:NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying。

接下来我们单独来看看AFURLSessionManger和AFHTTPSessionManager这两个类,在前面我们提到了AFHTTPSessionManager是AFURLSessionManger的子类,对于AFURLSessionManger我刚开始以为它是NSURLSession的子类,后来发现它不是,NSURLSession是这个类中的一个属性。

AFURLSessionManger

AFURLSessionManger 负责生成对应的NSURLSession的实例,管理AFNetworkReachabilityManager和AFSecurityPolicy,以此一来查看网络的连接情况,二来保证请求的安全,同时初始化生成一个AFJSONResponseSerializer的实例来序列化HTTP的响应结果;
AFURLSessionManger的属性和接口方法:
请添加图片描述
其中的核心方法包括:初始化方法、针对不同任务的request方法;
初始化方法的实现在上文已经详细讲过,最终生成一个AFURLSessionManager的实例对象;上文在以GET请求为例的时候发送请求过程的时候介绍了dataTaskWithRequest,这里主要来看一下uploadTaskWithRequest、downloadTaskWithRequest和downloadTaskWithResumeData这三个方法:

//使用本地文件的指定请求创建一个NSURLSessionUploadTask
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                         fromFile:(NSURL *)fileURL
                                         progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    //根据request以及要上传的本地文件的URL创建一个uploadTask
    NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
    //为uploadTask设置代理
    if (uploadTask) {
        [self addDelegateForUploadTask:uploadTask
                              progress:uploadProgressBlock
                     completionHandler:completionHandler];
    }

    return uploadTask;
}

//使用指定的HTTP body请求创建“NSURLSessionUploadTask”
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                         fromData:(NSData *)bodyData
                                         progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
    
    [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];

    return uploadTask;
}

//使用指定的流式处理请求创建NSURLSessionUploadTask
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
                                                 progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                        completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithStreamedRequest:request];

    [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];

    return uploadTask;
}

ploadTaskWithRequest根据不同的数据创建一个NSURLSessionUploadTask任务,最终都会走到addDelegateForUploadTask为对应的uploadTask设置代理。之前提到过在这就不说了。

然后接着往下看:
downloadTaskWithRequest:使用指定的请求request来创建对应的NSURLSessionDownloadTask,并且为所创建的task设置代理,最终将其返回;

//使用重用数据的下载任务,使用已经下载的部分数据resumeData创建一个下载任务,继续进行下载
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
                                                progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                             destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                       completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithResumeData:resumeData];

    [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];

    return downloadTask;
}

downloadTaskWithResumeData:使用已经下载的部分数据 resumeData 创建一个下载任务,继续进行下载。并且为所创建的task设置代理,最终将其返回;

还有下面两个方法,分别是来获取上传和下载的进度:

- (NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task {
    return [[self delegateForTask:task] uploadProgress];
}

- (NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task {
    return [[self delegateForTask:task] downloadProgress];
}

代理实现:
在这里插入图片描述
AFURLSessionManager遵守的代理都有NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying对应实现了这些代理中的一些方法

遵守的这些代理,最终AFURLSessionManager对这些代理做了一些公共的处理,最终转发到自定义的代理AFURLSessioniManagerTaskDelegate的3个代理方法中,用来负责把每个task对应的数据回调回去;

AFURLSessionManager中实现了NSURLSessionDownloadDelegate的三个代理方法,分别如下:

//下载完成的时候调用
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    //转发代理
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (self.downloadTaskDidFinishDownloading) {
        //调用自定义的block,拿到文件存储的地址
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            //判断从临时的下载路径移动至我们需要的路径是否成功
            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
                //不成功,发送会话下载任务未能移动文件通知
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            } else {
                //成功,发送会话下载任务成功移动文件通知
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification object:downloadTask userInfo:nil];
            }

            return;
        }
    }
    //进行代理转发
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

//周期性地通知下载进度调用
//bytesWritten 自上次调用方法后,接收到的数据字节数
//totalBytesWritten 目前已经接收到的数据字节数
//totalBytesExpectedToWrite 期望收到的文件总字节数 是由content-Length header提供,如果没有提供默认是NSURLSessionTransferSizeUnknown
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
    }

    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

//当下载被取消或失败后重新恢复下载后调用 告诉代理下载任务重新开始下载了 didResumeAtOffset:在偏移中恢复,即从哪里开始恢复下载 expectedTotalBytes:预期下载的总字节数
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes];
    }

    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}

这三个代理方法分别用来对下载任务进行处理,依次是下载完成时的调用,周期性通知下载进度的调用,当下载被取消或者失败后重新恢复下载时的调用;这三个代理方法最终都会进行代理转发,到AFURLSessionManagerTaskDelegate中,AF中的deleagate是需要对应每个task去私有化处理的。对应看看AFURLSessionManagerTaskDelegate中的这三个代理方法都做了什么:

#pragma mark - NSURLSessionDownloadDelegate

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    //更新当前下载进度
    self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
    self.downloadProgress.completedUnitCount = totalBytesWritten;
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
    //更新当前下载进度
    self.downloadProgress.totalUnitCount = expectedTotalBytes;
    self.downloadProgress.completedUnitCount = fileOffset;
}

//下载完成时的调用
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    self.downloadFileURL = nil;

    if (self.downloadTaskDidFinishDownloading) {
        //得到下载的文件路径
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            NSError *fileManagerError = nil;
            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
                //把下载路径移动到我们自定义的下载路径
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            } else {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification object:downloadTask userInfo:nil];
            }
        }
    }
}

在下载完成时的调用代理方法中,AFURLSessionManager和AFURLSessionManagerTaskDelegate中都进行了文件路径的移动,而NSURlSession代理的下载路径是所有request公用的下载路径,设置之后所有的request都会下载到之前的那个路径。而AFURLSessionManagerTaskDelegate中对应到每一个task中,每一个task可以设置自己的下载路径;

总结: 这些代理方法在AFURLSessionManager中实现的时候都是对session做一个公共的处理,每一个不同的task进行特定的处理时,需要将代理转发到AFURLSessionManagerTaskDelegate中,在AFURLSessionManagerTaskDelegate的代理中实现;

2.AFURLSessionManagerTaskDelegate的作用
主要用来管理进度;并且在task结束的时候回调使用;在上述提到AFURLSessionManagerTaskDelegate中关于NSURLSessionDownloadDelegate的代理方法实现,对应到每一个task中,每一个task可以设置自己的下载路径;相应的也实现了NSURLSessionDataDelegate,NSURLSessionTaskDelegate;这些代理都是用来对当前特定的task做处理;

监听的处理方法,observeValueForKeyPath,这个方法是用来当datatask状态发生改变时的监控处理逻辑,调用block回调,用户拿到进度;

3. _AFURLSessionTaskSwizzling的作用
用来修改NSURLSession的resume和suspend方法,使用af_resume和af_suspend这两种方法来替换原有的resume和suspend方法;这样做是为了在方法resume或者suspend被调用时发出通知;
load方法中采用OC中Runtime的method swizzling来进行实现, AFNetworkingTaskDidResumeNotification来通知当前的任务状态为resume,那么就需要调用taskDidResume:函数,而想要调用taskDidResume:函数就得调用af_resume函数。同理,AFNetworkingTaskDidSuspendNotification来通知当前的任务状态为suspend,那么就需要调用taskDidSuspend:函数,而想要调用taskDidSuspend:函数就得调用af_suspend函数。

Runtime的method swizzling是什么?
在 Objective-C 中,Method Swizzling 是动态修改方法实现的技术,它允许你在运行时改变一个类的实现,包括其继承的类。这个技术通常用于在不改变原有代码的情况下,给现有方法添加额外的功能或者替换原有的实现。
具体来说,Method Swizzling 可以通过交换方法的实现来达到目的。每个 Objective-C 方法都对应着一个 SEL 对象和一个 IMP 指针,SEL 对象表示方法名,而 IMP 指针则指向方法的实现。通过交换两个方法的 IMP 指针,就可以实现方法的调换。
例如,假设我们想要给一个已有的函数 foo 添加一些额外的逻辑。我们可以创建一个新的方法 bar,然后将 foo 和 bar 的实现进行交换。这样,当代码中调用 foo 时,实际上会执行 bar 的实现,从而达到我们想要的目的。同时,我们也可以将原本的 foo 实现保存下来,在 bar 的实现中调用它,从而保留原有的功能。
需要注意的是,Method Swizzling 动态地改变了类的实现,因此使用不当可能会导致意料之外的后果。为了避免出现问题,我们需要谨慎地使用 Method Swizzling,并且在使用之前需要仔细地测试和验证代码。

这让我想起来上次学习KVO的时候,提到了isa-swizzling方法,这个方法简单来说就是给一个类添加kvo之后,这个类就会变一个中间类,中间类是原类的子类,并动态修改当前对象的isa指向中间类,并且将class方法重写,返回原类的Class。

+ (void)load {
    /**
     WARNING: Trouble Ahead
     https://github.com/AFNetworking/AFNetworking/pull/2702
     */
    
    //判断当前的iOS版本是否含有NSURLSessionTask类
    if (NSClassFromString(@"NSURLSessionTask")) {
        /**
         iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
         Many Unit Tests have been built to validate as much of this behavior has possible.
         Here is what we know:
            - NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
            - Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
            - On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
            - On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
            - On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
            - On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
            - Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
        
         Some Assumptions:
            - No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
            - No background task classes override `resume` or `suspend`
         
         The current solution:
            1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
            2) Grab a pointer to the original implementation of `af_resume`
            3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
            4) Grab the super class of the current class.
            5) Grab a pointer for the current class to the current implementation of `resume`.
            6) Grab a pointer for the super class to the current implementation of `resume`.
            7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
            8) Set the current class to the super class, and repeat steps 3-8
         */
        
        //创建一个session的配置对象--利用它创建一个session,进一步创建task
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
        //originalAFResumeIMP:af_resume方法的实现 (返回给指定类的实例方法)method_getImplementation:返回方法的实现。 class_getInstanceMethod
        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
        Class currentClass = [localDataTask class];
        //循环去curentcalss中查看是否有resume方法
        while (class_getInstanceMethod(currentClass, @selector(resume))) {
            Class superClass = [currentClass superclass];
            IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
            IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
            //判断:af_resume和当前类的父类的resume的实现不相等且原来的af_resume和当前类的resume实现不相等
            if (classResumeIMP != superclassResumeIMP &&
                originalAFResumeIMP != classResumeIMP) {
                //调用swizzleResumeAndSuspendMethodForClass调剂该类的resume和suspenf方法
                [self swizzleResumeAndSuspendMethodForClass:currentClass];
            }
            currentClass = [currentClass superclass];
        }
        
        [localDataTask cancel];
        [session finishTasksAndInvalidate];
    }
}

//调剂theClass的resume和suspenf方法
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

    if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
        af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
    }

    if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
        af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
    }
}

load方法会被多次调用,但是每个类只会被调用一次。
调用load方法的顺序与类的继承关系无关,而是根据类的文件加载顺序决定的。

- (void)af_resume {
    //断言是否状态恢复
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    //获取取消状态
    NSURLSessionTaskState state = [self state];
    [self af_resume];
    //如果状态时取消状态,通过注册的AFNSURLSessionTaskDidResumeNotification值,通知给self(接收通知的对象)
    if (state != NSURLSessionTaskStateRunning) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
    }
}

- (void)af_suspend {
    
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    
    NSURLSessionTaskState state = [self state];
    [self af_suspend];

    if (state != NSURLSessionTaskStateSuspended) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
    }
}

AFHTTPSessionManager

AFHTTPSessionManager的功能:
AFHTTPSessionManager本身是对网络请求做了一些简单的封装,请求的整个逻辑是分发给AFURLSessionManager或者其他类去做的;其内部管理自己的两种序列化工具,用来对请求和响应的数据做序列化;同时依赖于父类提供的保证安全,监控网络状态,实现发出HTTP请求的核心功能;
请添加图片描述
这个类的基本知识其实在前面已经差不多都看过了,就不多说了。

我们在看源码文件的时候可以看到,有一个文件夹没有打开过,好像整个过程都在看下面的文件夹:
在这里插入图片描述

我们就来浅看一下UIKit+AFNetworking
在这里插入图片描述

UIKit+AFNetworking

这块知识网上资料也比较少,我们不做特别细的分析

看了上图的文件和类,是不是感觉有点似曾相识?好像和SDWebImage有些相似,我们看看它的过程和SDWebImage也相似吗?

使用时的调用:

[self.imageView setImageWithURL:@""];

从这里点进去:

- (void)setImageWithURL:(NSURL *)url {
    [self setImageWithURL:url placeholderImage:nil];
}

- (void)setImageWithURL:(NSURL *)url
       placeholderImage:(UIImage *)placeholderImage
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];

    [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}

最终调用了[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];这个方法

- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
              placeholderImage:(UIImage *)placeholderImage
                       success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                       failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
    //容错处理
    //这里如果request的URL不存在的话,那就无法请求了,
    //这里就将当前UIImageView的image设置为palceHolder图像,并取消该图像下载任务
    if ([urlRequest URL] == nil) {
        self.image = placeholderImage;
        if (failure) {
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
            failure(urlRequest, nil, error);
        }
        return;
    }
    //下面就看一下根据URL判断任务是否存在,如果存在就return,接着就是取消图像下载任务
    if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
        return;
    }
    
    [self cancelImageDownloadTask];
    //获取缓存图像并做决策
    //首先是获取下载器,然后获取下载器的缓存,最后根据请求request获取UIImage
    AFImageDownloader *downloader = [[self class] sharedImageDownloader];
    id <AFImageRequestCache> imageCache = downloader.imageCache;

    //Use the image from the image cache if it exists
    UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
    //如果缓存图像存在,如果success的block存在就回调出去,否则就赋值给image。
    //最后还是调用clearActiveDownloadInformation,清除下载信息
    if (cachedImage) {
        if (success) {
            success(urlRequest, nil, cachedImage);
        } else {
            self.image = cachedImage;
        }
        [self clearActiveDownloadInformation];
    } else {
        //缓存图像不存在
        if (placeholderImage) {
            //暂时将图像设置为占位符
            self.image = placeholderImage;
        }

        __weak __typeof(self)weakSelf = self;
        NSUUID *downloadID = [NSUUID UUID];
        AFImageDownloadReceipt *receipt;
        //用下载器进行下载,不管成功还是失败都进行相应的回调,并清除下载信息clearActiveDownloadInformation。
        //并在成功的时候设置图像替换掉下载图strongSelf.image = responseObject
        receipt = [downloader
                   downloadImageForURLRequest:urlRequest
                   withReceiptID:downloadID
                   success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
                       __strong __typeof(weakSelf)strongSelf = weakSelf;
                       if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                           if (success) {
                               success(request, response, responseObject);
                           } else if(responseObject) {
                               strongSelf.image = responseObject;
                           }
                           [strongSelf clearActiveDownloadInformation];
                       }

                   }
                   failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
                       __strong __typeof(weakSelf)strongSelf = weakSelf;
                        if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                            if (failure) {
                                failure(request, response, error);
                            }
                            [strongSelf clearActiveDownloadInformation];
                        }
                   }];
        //根据下载器返回的凭据,更新内存中的有效凭据self.af_activeImageDownloadReceipt
        self.af_activeImageDownloadReceipt = receipt;
    }
}

下面看一下取消任务的方法:

- (void)cancelImageDownloadTask {
    if (self.af_activeImageDownloadReceipt != nil) {
        [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
        [self clearActiveDownloadInformation];
     }
}
  • 首先就是判断下载任务的凭据是否存在,如果不存在不用管,说明没有这个任务,这里只处理有这个任务的情况。
  • 调用下载器的cancelTaskForImageDownloadReceipt:方法,带入凭据参数,取消下载任务。
  • 初始化凭据参数,置为nil,实现过程如下
- (void)clearActiveDownloadInformation {
    self.af_activeImageDownloadReceipt = nil;
}

从上面不难看出AFNetworking的图片加载一样是可以先判断缓存中是否下载过然后再下载,基本原理也和SDWebImage相似,就不再往下细看了,AFNetworking的图片加载没有占位图功能,所以建议正常还是用SDWebImage为益。

关于AFNetworking的讲解基本就完了,我们后面来看看几个可能忽略掉的或者不懂得问题:

AFNetworking4.0较AFNetworking3.0有什么变动?

看看官方对每个版本的解释(简化版):

  • AFNetworking 1.0 是基于NSURLConnection开发出来的。而NSURLConnection是苹果早些年提供的网络通讯的API接口。目前该接口已经废弃。
  • AFNetworking 2.0 是基于部分NSURLConnection接口 和部分NSURLSession接口开发的。简单来说,2.0是介于NSURLConnection和NSURLSession的过渡阶段。其中NSURLSession接口是苹果提供且目前主推的网络通讯API接口。
  • AFNetworking 3.0 完全基于NSURLSession开发,此版本中的NSURLConnection全部弃用。这样不仅降低了代码维护工作,还更好地支持了NSURLSession提供的额外功能。
  • AFNetworking 4.0 是2020年发布的。主要是配合苹果公司弃用UIWebView控件的升级,同时也移除之前弃用的API接口。不过要特别说明,这个版本支持的iOS版是9.0(之前是7.0),macOS 10.10。

再来看看chatGPT的回答:

  • AFNetworking 4.0增加了对NSURLSession的支持,因此可以在iOS7及以上版本中使用NSURLSession。NSURLSession提供更好的性能,可控制性和可定制性。
  • AFNetworking 4.0将之前的AFHTTPRequestOperationManager替换成了AFHTTPSessionManager,以支持NSURLSession。同时也提供了AFURLSessionManager类,用于处理自定义的NSURLSessionDelegate行为。
  • AFNetworking 4.0中移除了UIWebView相关的代码,以适应苹果最新的App Store审核规则。
  • AFNetworking 4.0默认情况下使用JSON序列化器(AFJSONRequestSerializer),而不是之前的XML序列化器(AFXMLRequestSerializer)。
  • AFNetworking 4.0引入了一些新的功能,比如多部分表单数据上传,基础认证,OAuth1和OAuth2认证等。

总的来说,AFNetworking 4.0相对于3.0版本来说,在性能、可定制性和功能方面都得到了改进和扩展。

我自己对使用两者的感受主要就是在请求的时候,新版本的请求头直接加在方法参数里,老版本则要单独添加请求头,其他的没有什么太能用得到的地方。

用AFNetworking第三方库去进行网络请求比原生的网络请求好在哪里?

AFNetworking是一个开源的iOS网络请求库,相较于原生的网络请求框架,它具有以下几个优点:

  • AFNetworking提供了更加简单易用的API接口,支持链式编程,可以让代码更加清晰易读。
  • AFNetworking支持各种常用的网络传输协议,例如HTTP、HTTPS、WebSocket等,并提供了丰富的功能和扩展,比如支持SSL Pinning、请求重试、上传和下载进度回调等。
  • AFNetworking对网络请求的封装使得开发者可以更加方便地处理网络请求的状态和响应,例如自动解析JSON数据并返回对象等。
  • AFNetworking提供了高效的缓存策略,能够减少网络请求次数,提升应用的性能和用户体验。

总体来说,AFNetworking提供了一种更加简洁、易用、高效的方式来进行网络请求,适用于大部分iOS应用开发场景。

AFSecurityPolicy怎么保证请求的安全性

AFSecurityPolicy是一个用于网络请求的安全策略管理工具,它可以通过一系列的设置和验证规则来保证请求的安全性。以下是AFSecurityPolicy保证请求安全性的几种常见方法:

  • SSL Pinning:在客户端内置服务器的公钥或证书以确保连接的身份验证。这样可以防止中间人攻击。
  • TLS版本限制:只对TLS 1.2及以上版本进行支持,以避免使用已知存在安全问题的较旧协议。
  • 安全密码学套件:限制加密协议为足够安全的密码学套件,如AES、RSA等。
  • 服务器证书验证:验证服务器证书是否由受信任的权威机构颁发,并且证书中包含正确的主题名称。如果证书不符合要求,则会导致请求失败。
  • 验证域名:确保请求的目标URL与服务器证书中的主题名称匹配。
  • 防止重定向攻击:禁止HTTP重定向到可能造成安全风险的URL。

如果出现不安全的域名或者可能造成安全风险的URL,AFSecurityPolicy将会拒绝连接并返回一个错误。这是因为AFSecurityPolicy默认情况下会进行服务器证书验证和域名验证,确保请求发送到的是一个受信任的、安全的服务器,并且请求的目标URL与服务器证书中的主题名称匹配。

AFNetworkReachabilityManager是干什么的

AFNetworkReachabilityManager是一个用于检测设备网络连接状态的工具,它可以监控设备当前的网络状态,并在网络状态变化时提供相应的通知。通常情况下,我们使用AFNetworkReachabilityManager来判断设备是否有网络连接,以便在没有网络或者网络状态不佳时及时给用户反馈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/539546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux服务器--基线检查

Linux服务器--基线检查 一、前言二、口令策略2.1 检查项—密码策略2.2 检查项—密码生存期2.3 检查项—检查密码生存期是否作用于每个用户 三、账号管理3.1 检查项—禁止存在UID相同的用户3.1 检查项—禁止存在空密码的帐户 四、认证授权4.1 检查项—登陆失败处理 五、文件权限…

Pytroch 模型权重初始化

目录 1 概念 2 权值初始化方法 2.1 常数初始化 2.2 均匀分布初始化 2.3 正态分布初始化 2.4 Xavier 均匀分布 2.5 Xavier 正态分布 2.6 kaiming 均匀分布 2.7 kaiming 正态分布 2.8 单位矩阵初始化 2.9 正交初始化 2.10 稀疏初始化 2.11 狄拉克δ函数初始化 3 py…

STC15通过内部BandGap电压值测量ADC外部输入电压

STC15通过内部BandGap参考电压值测量ADC通道外部输入电压 📜内部 BandGap参考电压值获取方式: 🎬通过VOFA图形化显示ADC值 🔧vofa+工具下载地址:https://www.vofa.plus/🌿验证对象:IAP15F2K61S2🌿时钟频率:11.0592MHz🌿波特率:115200🔖在通过STC-ISP烧录程序…

XSS攻击以及java应对措施

文章目录 一. XSS攻击介绍1. 前端安全2. xss攻击简介3. xss的攻击方式 二. java应对xss攻击的解决方案1. 强制修改html敏感标签内容2. 利用过滤器过滤非法html标签 一. XSS攻击介绍 1. 前端安全 随着互联网的高速发展&#xff0c;信息安全问题已经成为企业最为关注的焦点之一…

大脑的故事

婴⼉的神经元是相互独⽴的、未连接的。在⼈⽣的头两年&#xff0c; 随着⼤脑细胞接收感觉信息&#xff0c;它们异常迅速地连接起来。 每⼀秒就有多达 200万个新连接&#xff08;突触&#xff09;在婴⼉的⼤脑⾥形成。两岁时&#xff0c;⼩孩⼦拥有超过 100万亿个突触&#xff…

版本发布 | 科东软件Intewell-Win V2.1.0 release版本正式发布

Intewell是由科东软件自主研发的工业嵌入式实时操作系统&#xff0c;具有高实时&#xff0c;确定性、高安全、高可靠、虚拟化等特点。Intewell系统源自于1990年诞生的“道”操作系统&#xff0c;至今已有30多年历史&#xff0c;已在多种严苛环境下运行检验&#xff0c;广泛商用…

springboot+java超市收银管理系统idea

考虑到实际生活中在超市 POS 收银管理方面的需要以及对该系统认真的分析&#xff0c;将系统权限按管理员和员工这两类涉及用户划分。 Spring Boot 是 Spring 家族中的一个全新的框架&#xff0c;它用来简化Spring应用程序的创建和开发过程。也可以说 Spring Boot 能简化我们之…

mmFormer:用于脑肿瘤分割的不完全多模态学习的多模态医学Transformer

文章目录 mmFormer: Multimodal Medical Transformer for Incomplete Multimodal Learning of Brain Tumor Segmentation摘要本文方法Hybrid Modality-Specific EncoderModality-Correlated EncoderConvolutional DecoderAuxiliary Regularizer 实验结果 mmFormer: Multimodal …

Kali-linux使用假冒令牌

使用假冒令牌可以假冒一个网络中的另一个用户进行各种操作&#xff0c;如提升用户权限、创建用户和组等。令牌包括登录会话的安全信息&#xff0c;如用户身份识别、用户组和用户权限。当一个用户登录Windows系统时&#xff0c;它被给定一个访问令牌作为它认证会话的一部分。例如…

师从英国两院院士|生物医学科研人员获CSC资助赴剑桥大学访学

L老师拟申报CSC公派访问学者项目&#xff0c;希望到欧洲TOP学校&#xff0c;师从知名教授&#xff0c;在自己的研究基础上取得进一步的进展和突破。最终我们获得世界名校剑桥大学的邀请函&#xff0c;导师是英国皇家科学学会及英国医学科学院两院院士&#xff0c;凭借这份硬气十…

【SAP Abap】X-DOC:SE18/19 - SAP第四代增强概念理解

【SAP Abap】X-DOC&#xff1a;SE18/19 - SAP第四代增强概念理解 1、Tcode2、概念3、增强选项类型4、增强实现类型5、增强操作方式6、增强选项与增强实现关系7、增强实施建议 1、Tcode SE18&#xff1a;Business Add-Ins: Definitions&#xff08;增强点定义/查看&#xff09;…

云平台电子班牌系统源码

越来越多的教育单位加入了数字化校园和智慧校园建设行列。在不断探究、建设和实施的过程中&#xff0c;建立强大的、高扩展性的智慧教育管理平台被众多学校和教育单位所认同。智慧班牌是电子班牌信息发布系统的数据呈现端&#xff0c;也是智慧平台数据的采集工具之一。通过智慧…

EtherCAT运动控制器在数控加工手轮随动中的应用之C++

本文以正运动技术具备专用手轮接口的运动控制器ZMC408CE为例&#xff0c;介绍手轮、手轮的作用及原理、控制器手轮接口接线以及手轮程序配置。 上节讲解了使用正运动basic语言进行手轮应用配置&#xff0c;本节主要讲解C调用API函数库接口实现手轮配置。 01 手轮作用及原理 …

第一个gin程序

一、下载并安装gin go get -u github.com/gin-gonic/gin二、第一个gin程序 package mainimport "github.com/gin-gonic/gin"func sayHello(c *gin.Context) {// 返回给客户端一个JSON格式的数据&#xff0c;其中HTTP状态码为200&#xff0c;表示处理成功c.JSON(200…

成功的产品经理,应该了解一定的开发知识

产品经理在互联网产品开发中扮演着协调和推动的重要角色。然而&#xff0c;由于产品经理没有直接的实际权力&#xff0c;与开发团队合作时可能会遇到各种挑战。当你给开发人员分配任务时&#xff0c;他们可能会找各种借口推脱工作。 在项目开发中&#xff0c;所有成员必须共同…

【C++】详解STL中的list及其与vector的比较

目录 一、list的介绍及其使用1、list的介绍2、list的使用2.1 list的构造2.2 list iterator的使用3、list的元素访问接口4、list的调节器6、list的迭代器失效 二、list的模拟实现及反向迭代器1、模拟实现list2、list的反向迭代器 三、list和vector的比较 一、list的介绍及其使用…

142. 环形链表 II Python

文章目录 一、题目描述示例 1示例 2示例 3 二、代码三、解题思路 一、题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#x…

bug记录:遇到的tinycudann编译的N种错误

1. 编译成功&#xff0c;但是import tinycudann报错找不到DLL 编译成功&#xff0c;但是import tinycudann的时候&#xff0c;报错&#xff1a; 开始打断点&#xff0c;搜索电脑文件&#xff0c;发现_75_c.py应该是存在的&#xff0c;但就是读不到。 发现其所在的文件夹名称…

自定义组件间通信-2

目录 一、 父子组件间通信的3种方式 二、属性绑定&#xff0c;父-> 子 三、事件绑定&#xff0c;子-> 父 四、获取组件实例 一、 父子组件间通信的3种方式 属性绑定&#xff1a;用于父组件向子组件的指定属性设置设置数据&#xff0c;仅能设置JSON兼容的数据事件绑定&…

三分钟挖掘快速软件开发框架提高办公效率的秘诀

在科技日新月异的当今社会&#xff0c;学会利用快速软件开发框架&#xff0c;可以给企业带来更大的便利和市场价值。因为它拥有可视化设计、灵活简便、易操作、易上手等优势特点&#xff0c;在助推企业实现数字化转型的过程中有着举足轻重的作用。那么&#xff0c;快速软件开发…