终于写完了 AFNetworking 的源码解读。这一过程耗时数天。当我回过头又重头到尾的读了一篇,又有所收获。不由让我想起了当初上学时的种种情景。咱们应该对知识进行反复的记忆和理解。下边是我总结的 AFNetworking 中可以学到的知识点。css
1.枚举(enum)
使用原则:当知足一个有限的并具备统一主题的集合的时候,咱们就考虑使用枚举。这在不少框架中都验证了这个原则。最重要的是可以增长程序的可读性。html
示例代码:java
/** * 网络类型 (须要封装为一个本身的枚举) */ typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { /** * 未知 */ AFNetworkReachabilityStatusUnknown = -1, /** * 无网络 */ AFNetworkReachabilityStatusNotReachable = 0, /** * WWAN 手机自带网络 */ AFNetworkReachabilityStatusReachableViaWWAN = 1, /** * WiFi */ AFNetworkReachabilityStatusReachableViaWiFi = 2, };
2.注释
咱们必须知道一个事实,注释的代码是不会编译到目标文件的,所以放心大胆的注释吧。在平日里的开发中,应该常常问问本身是否把每段代码都当成写API那样对待?ios
曾经看过两种不一样的说辞,一种是说把代码注释尽可能少些,要求代码简介可读性强。另外一种是说注释要详细,着重考虑他人读代码的感觉。我的感受仍是写详 细一点比较好,由于可能过一段时间以后,本身再去看本身当时写的代码可能就不记得了。颇有可能在写这些繁琐的注释的过程当中,可以想到些什么,好比如何合并 掉一些不必的方法等等。nginx
示例代码:git
/*! @header SCNetworkReachability @discussion The SCNetworkReachability API allows an application to determine the status of a system's current network configuration and the reachability of a target host. In addition, reachability can be monitored with notifications that are sent when the status has changed. "Reachability" reflects whether a data packet, sent by an application into the network stack, can leave the local computer. Note that reachability does <i>not</i> guarantee that the data packet will actually be received by the host. */ /*! @typedef SCNetworkReachabilityRef @discussion This is the handle to a network address or name. */ typedef const struct CF_BRIDGED_TYPE(id) __SCNetworkReachability * SCNetworkReachabilityRef;
3.BOOL属性的property书写规则
一般咱们在定义一个BOOL属性的时候,要自定义getter方法,这样作的目的是为了增长程序的可读性。Apple中的代码也是这么写的。github
示例代码:web
/** Whether or not the network is currently reachable. */ @property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable; // setter self.reachable = YES; // getter if (self.isReachable) {}
4.按功能区分代码
假如咱们写的一个控制器中大概有500行代码,咱们应该保证可以快速的找到咱们须要查找的内容,这就须要把代码按照功能来分隔。算法
一般在.h中 咱们可使用一个自定义的特殊的注释来分隔,在.m中使用#pragma mark -
来分隔。编程
示例代码:
///--------------------- /// @name Initialization ///--------------------- ///------------------------------ /// @name Evaluating Server Trust ///------------------------------ #pragma mark - UI ...设置UI相关 #pragma mark - Data ...处理数据 #pragma mark - Action ...点击事件
5.通知
咱们都知道通知能够用来传递事件和数据,但要想用好它,也不太容易。在 AFNetworking 事件和数据的传递使用的是通知和Block,按照AFNetworking对通知的使用习惯。我总结了几点:
- 原则:若是咱们须要传递事件或数据,可采用代理和Block,同时额外增长一个通知。由于通知具备跨多个界面的优势。
- 释放问题:在接收通知的页面,必定要记得移除监听。
- 使用方法:在.h中
FOUNDATION_EXPORT
+NSString * const
+通知名
在.m中赋值。若是在别的页面用到这个通知,使用extern
+NSString * const
+通知名
就能够了。
ps: FOUNDATION_EXPORT 和#define 都能定义常量。FOUNDATION_EXPORT 可以使用==进行判断,效率略高。并且可以隐藏定义细节(就是实现部分不在.中)
示例代码:
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification; FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem; /** * 网络环境发生改变的时候接受的通知 */ NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; /** * 网络环境发生变化是会发送一个通知,同时携带一组状态数据,根据这个key来去除网络status */ NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";
6.国际化的问题
我我的认为在开发一个APP之初,就应该考虑国际化的问题,无论往后会不会用到这个功能。当你有了国际化的思想以后,在对控件进行布局的时候,就会 比只在一种语言下考虑的更多,这会让一我的对控件布局的视野更加宽阔。好了,这个问题就说这么多。有兴趣的朋友请自行查找相关内容。
7.私有方法
在开发中,不免会使用私有方法来协助咱们达到某种目的或获取某个数据。在oc中,我看到不少人都会这样写:- (void)funName {}
。我的是不同意这样写了,除非方法内部使用了self。总之,相似于这样的方法,其实跟咱们的业务并无太大的关系。我进入一个控制器的文件中,目光应该集中在业务代码上才对。
在 AFNetworking 中,通常都会把私有方法,也能够叫函数,放到头部,你即便不看这些代码,对于整个业务的理解也不会受到影响。因此,这种写法值得推荐。能够适当的使用内联函数,提升效率.
示例代码:
/** * 把枚举的值转换成字符串 */ NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusNotReachable: return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWWAN: return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWiFi: return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil); case AFNetworkReachabilityStatusUnknown: default: return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil); } } - (NSString *)AFStringFromNetworkReachabilityStatus:(AFNetworkReachabilityStatus)status { switch (status) { case AFNetworkReachabilityStatusNotReachable: return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWWAN: return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil); case AFNetworkReachabilityStatusReachableViaWiFi: return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil); case AFNetworkReachabilityStatusUnknown: default: return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil); } }
8.SCNetworkReachabilityRef(网络监控核心实现)
SCNetworkReachabilityRef 是获取网络状态的核心对象,建立这个对象有两个方法:
- SCNetworkReachabilityCreateWithName
- SCNetworkReachabilityCreateWithAddress
咱们看看实现网络监控的核心代码:
示例代码:
- (void)startMonitoring { [self stopMonitoring]; if (!self.networkReachability) { return; } __weak __typeof(self)weakSelf = self; AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ SCNetworkReachabilityFlags flags; if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) { AFPostReachabilityStatusChange(flags, callback); } }); }
上边的方法中涉及了一些 CoreFoundation 的知识,咱们来看看:
SCNetworkReachabilityContext点进去,会发现这是一个结构体,通常c语言的结构体是对要保存的数据的一种描述
示例代码:
typedef struct { CFIndex version; void * __nullable info; const void * __nonnull (* __nullable retain)(const void *info); void (* __nullable release)(const void *info); CFStringRef __nonnull (* __nullable copyDescription)(const void *info); } SCNetworkReachabilityContext;
- 第一个参数接受一个signed long 的参数
- 第二个参数接受一个void * 类型的值,至关于oc的id类型,void * 能够指向任何类型的参数
- 第三个参数 是一个函数 目的是对info作retain操做
- 第四个参数是一个函数,目的是对info作release操做
- 第五个参数是 一个函数,根据info获取Description字符串
设置网络监控分为下边几个步骤:
1.咱们先新建上下文
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
2.设置回调
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
3.加入RunLoop池
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
9.键值依赖
注册键值依赖,这个可能你们平时用的比较少。能够了解一下。举个例子:
好比说一个类User中有两个属性
还有一个卡片的类card
咱们写一个info的setter 和 getter 方法,
这么作的目的是,若是我监听info这个属性,当user中的name或者age有一个改变了,可以出发info的这个监听事件。
示例代码:
@interface User :NSObject @property (nonatomic,copy)NSString *name; @property (nonatomic,assign)NSUInteger age; @end @interface card :NSObject @property (nonatomic,copy)NSString *info; @property (nonatomic,strong)User *user; @end @implementation card - (NSString *)info { return [NSString stringWithFormat:@"%@/%lu",_user.name,(unsigned long)_user.age]; } - (void)setInfo:(NSString *)info { NSArray *array = [info componentsSeparatedByString:@"/"]; _user.name = array[0]; _user.age = [array[1] integerValue]; } + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; NSArray * moreKeyPaths = nil; if ([key isEqualToString:@"info"]) { moreKeyPaths = [NSArray arrayWithObjects:@"user.name", @"user.age", nil]; } if (moreKeyPaths) { keyPaths = [keyPaths setByAddingObjectsFromArray:moreKeyPaths]; } return keyPaths; } @end
10.HTTP
- HTTP协议用于客户端和服务器端之间的通讯
- 经过请求和相应的交换达成通讯
- HTTP是不保存状态的协议
- HTTP自身不会对请求和相应之间的通讯状态进行保存。什么意思呢?就是说,当有新的请求到来的时候,HTTP就会产生新的响应,对以前的请求和响应的保温信息不作任何存储。这也是为了快速的处理事务,保持良好的可伸展性而特地设计成这样的。
- 请求URI定位资源
- URI算是一个位置的索引,这样就能很方便的访问到互联网上的各类资源。
- 告知服务器意图的HTTP方法
- ①GET: 直接访问URI识别的资源,也就是说根据URI来获取资源。
- ②POST: 用来传输实体的主体。
- ③PUT: 用来传输文件。
- ④HEAD: 用来获取报文首部,和GET方法差很少,只是响应部分不会返回主体内容。
- ⑤DELETE: 删除文件,和PUT偏偏相反。按照请求的URI来删除指定位置的资源。
- ⑥OPTIONS: 询问支持的方法,用来查询针对请求URI指定的资源支持的方法。
- ⑦TRACE: 追踪路径,返回服务器端以前的请求通讯环信息。
- ⑧CONNECT: 要求用隧道协议链接代理,要求在与代理服务器通讯时创建隧道,实现用隧道协议进行TCP通讯。SSL(Secure Sockets Layer)和TLS(Transport Layer Security)就是把通讯内容加密后进行隧道传输的。
- 管线化让服务器具有了相应多个请求的能力
- Cookie让HTTP有迹可循
11.HTTPS
HTTPS是一个通讯安全的解决方案,能够说相对已经很是安全。为何它会是一个很安全的协议呢?下边会作出解释。你们能够看看这篇文章,解释的颇有意思 。《简单粗暴系列之HTTPS原理》.
HTTP + 加密 + 认证 + 完整性保护 = HTTPS
其实HTTPS是身披SSL外壳的HTTP,这句话怎么理解呢?
你们应该都知道HTTP是应用层的协议,但HTTPS并不是是应用层的一种新协议,只是HTTP通讯接口部分用SSL或TLS协议代替而已。
一般 HTTP 直接和TCP通讯,当使用SSL时就不一样了。要先和SSL通讯,再由SSL和TCP通讯。
这里再说一些关于加密的题外话:
现现在,一般加密和解密的算法都是公开的。举个例子: a * b = 200,加入a是你知道的密码,b是须要被加密的数据,200 是加密后的结果。那么这里这个*号就是一个很简单的加密算法。这个算法是如此简单。可是若是想要在不知道a和b其中一个的状况下进行破解也是很困难的。就 算咱们知道了200 而后获得a b 这个也很难。假设知道了密码a 那么b就很容易算出b = 200 / a 。
实际中的加密算法比这个要复杂的多。
介绍两种经常使用加密方法:
-
共享密钥加密
-
公开密钥加密
共享密钥加密就是加密和解密通用一个密钥,也称为对称加密。优势是加密解密速度快,缺点是一旦密钥泄露,别人也能解密数据。
公开密钥加密偏偏能解决共享密钥加密的困难,过程是这样的:
-
①发文方使用对方的公开密钥进行加密
-
②接受方在使用本身的私有密钥进行解密
关于公开密钥,也就是非对称加密 能够看看这篇文章 RSA算法原理
原理都是同样的,这个不一样于刚才举得a和b的例子,就算知道告终果和公钥,破解出被机密的数据是很是难的。这里边主要涉及到了复杂的数学理论。
HTTPS采用混合加密机制
HTTPS采用共享密钥加密和公开密钥加密二者并用的混合加密机制。
注意黄色的部分,这个指明了,咱们平时使用的一个场景。这篇文章会很长,不只仅是为了解释HTTPS,还为了可以增长记忆,当往后想看看的时候,就能经过读这边文章想起大部分的HTTPS的知识。下边解释一些更加详细的HTTPS过程。
12.如何获取证书中的PublicKey
// 在证书中获取公钥 static id AFPublicKeyForCertificate(NSData *certificate) { id allowedPublicKey = nil; SecCertificateRef allowedCertificate; SecCertificateRef allowedCertificates[1]; CFArrayRef tempCertificates = nil; SecPolicyRef policy = nil; SecTrustRef allowedTrust = nil; SecTrustResultType result; // 1. 根据二进制的certificate生成SecCertificateRef类型的证书 // NSData *certificate 经过CoreFoundation (__bridge CFDataRef)转换成 CFDataRef // 看下边的这个方法就能够知道须要传递参数的类型 /* SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef __nullable allocator, CFDataRef data) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0); */ allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate); // 2.若是allowedCertificate为空,则执行标记_out后边的代码 __Require_Quiet(allowedCertificate != NULL, _out); // 3.给allowedCertificates赋值 allowedCertificates[0] = allowedCertificate; // 4.新建CFArra: tempCertificates tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL); // 5. 新建policy为X.509 policy = SecPolicyCreateBasicX509(); // 6.建立SecTrustRef对象,若是出错就跳到_out标记处 __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out); // 7.校验证书的过程,这个不是异步的。 __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out); // 8.在SecTrustRef对象中取出公钥 allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust); _out: if (allowedTrust) { CFRelease(allowedTrust); } if (policy) { CFRelease(policy); } if (tempCertificates) { CFRelease(tempCertificates); } if (allowedCertificate) { CFRelease(allowedCertificate); } return allowedPublicKey; }
在二进制的文件中获取公钥的过程是这样
- ① NSData *certificate -> CFDataRef -> (SecCertificateCreateWithData) -> SecCertificateRef allowedCertificate
- ②判断SecCertificateRef allowedCertificate 是否是空,若是为空,直接跳转到后边的代码
- ③allowedCertificate 保存在allowedCertificates数组中
- ④allowedCertificates -> (CFArrayCreate) -> SecCertificateRef allowedCertificates[1]
- ⑤根据函数SecPolicyCreateBasicX509() -> SecPolicyRef policy
- ⑥SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust) -> 生成SecTrustRef allowedTrust
- ⑦SecTrustEvaluate(allowedTrust, &result) 校验证书
- ⑧(__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust) -> 获得公钥id allowedPublicKey
这个过程咱们平时也不怎么用,了解下就好了,真须要的时候知道去哪里找资料就好了。
这里边值得学习的地方是:
__Require_Quiet 和 __Require_noErr_Quiet 这两个宏定义。
咱们看看他们内部是怎么定义的
能够看出这个宏的用途是:当条件返回false时,执行标记之后的代码
能够看出这个宏的用途是:当条件抛出异常时,执行标记之后的代码
这样就有不少使用场景了。当必需要对条件进行判断的时候,咱们有下边几种方案了
-
#ifdef
这个是编译特性 -
if else
代码层次的判断 -
__Require_XXX
宏
_out 就是一个标记,这段代码__Require_Quiet 到_out之间的代码不会执行
13.URL编码
关于什么叫URI编码和为何要编码,请看我转载的这篇文章url 编码(percentcode 百分号编码)
根据RFC 3986的规定:URL百分比编码的保留字段分为:
- ':' '#' '[' ']' '@' '?' '/'
- '!' '$' '&' ''' '(' ')' '*' '+' ',' ';' '='
在对查询字段百分比编码时,'?'和'/'能够不用编码,其余的都要进行编码。我记得在使用支付宝支付时,在对数据进行URL编码时要求编码'/'.
NSString * AFPercentEscapedStringFromString(NSString *string) { static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4 static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;="; // '?'和'/'在query查询容许不被转译,所以!$&'()*+,;=和:#[]@都要被转译,也就是在URLQueryAllowedCharacterSet中删除掉这些字符 NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]]; // FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028 // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; static NSUInteger const batchSize = 50; NSUInteger index = 0; NSMutableString *escaped = @"".mutableCopy; while (index < string.length) { //http://www.jianshu.com/p/eb03e20f7b1c #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wgnu" NSUInteger length = MIN(string.length - index, batchSize); #pragma GCC diagnostic pop NSRange range = NSMakeRange(index, length); // To avoid breaking up character sequences such as 👴🏻👮🏽 range = [string rangeOfComposedCharacterSequencesForRange:range]; NSString *substring = [string substringWithRange:range]; NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; [escaped appendString:encoded]; index += range.length; } return escaped; }
上边的这个方法能够做为URL编码的通用方法,能够直接使用,也能够写到NSString的分类中。YYModel就有这个方法。
这里值得注意的是:
- 字符串须要通过过滤 ,过滤法则经过 NSMutableCharacterSet 实现。添加规则后,只对规则内的因子进行编码。
- 为了处理相似emoji这样的字符串,rangeOfComposedCharacterSequencesForRange 使用了while循环来处理,也就是把字符串按照batchSize分割处理完再拼回。
14.HTTPBody
咱们有必要了解下请求提body的组成部分。先看下一个HTTTP请求是什么样的?
某app的一个登陆POST请求:
POST / HTTP/1.1 Host: log.nuomi.com Content-Type: multipart/form-data; boundary=Boundary+6D3E56AA6EAA83B7 Cookie: access_log=7bde65268e2260bb0a85c7de2c67c468; BAIDUID=428D86FDBA6028DE2A5496BE3E7FC308:FG=1; BAINUOCUID=4368e1b7499c455dcd437da336ca1ca9feb8f57d; BDUSS=Ecwa3NvN1NjNWhsVGxWZktFfkc2bzJxQjZ3RFJpTFBiUzZqZUJZU0ZTSmZsN0ZXQVFBQUFBJCQAAAAAAAAAAAEAAABxbLRYWXV1dXV3dXV1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF8KilZfCopWR; bn_na_copid=60139b4b2ba75706fc384d987c2e4007; bn_na_ctag=W3siayI6Imljb25fMSIsInMiOiJ0dWFuIiwidiI6IjMyNiIsInQiOiIxNDUxODg2OTE0In1d; channel=user_center%7C%7C; channel_content=; channel_webapp=webapp; condition=6.0.3; domainUrl=sh; na_qab=6be39bfce918bb7b51887412e009faa6; UID=1488219249 Connection: keep-alive Accept: */* User-Agent: Bainuo/6.1.0 (iPhone; iOS 9.0; Scale/2.00) Accept-Language: zh-Hans-CN;q=1, en-CN;q=0.9 Content-Length: 22207 Accept-Encoding: gzip, deflate --Boundary+6D3E56AA6EAA83B7 /// 开始 Content-Disposition: form-data; name="app_version" 6.1.0 --Boundary+6D3E56AA6EAA83B7
HTTP请求头咱们就暂时不说了,看这个body的内容
--Boundary+6D3E56AA6EAA83B7 /// 开始 Content-Disposition: form-data; name="app_version" 6.1.0 --Boundary+6D3E56AA6EAA83B7
组成分为4个部分: 1.初始边界 2.body头 3.body 4.结束边界。 下边就会用着这些知识。
15.保证方法在主线程执行
有时候咱们必需要确保某个方法在主线程调用,就可使用下边的思路来作。
- (BOOL)transitionToNextPhase { // 保证代码在主线程 if (![[NSThread currentThread] isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{ [self transitionToNextPhase]; }); return YES; } }
16.代码跟思想的碰撞
示例代码:
- (BOOL)transitionToNextPhase { // 保证代码在主线程 if (![[NSThread currentThread] isMainThread]) { dispatch_sync(dispatch_get_main_queue(), ^{ [self transitionToNextPhase]; }); return YES; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wcovered-switch-default" switch (_phase) { case AFEncapsulationBoundaryPhase: _phase = AFHeaderPhase; break; case AFHeaderPhase: // 打开流,准备接受数据 [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [self.inputStream open]; _phase = AFBodyPhase; break; case AFBodyPhase: // 关闭流 [self.inputStream close]; _phase = AFFinalBoundaryPhase; break; case AFFinalBoundaryPhase: default: _phase = AFEncapsulationBoundaryPhase; break; } // 重置offset _phaseReadOffset = 0; #pragma clang diagnostic pop return YES; }
回过头来看这段代码,我又有新的想法。本来对数据的操做,对body的操做,是一件很复杂的事情。但做者的思路很是清晰。就像上边这个方法同样,它 只实现一个功能,就是切换body组成部分。它只作了这一件事,咱们在开发中,如遇到有些复杂的功能,在写方法的时候,可能考虑了不少东西,当时全部的考 虑可能都写到一个方法中了。
能不能写出一个思路图,先无论思路的实现如何,先一一列出来,最后在一一实现,一一拼接起来。
17.NSInputStream
NSInputStream有好几种类型,根据不一样的类型返回不一样方法建立的NSInputStream
示例代码:
- (NSInputStream *)inputStream { if (!_inputStream) { if ([self.body isKindOfClass:[NSData class]]) { _inputStream = [NSInputStream inputStreamWithData:self.body]; } else if ([self.body isKindOfClass:[NSURL class]]) { _inputStream = [NSInputStream inputStreamWithURL:self.body]; } else if ([self.body isKindOfClass:[NSInputStream class]]) { _inputStream = self.body; } else { _inputStream = [NSInputStream inputStreamWithData:[NSData data]]; } } return _inputStream; }
18.对文件的操做
- NSParameterAssert() 用来判断参数是否为空,若是为空就抛出异常
- 使用isFileURL 判断一个URL是否为fileURL 使用checkResourceIsReachableAndReturnError判断路径可以到达
- 使用 [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error] 获取本地文件属性
- lastPathComponent ,https://www.baidu.com/abc.html 结果就是abc.html
- pathExtension https://www.baidu.com/abc.html 结果就是html
19.NSURLRequestCachePolicy缓存策略
这个要仔细介绍下,在某些特殊的场景下仍是能用到的。咱们点开NSURLRequestCachePolicy 能够看到是一个枚举值
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy) { NSURLRequestUseProtocolCachePolicy = 0, NSURLRequestReloadIgnoringLocalCacheData = 1, NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData, NSURLRequestReturnCacheDataElseLoad = 2, NSURLRequestReturnCacheDataDontLoad = 3, NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented };
NSURLRequestUseProtocolCachePolicy 这个是默认的缓存策略,缓存不存在,就请求服务器,缓存存在,会根据response中的Cache-Control字段判断下一步操做,如: Cache-Control字段为must-revalidata, 则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端。
- NSURLRequestReloadIgnoringLocalCacheData 这个策略是无论有没有本地缓存,都请求服务器。
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData 这个策略会忽略本地缓存和中间代理 直接访问源server
- NSURLRequestReturnCacheDataElseLoad 这个策略指,有缓存就是用,无论其有效性,即Cache-Control字段 ,没有就访问源server
- NSURLRequestReturnCacheDataDontLoad 这个策略只加载本地数据,不作其余操做,适用于没有网路的状况
- NSURLRequestReloadRevalidatingCacheData 这个策略标示缓存数据必须获得服务器确认才能使用,未实现。
20.管线化
在HTTP链接中,通常都是一个请求对应一个链接,每次创建tcp链接是须要必定时间的。管线化,容许一次发送一组请求而没必要等到相应。但因为目前 并非全部的服务器都支持这项功能,所以这个属性默认是不开启的。管线化使用同一tcp链接完成任务,所以可以大大提交请求的时间。可是响应要和请求的顺 序 保持一致才行。使用场景也有,好比说首页要发送不少请求,能够考虑这种技术。但前提是创建链接成功后才可使用。
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] 4 2016-08-10 11:39:00.827 xxxx[3765:100078] 6 2016-08-10 11:39:00.827 xxxx[3765:100078] 8 2016-08-10 11:39:00.827 xxxx[3765:100078] 19 2016-08-10 11:39:00.827 xxxx[3765:100078] 20 2016-08-10 11:39:00.828 xxxx[3765:100078] 21 2016-08-10 11:39:00.828 xxxx[3765:100078] 22 2016-08-10 11:39:00.828 xxxx[3765:100078] 23 2016-08-10 11:39:00.828 xxxx[3765:100078] 24 2016-08-10 11:39:00.828 xxxx[3765:100078] 25 2016-08-10 11:39:00.828 xxxx[3765:100078] 26 2016-08-10 11:39:00.828 xxxx[3765:100078] 27 2016-08-10 11:39:00.828 xxxx[3765:100078] 28 2016-08-10 11:39:00.829 xxxx[3765:100078] 29
这充分说明了一下几点
- 它是一个无符号整数的集合
- 用addIndex方法能够添加单个整数值,使用addIndexesInRange能够添加一个范围,好比NSMakeRange(20, 10) 取20包括20后边十个整数
- 惟一性,集合内的整数不会重复出现
- 具体用法就再也不次作详细的解释了,有兴趣的朋友能够看看这篇文章: NSIndexSet 用法
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_jpg
2.以上图片手动解压方式都是在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则表示是并行队列。
关于队列的小的知识点,参考了这篇文章 Objective C 高级进阶— GCD队列浅析(一).
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-1 2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2 2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier 2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3 2016-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-1 2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2 2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier 2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3 2016-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] 1 2016-08-25 11:50:51.601 xxxx[1353:102804] 2 2016-08-25 11:50:56.603 xxxx[1353:102804] 3 2016-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] 1 2016-08-25 11:52:29.023 xxxx[1392:104246] 4 2016-08-25 11:52:29.023 xxxx[1392:104284] 2 2016-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 的源码解读到此就结束了。我曾经说要写一个网络框架,但随着对网络的更进一步的了解。一个好的框架不是随随便便写的。须要对使用者负责。所以我又找了几个目前比较流行的框架,对他们的思想研究研究。实属拿来主义。