承接上一篇
21.网络服务类型NSURLRequestNetworkServiceType
示例代码:
typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType){ NSURLNetworkServiceTypeDefault = 0, // Standard internet traffic NSURLNetworkServiceTypeVoIP = 1, // Voice over IP control traffic NSURLNetworkServiceTypeVideo = 2, // Video traffic NSURLNetworkServiceTypeBackground = 3, // Background traffic NSURLNetworkServiceTypeVoice = 4 // Voice data};
可以通过这个值来指定当前的网络类型,系统会跟据制定的网络类型对很多方面进行优化,这个就设计到很细微的编程技巧了,可作为一个优化的点备用。
22.Authorization字段
在请求头中可以添加Authorization字段。
Authorization: Basic YWRtaW46YWRtaW4= 其中Basic表示基础认证,当然还有其他认证,如果感兴趣,可以看看本文开始提出的那本书。后边的YWRtaW46YWRtaW4= 是根据username:password 拼接后然后在经过Base64编码后的结果。
如果header中有 Authorization这个字段,那么服务器会验证用户名和密码,如果不正确的话会返回401错误。
23.使用流写数据
下边的代码可以说是使用流写数据的经典案例。可直接拿来使用。
示例代码:
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL completionHandler:(void (^)(NSError *error))handler{ NSParameterAssert(request.HTTPBodyStream); NSParameterAssert([fileURL isFileURL]); // 加上上边的两个判断,下边的这些代码就是把文件写到另一个地方的典型使用方法了 NSInputStream *inputStream = request.HTTPBodyStream; NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO]; __block NSError *error = nil; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inputStream open]; [outputStream open]; // 读取数据 while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) { uint8_t buffer[1024]; NSInteger bytesRead = [inputStream read:buffer maxLength:1024]; if (inputStream.streamError || bytesRead < 0) { error = inputStream.streamError; break; } NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead]; if (outputStream.streamError || bytesWritten < 0) { error = outputStream.streamError; break; } if (bytesRead == 0 && bytesWritten == 0) { break; } } [outputStream close]; [inputStream close]; if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ handler(error); }); } }); NSMutableURLRequest *mutableRequest = [request mutableCopy]; mutableRequest.HTTPBodyStream = nil; return mutableRequest;}
24.NSIndexSet
定义:NSIndexSet是一个有序的,唯一的,无符号整数的集合。
我们先看个例子:
NSMutableIndexSet *indexSetM = [NSMutableIndexSet indexSet]; [indexSetM addIndex:19]; [indexSetM addIndex:4]; [indexSetM addIndex:6]; [indexSetM addIndex:8]; [indexSetM addIndexesInRange:NSMakeRange(20, 10)]; [indexSetM enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"%lu",idx); }];
打印结果如下:
2016-08-10 11:39:00.826 xxxx[3765:100078] 42016-08-10 11:39:00.827 xxxx[3765:100078] 62016-08-10 11:39:00.827 xxxx[3765:100078] 82016-08-10 11:39:00.827 xxxx[3765:100078] 192016-08-10 11:39:00.827 xxxx[3765:100078] 202016-08-10 11:39:00.828 xxxx[3765:100078] 212016-08-10 11:39:00.828 xxxx[3765:100078] 222016-08-10 11:39:00.828 xxxx[3765:100078] 232016-08-10 11:39:00.828 xxxx[3765:100078] 242016-08-10 11:39:00.828 xxxx[3765:100078] 252016-08-10 11:39:00.828 xxxx[3765:100078] 262016-08-10 11:39:00.828 xxxx[3765:100078] 272016-08-10 11:39:00.828 xxxx[3765:100078] 282016-08-10 11:39:00.829 xxxx[3765:100078] 29
这充分说明了一下几点
- 它是一个无符号整数的集合
- 用addIndex方法可以添加单个整数值,使用addIndexesInRange可以添加一个范围,比如NSMakeRange(20, 10) 取20包括20后边十个整数
- 唯一性,集合内的整数不会重复出现
- 具体用法就不再次做详细的解释了,有兴趣的朋友可以看看这篇文章:
25.NSUnderlyingErrorKey优先错误
当出现可能会有两个错误的情况下,可以考虑使用NSUnderlyingErrorKey处理这种情况。
示例代码:
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) { if (!error) { return underlyingError; } if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) { return error; } NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy]; mutableUserInfo[NSUnderlyingErrorKey] = underlyingError; return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];}
26.NSJSONReadingOptions
这个选项可以设置json的读取选项,我们点进去可以看到:
typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) { NSJSONReadingMutableContainers = (1UL << 0), NSJSONReadingMutableLeaves = (1UL << 1), NSJSONReadingAllowFragments = (1UL << 2)} NS_ENUM_AVAILABLE(10_7, 5_0);
- NSJSONReadingMutableContainers 这个解析json成功后返回一个容器
- NSJSONReadingMutableLeaves 返回中的json对象中字符串为NSMutableString
- NSJSONReadingAllowFragments 允许JSON字符串最外层既不是NSArray也不是NSDictionary,但必须是有效的JSON Fragment。例如使用这个选项可以解析 @“123” 这样的字符串
我们举个例子说明一下NSJSONReadingMutableContainers:
NSString *str = @"{\"name\":\"zhangsan\"}";NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];// 应用崩溃,不选用NSJSONReadingOptions,则返回的对象是不可变的,NSDictionary[dict setObject:@"male" forKey:@"sex"];NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];// 没问题,使用NSJSONReadingMutableContainers,则返回的对象是可变的,NSMutableDictionary[dict setObject:@"male" forKey:@"sex"];NSLog(@"%@", dict);
如果服务器返回的json的最外层并不是以NSArray 或者 NSDictionary ,而是一个有效的json fragment ,比如 就返回了一个@"abc"。 那么我们使用NSJSONReadingAllowFragments这个选项也能够解析出来。
27.图片解压杂谈
AFJSONResponseSerializer使用系统内置的NSJSONSerialization解析json,NSJSON只支持解析UTF8编码的数据(还有UTF-16LE之类的,都不常用),所以要先把返回的数据转成UTF8格式。这里会尝试用HTTP返回的编码类型和自己设置的stringEncoding去把数据解码转成字符串NSString,再把NSString用UTF8编码转成NSData,再用NSJSONSerialization解析成对象返回。
上述过程是NSData->NSString->NSData->NSObject,这里有个问题,如果你能确定服务端返回的是UTF8编码的json数据,那NSData->NSString->NSData这两步就是无意义的,而且这两步进行了两次编解码,很浪费性能,所以如果确定服务端返回utf8编码数据,就建议自己再写个JSONResponseSerializer,跳过这两个步骤。
此外AFJSONResponseSerializer专门写了个方法去除NSNull,直接把对象里值是NSNull的键去掉,还蛮贴心,若不去掉,上层很容易忽略了这个数据类型,判断了数据是否nil没判断是否NSNull,进行了错误的调用导致core。
图片解压
当我们调用UIImage的方法imageWithData:方法把数据转成UIImage对象后,其实这时UIImage对象还没准备好需要渲染到屏幕的数据,现在的网络图像PNG和JPG都是压缩格式,需要把它们解压转成bitmap后才能渲染到屏幕上,如果不做任何处理,当你把UIImage赋给UIImageView,在渲染之前底层会判断到UIImage对象未解压,没有bitmap数据,这时会在主线程对图片进行解压操作,再渲染到屏幕上。这个解压操作是比较耗时的,如果任由它在主线程做,可能会导致速度慢UI卡顿的问题。
AFImageResponseSerializer除了把返回数据解析成UIImage外,还会把图像数据解压,这个处理是在子线程(AFNetworking专用的一条线程,详见AFURLConnectionOperation),处理后上层使用返回的UIImage在主线程渲染时就不需要做解压这步操作,主线程减轻了负担,减少了UI卡顿问题。
具体实现上在AFInflatedImageFromResponseWithDataAtScale里,创建一个画布,把UIImage画在画布上,再把这个画布保存成UIImage返回给上层。只有JPG和PNG才会尝试去做解压操作,期间如果解压失败,或者遇到CMKY颜色格式的jpg,或者图像太大(解压后的bitmap太占内存,一个像素3-4字节,搞不好内存就爆掉了),就直接返回未解压的图像。
另外在代码里看到iOS才需要这样手动解压,MacOS上已经有封装好的对象NSBitmapImageRep可以做这个事。
关于图片解压,还有几个问题不清楚:
1.本来以为调用imageWithData方法只是持有了数据,没有做解压相关的事,后来看到调用堆栈发现已经做了一些解压操作,从调用名字看进行了huffman解码,不知还会继续做到解码jpg的哪一步。
UIImage_jpg2.以上图片手动解压方式都是在CPU进行的,如果不进行手动解压,把图片放进layer里,让底层自动做这个事,是会用GPU进行的解压的。不知用GPU解压与用CPU解压速度会差多少,如果GPU速度很快,就算是在主线程做解压,也变得可以接受了,就不需要手动解压这样的优化了,不过目前没找到方法检测GPU解压的速度。
28.获取系统版本
#ifndef NSFoundationVersionNumber_iOS_8_0#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11#else#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0#endif
上边的这个宏的目的是通过NSFoundation的版本来判断当前ios版本,关键是这个宏的调试目标是IOS,来看看系统是怎么定义的:
那么我们就能够联想到,目前我们能够判断系统版本号的方法有几种呢?最少三种:- [UIDevice currentDevice].systemVersion
- 通过比较Foundation框架的版本号,iOS系统升级的同时Foundation框架的版本也会提高
- 通过在某系版本中新出现的方法来判断,UIAlertController 这个类是iOS8之后才出现的 NS_CLASS_AVAILABLE_IOS(8_0),如果当前系统版本没有这个类NSClassFromString(@"UIAlertController" == (null),从而判断当前版本是否大于等于iOS8
29.dispatch_queue_create()
AFNetworking中所有的和创建任务相关的事件都放到了一个单例的队列中,我们平时可能会使用这些方法,但还是可能会忽略一些内容,dispatch_queue_create()这个是队列的方法,第一个参数是队列的identifier,第二个参数则表示这个队列是串行队列还是并行队列。
如果第二个参数为DISPATCH_QUEUE_SERIAL或NULL 则表示队列为串行队列。如果为DISPATCH_QUEUE_CONCURRENT则表示是并行队列。
关于队列的小的知识点,参考了这篇文章30.dispatch_block_t
这个方法还有一个小知识点:dispatch_block_t ,点击去可以看到:
typedef void (^dispatch_block_t)(void);
关于这个Block我们应该注意几点:
- 非ARC情况下,Block被allocated或者copied到堆后,一定要记得释放它,通过[release]或者Block_release()
- 非ARC情况下,Block被allocated或者copied到堆后,一定要记得释放它,通过[release]或者Block_release()
31.NSKeyValueObservingOptions
- NSKeyValueObservingOptionNew 把更改之前的值提供给处理方法
- NSKeyValueObservingOptionOld 把更改之后的值提供给处理方法
- NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值
- NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后
32.task delegate出发问题
task一共有4个delegate,只要设置了一个,就代表四个全部设置,有时候一些delegate不会被触发的原因在于这四种delegate是针对不同的URLSession类型和URLSessionTask类型来进行响应的,也就是说不同的类型只会触发这些delegate中的一部分,而不是触发所有的delegate。
举例说明如下:
触发NSURLSessionDataDelegate
//使用函数dataTask来接收数据 -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data //则NSURLSession部分的代码如下 NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"]; NSURLSessionDataTask* dataTask=[session dataTaskWithURL:url]; [dataTask resume];
触发NSURLSessionDownloadDelegate
//使用函数downloadTask来接受数据 -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location //则NSURLSession部分的代码如下 NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"]; NSURLSessionDownloadTask* dataTask=[session downloadTaskWithURL:url]; [dataTask resume];
这两段代码的主要区别在于NSURLSessionTask的类型的不同,造成了不同的Delegate被触发.
33.@unionOfArrays
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
这么使用之前确实不太知道,如果是我,可能就直接赋值给数组了。那么@unionOfArrays.self又是什么意思呢?
- @distinctUnionOfObjects 清楚重复值
- unionOfObjects 保留重复值
34.相对路径(relative)
假如有一个基础路径NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
我们暂时命名为baseURL.所谓 相对 肯定跟这个baseURL有关系
我们可以通过 NSURL +URLWithString:relativeToUL:
这个方法来获取一个路径,至于怎么使用,我们通过一个例子来说明:
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];[NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz[NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo[NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo[NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
至于 绝对路径 和 相对路径 的定义,使用方法,优缺点,这里就不提了,大家可以自行了解。说一下为什么使用相对路径吧。
在真实开发中,一般都会有一个线上的服务器和一下测试服务器,当然也可能多个。在ios开发中切换开发环境有好几种方法
- 通过target
- 自定义一个字段HTTPURL,用它来控制路径
- 通过相对路径来切换接口
35.dispatch_barrier_async
barrier 这个单词的意思是障碍,拦截的意思,也即是说 dispatch_barrier_async 一定是有拦截事件的作用。
看下边这段代码:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-1"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-2"); }); dispatch_barrier_async(concurrentQueue, ^(){ NSLog(@"dispatch-barrier"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-3"); }); dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-4"); });
打印结果:
2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-12016-08-22 16:43:20.555 xxx[26805:271422] dispatch-22016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-32016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4
这个说明了 dispatch_barrier_async 能够拦截它前边的异步事件,等待两个异步方法都完成之后,调用 dispatch_barrier_async。
我们稍微改动一下:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-1");});dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-2");});dispatch_barrier_sync(concurrentQueue, ^(){ NSLog(@"dispatch-barrier");});dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-3");});dispatch_async(concurrentQueue, ^(){ NSLog(@"dispatch-4");});
打印结果:
2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-12016-08-22 16:43:20.555 xxx[26805:271422] dispatch-22016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-32016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4
36.dispatch_sync() 和 dispatch_async()
大概说下 dispatch_sync() 和 dispatch_async() 这两个方法。
示例代码:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); dispatch_sync(concurrentQueue, ^(){ NSLog(@"2"); [NSThread sleepForTimeInterval:5]; NSLog(@"3"); }); NSLog(@"4");
输出为:
2016-08-25 11:50:51.601 xxxx[1353:102804] 12016-08-25 11:50:51.601 xxxx[1353:102804] 22016-08-25 11:50:56.603 xxxx[1353:102804] 32016-08-25 11:50:56.603 xxxx[1353:102804] 4
再看:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); dispatch_async(concurrentQueue, ^(){ NSLog(@"2"); [NSThread sleepForTimeInterval:5]; NSLog(@"3"); }); NSLog(@"4");
输出为:
2016-08-25 11:52:29.022 xxxx[1392:104246] 12016-08-25 11:52:29.023 xxxx[1392:104246] 42016-08-25 11:52:29.023 xxxx[1392:104284] 22016-08-25 11:52:34.029 xxxx[1392:104284] 3
通过上边的两个例子,我们可以总结出:
- dispatch_sync(),同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行
- dispatch_async ,异步添加进任务队列,它不会做任何等待
37.NSURLCache
我们简单介绍下NSURLCache。NSURLCache 为您的应用的 URL 请求提供了内存中以及磁盘上的综合缓存机制。网络缓存减少了需要向服务器发送请求的次数,同时也提升了离线或在低速网络中使用应用的体验。当一个请求完成下载来自服务器的回应,一个缓存的回应将在本地保存。下一次同一个请求再发起时,本地保存的回应就会马上返回,不需要连接服务器。NSURLCache 会 自动 且 透明 地返回回应。
为了好好利用 NSURLCache,你需要初始化并设置一个共享的 URL 缓存。在 iOS 中这项工作需要在 -application:didFinishLaunchingWithOptions: 完成,而 OS X 中是在 –applicationDidFinishLaunching::
示例代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil]; [NSURLCache setSharedURLCache:URLCache];}
NSURLRequest 有个 cachePolicy 属性,我们平时最常用的有四个属性:
- NSURLRequestUseProtocolCachePolicy: 对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略
- NSURLRequestReloadIgnoringLocalCacheData:数据需要从原始地址加载。不使用现有缓存。
- NSURLRequestReturnCacheDataElseLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据
- NSURLRequestReturnCacheDataDontLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
38.@synchronized()锁
synchronized是一种锁,这种锁不管是在oc中还是java中用的都挺多的,而且这种锁锁得是对象。具体原理,可以看这篇文章后边的 参考 那一部分。
总结一下,锁一般用于多线程环境下对数据的操作中。在 AFNetworking 中我们见到了3种不同的锁,分别是:NSLock
dispatch_semaphore_wait
@synchronized
39.UIWebView+AFNetworking
UIWebView的这个分类是这几个分类中最让我惊讶的一个。让我真正认识到条条大路通罗马到底是什么意思。有时候人的思想确实会被固有的思维所束缚。这里只是用了UIWebView的loadData:(NSData )data MIMEType:(NSString )MIMEType textEncodingName:(NSString )textEncodingName baseURL:(NSURL )baseURL方法
你会发现使用这个分类配合UIWebView,所有的事情都变得很简单。
示例代码
- (void)loadRequest:(NSURLRequest *)request MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(void (^)(NSError *error))failure{ // 检查参数 NSParameterAssert(request); // 如果正处于运行或者暂停装状态,就取消之前的任务task并设置为nil if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) { [self.af_URLSessionTask cancel]; } self.af_URLSessionTask = nil; __weak __typeof(self)weakSelf = self; NSURLSessionDataTask *dataTask; dataTask = [self.sessionManager GET:request.URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { __strong __typeof(weakSelf) strongSelf = weakSelf; // 请求成功后,调用success block if (success) { success((NSHTTPURLResponse *)task.response, responseObject); } // 显示数据 [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[task.currentRequest URL]]; // 调用webViewDidFinishLoad if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { [strongSelf.delegate webViewDidFinishLoad:strongSelf]; } } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) { if (failure) { failure(error); } }]; self.af_URLSessionTask = dataTask; // 设置progress,这个来自于self.sessionManager if (progress != nil) { *progress = [self.sessionManager downloadProgressForTask:dataTask]; } // 开启任务 [self.af_URLSessionTask resume]; // 调用webViewDidStartLoad方法 if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { [self.delegate webViewDidStartLoad:self]; }}
总结
AFNetworking 的源码解读到此就结束了。我曾经说要写一个网络框架,但随着对网络的更进一步的了解。一个好的框架不是随随便便写的。需要对使用者负责。因此我又找了几个目前比较流行的框架,对他们的思想研究研究。实属拿来主义。