AFNetworking是适用于iOS,macOS,watchOS和tvOS的的网络库。它构建于Foundation URL系统之上,扩展了Cocoa内置的强大的高级网络抽象。它采用模块化架构,设计精良,功能丰富的API,使用起来很是简单。本文重点介绍缓存和安全两个模块;objective-c
AFNetWorking是基于NSURLSession(iOS7以上的网络请求框架),在生成配置的时候有三种缓存配置选择:数据库
// 默认会话模式:默认添加内存缓存和磁盘缓存。
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
// 瞬时会话模式:只添加内存缓存,不实现磁盘缓存。
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
// 后台会话模式:内存和磁盘都不进行缓存。
+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;
复制代码
咱们还能够对缓存的大小进行设置,只须要对NSURLCache进行初始化就能够了。数组
在-application:didFinishLaunchingWithOptions:
中对[NSURLCache sharedURLCache]
进行初始化设置:缓存
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
复制代码
也能够单独对NSURLSession的configuration进行设置, 在AFNetWorking中对于图片网络请求设置了20M的内存缓存和150M的硬盘缓存:安全
+ (NSURLCache *)defaultURLCache {
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:150 * 1024 * 1024
diskPath:@"com.alamofire.imagedownloader"];
}
复制代码
简介:缓存策略是指对网络请求缓存处理是使用缓存仍是不使用:服务器
NSURLRequestUseProtocolCachePolicy:对特定的URL请求使用网络协议中实现的缓存逻辑,这是默认的策略。
NSURLRequestReloadIgnoringLocalCacheData:数据须要从原始地址加载,不使用现有缓存。NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不只忽略本地缓存,同时也忽略代理服务器或其余中间介质目前已有的、协议容许的缓存。
NSURLRequestReturnCacheDataElseLoad:不管缓存是否过时,先使用本地缓存数据。若是缓存中没有请求所对应的数据,那么从原始地址加载数据。
NSURLRequestReturnCacheDataDontLoad:不管缓存是否过时,先使用本地缓存数据。若是缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
NSURLRequestReloadRevalidatingCacheData:从原始地址确认缓存数据的合法性后,缓存数据就可使用,不然从原始地址加载。
复制代码
简介:就是咱们常说的把数据保存在本地,好比FMDB、CoreData、归档、NSUserDefaults、NSFileManager等等,这里就很少说了。 AFNetWorking3.0没有直接作图片硬盘缓存,而是经过URL缓存作的硬盘缓存。也就是说,若是内存缓存没有读取到图片,就会调用下载逻辑,经过下载缓存的内存缓存硬盘缓存来获取到已下载过的图片,若是没有下载过,就会从新下载。 若是咱们本身作图片硬盘缓存建议使用NSFileManager,由于通常图片data会比较大,测试证实路径缓存会比放在数据库有更高的性能。网络
AFAutoPurgingImageCache是协议定义了一组API,用于同步地从缓存中添加、删除和获取图像。session
每次下载完图片将图片添加到缓存中以前,先去检测一下当前已缓存memoryUsage的大小,若是超过总缓存memoryCapacity,则获取须要清理的缓存大小,须要清理的缓存大小 = 总缓存大小 - 预留缓存大小(memoryCapacity - preferredMemoryUsageAfterPurge),而后将缓存里面的图片按照插入时间排序,将最早插入的图片依次删除,若是删除缓存大小大于须要清理的缓存大小,则结束。架构
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
dispatch_barrier_async(self.synchronizationQueue, ^{
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
// 第一步:若是identifier的缓存对象存在,要为identifier赋予新的缓存对象,并将以前的缓存对象的内存减去
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
if (previousCachedImage != nil) {
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
self.cachedImages[identifier] = cacheImage;
self.currentMemoryUsage += cacheImage.totalBytes;
});
dispatch_barrier_async(self.synchronizationQueue, ^{
if (self.currentMemoryUsage > self.memoryCapacity) {
/* 第二步:计算出将要移除的内存大小 */
UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
/*第三步: 将全部的缓存按最后使用时间从远到近排列,而后依次删除,每次删除时都累加删除的缓存的内存大小,累加的内存大于等于要移除的内存大小时中止删除。*/
NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate" ascending:YES];
[sortedImages sortUsingDescriptors:@[sortDescriptor]];
UInt64 bytesPurged = 0;
for (AFCachedImage *cachedImage in sortedImages) {
[self.cachedImages removeObjectForKey:cachedImage.identifier];
bytesPurged += cachedImage.totalBytes;
if (bytesPurged >= bytesToPurge) {
break ;
}
}
/* 第四步:更新当前使用的缓存内存大小 */
self.currentMemoryUsage -= bytesPurged;
}
});
}
复制代码
首先介绍一下HTTPS:app
HTTPS协议中的加密是用共享密钥加密与公开密钥加密的混合加密。共享密钥加密,加解密使用同一个密钥,即对称加密;公开密钥加密,分为公钥与私钥,公钥加密公开使用,而私钥则用于解密。HTTPS协议在交换密钥时使用公开密钥加密,在通讯报文交换的过程当中使用共享密钥。首先使用公开密钥加密的方式安全地交换将在稍后的共享密钥加密中要使用的密钥,在确保交换的密钥时安全的前提下,再使用共享密钥加密方式进行通信交互。
AFSecurityPolicy类只作了一件事,就是完成HTTPS认证,是对系统类库<Security/Security.h>的进一步封装;AFNetworking的默认证书认证流程是客户端单项认证,假如须要双向验证,则服务器和客户端都须要发送数字证书给对方验证,须要用户自行实现。
AFSecurityPolicy的三种验证模式:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone, //表明无条件信任服务器的证书
AFSSLPinningModePublicKey, //表明会对服务器返回的证书中的PublicKey进行验证
AFSSLPinningModeCertificate, //表明会对服务器返回的证书同本地证书所有进行校验
}
接下来声明了四个属性:
SSLPinningMode:返回SSL Pinning的类型,默认的是AFSSLPinningModeNone;
pinnedCertificates:保存着全部的可用作校验的证书的集合,evaluateServerTrust:forDomain: 就会返回true,即经过校验;
allowInvalidCertificates:使用容许无效或过时的证书,默认是NO不容许;
validatesDomainName:是否验证证书中的域名domain,默认是YES;
复制代码
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
// 挑战处理类型为默认
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// 自定义的方法,用来如何应对服务器端的认证挑战
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 判断接收服务器挑战的方法是不是信任证书
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 去验证服务器端的证书是否安全,即HTTPS的单项认证,这是AF的默认处理的认证方式,其它的认证方式只能由咱们本身的block去实现
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
// 证书挑战
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
// 默认挑战方式 (不提供证书)
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
// 取消挑战 (取消链接)
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
// 默认挑战方式 (不提供证书)
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
/* 完成挑战,将信任凭证发送给服务器 */
if (completionHandler) {
completionHandler(disposition, credential);
}
}
复制代码
// 根据当前的安全策略,返回是否受信
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
/*
评估必须保证是一个有效的过程。
若是容许无效证书,可是没有证书或者采用AFSSLPinningModeNone模式,其余信息齐全的时候,这时候会被告知,这是一个无效的验证过程。
*/
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
// 建立安全策略,若是须要对域名进行验证,则建立附带入参域名的 SSL 安全策略。 不然建立一个基于 X.509 的安全策略。
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 将建立的安全策略加入到服务器给予的信任评估中。 这个评估认证将会和本地的证书或者公钥进行评估得出结果。
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// 在 AFSSLPinningModeNone,不会进行公钥或者证书的认证。 只要确保服务器给的信任评估是有效的(可以获取到CA根证书)。 或者,若是用户设置容许无效证书,那么也会直接返回经过。
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
// 根据不一样的模式进行相应的认证操做
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
// 上面已经对 AFSSLPinningModeNone 作了出了,这里直接当成默认的状况。返回NO
case AFSSLPinningModeCertificate: {
// 验证本地证书和服务器发过来的信任进行甄别。
// 这里本地使用的证书 "pinnedCertificates" 可能有不少个,因而转化成 CFData 放入数组。
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 将pinnedCertificates设置成须要参与验证的Anchor Certificate(锚点证书,经过SecTrustSetAnchorCertificates设置了参与校验锚点证书以后,假如验证的数字证书是这个锚点证书的子节点,
//即验证的数字证书是由锚点证书对应CA或子CA签发的,
//或是该证书自己,则信任该证书),具体就是调用SecTrustEvaluate来验证。
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// 获取全部的服务器的证书链,注意这和 AnchorCertificates 是不相同的。
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
// 遍历服务器的证书链
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
// 若是证书链中包含了本地的证书,说明 serverTrust 是有效的服务器信任凭证。返回YES
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
// 验证本地公钥和服务器发过来的信任进行甄别
// 获取服务器的公钥链
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
// 遍历公钥链 在本地查找合适的公钥,若是有至少一个符合,则为验证经过。
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
复制代码
简介:闫名月,民生科技有限公司,用户体验技术部Firefly移动金融开发平台iOS开发工程师。