養(yǎng)成記筆記的習(xí)慣毅人,對(duì)于一個(gè)軟件工程師來(lái)說(shuō)吭狡,我覺(jué)得很重要。記得在知乎上看到過(guò)一個(gè)問(wèn)題丈莺,說(shuō)是人類(lèi)最大的缺點(diǎn)是什么划煮?我個(gè)人覺(jué)得記憶算是一個(gè)缺點(diǎn)。它就像時(shí)間一樣场刑,會(huì)自己消散般此。
前言
終于寫(xiě)完了 AFNetworking 的源碼解讀。這一過(guò)程耗時(shí)數(shù)天牵现。當(dāng)我回過(guò)頭又重頭到尾的讀了一篇铐懊,又有所收獲。不禁讓我想起了當(dāng)初上學(xué)時(shí)的種種情景瞎疼。我們應(yīng)該對(duì)知識(shí)進(jìn)行反復(fù)的記憶和理解科乎。下邊是我總結(jié)的 AFNetworking 中能夠?qū)W到的知識(shí)點(diǎn)。
1.枚舉(enum)
使用原則:當(dāng)滿足一個(gè)有限的并具有統(tǒng)一主題的集合的時(shí)候贼急,我們就考慮使用枚舉茅茂。這在很多框架中都驗(yàn)證了這個(gè)原則令杈。最重要的是能夠增加程序的可讀性逗噩。
示例代碼:
/**
* 網(wǎng)絡(luò)類(lèi)型 (需要封裝為一個(gè)自己的枚舉)
*/
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
/**
* 未知
*/
AFNetworkReachabilityStatusUnknown = -1,
/**
* 無(wú)網(wǎng)絡(luò)
*/
AFNetworkReachabilityStatusNotReachable = 0,
/**
* WWAN 手機(jī)自帶網(wǎng)絡(luò)
*/
AFNetworkReachabilityStatusReachableViaWWAN = 1,
/**
* WiFi
*/
AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
2.注釋
我們必須知道一個(gè)事實(shí),注釋的代碼是不會(huì)編譯到目標(biāo)文件的纲刀,因此放心大膽的注釋吧。在平日里的開(kāi)發(fā)中耻台,應(yīng)該經(jīng)常問(wèn)問(wèn)自己是否把每段代碼都當(dāng)成寫(xiě)API那樣對(duì)待盆耽?
曾經(jīng)看過(guò)兩種不同的說(shuō)辭,一種是說(shuō)把代碼注釋盡量少些析恢,要求代碼簡(jiǎn)介可讀性強(qiáng)映挂。另一種是說(shuō)注釋要詳細(xì),著重考慮他人讀代碼的感受鞍时。個(gè)人感覺(jué)還是寫(xiě)詳細(xì)一點(diǎn)比較好及塘,因?yàn)榭赡苓^(guò)一段時(shí)間之后笙僚,自己再去看自己當(dāng)時(shí)寫(xiě)的代碼可能就不記得了。很有可能在寫(xiě)這些繁瑣的注釋的過(guò)程中,能夠想到些什么鸳兽,比如如何合并掉一些沒(méi)必要的方法等等。
示例代碼:
/*!
@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書(shū)寫(xiě)規(guī)則
通常我們?cè)诙x一個(gè)BOOL屬性的時(shí)候衷掷,要自定義getter方法,這樣做的目的是為了增加程序的可讀性懦胞。Apple中的代碼也是這么寫(xiě)的躏尉。
示例代碼:
/**
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.按功能區(qū)分代碼
假如我們寫(xiě)的一個(gè)控制器中大概有500行代碼,我們應(yīng)該保證能夠快速的找到我們需要查找的內(nèi)容教藻,這就需要把代碼按照功能來(lái)分隔。
通常在.h中 我們可以使用一個(gè)自定義的特殊的注釋來(lái)分隔痊臭,在.m中使用#pragma mark -
來(lái)分隔广匙。
示例代碼:
///---------------------
/// @name Initialization
///---------------------
///------------------------------
/// @name Evaluating Server Trust
///------------------------------
#pragma mark - UI
...設(shè)置UI相關(guān)
#pragma mark - Data
...處理數(shù)據(jù)
#pragma mark - Action
...點(diǎn)擊事件
5.通知
我們都知道通知可以用來(lái)傳遞事件和數(shù)據(jù)潮剪,但要想用好它抗碰,也不太容易。在 AFNetworking 事件和數(shù)據(jù)的傳遞使用的是通知和Block看疗,按照AFNetworking對(duì)通知的使用習(xí)慣。我總結(jié)了幾點(diǎn):
- 原則:如果我們需要傳遞事件或數(shù)據(jù)怖辆,可采用代理和Block疗隶,同時(shí)額外增加一個(gè)通知。因?yàn)橥ㄖ哂锌缍鄠€(gè)界面的優(yōu)點(diǎn)坚弱。
- 釋放問(wèn)題:在接收通知的頁(yè)面荒叶,一定要記得移除監(jiān)聽(tīng)。
- 使用方法:在.h中
FOUNDATION_EXPORT
+NSString * const
+通知名
在.m中賦值愁茁。如果在別的頁(yè)面用到這個(gè)通知嘶居,使用extern
+NSString * const
+通知名
就可以了。
ps: FOUNDATION_EXPORT 和#define 都能定義常量佑吝。FOUNDATION_EXPORT 能夠使用==進(jìn)行判斷迹蛤,效率略高嚷量。而且能夠隱藏定義細(xì)節(jié)(就是實(shí)現(xiàn)部分不在.中)
示例代碼:
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
/**
* 網(wǎng)絡(luò)環(huán)境發(fā)生改變的時(shí)候接受的通知
*/
NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
/**
* 網(wǎng)絡(luò)環(huán)境發(fā)生變化是會(huì)發(fā)送一個(gè)通知嗜历,同時(shí)攜帶一組狀態(tài)數(shù)據(jù),根據(jù)這個(gè)key來(lái)去除網(wǎng)絡(luò)status
*/
NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";
6.國(guó)際化的問(wèn)題
我個(gè)人認(rèn)為在開(kāi)發(fā)一個(gè)APP之初暴匠,就應(yīng)該考慮國(guó)際化的問(wèn)題每窖,不管日后會(huì)不會(huì)用到這個(gè)功能。當(dāng)你有了國(guó)際化的思想之后瀑志,在對(duì)控件進(jìn)行布局的時(shí)候昧甘,就會(huì)比只在一種語(yǔ)言下考慮的更多,這會(huì)讓一個(gè)人對(duì)控件布局的視野更加寬闊痛黎。好了湖饱,這個(gè)問(wèn)題就說(shuō)這么多。有興趣的朋友請(qǐng)自行查找相關(guān)內(nèi)容仅仆。
7.私有方法
在開(kāi)發(fā)中墓拜,難免會(huì)使用私有方法來(lái)協(xié)助我們達(dá)到某種目的或獲取某個(gè)數(shù)據(jù)。在oc中涌韩,我看到很多人都會(huì)這樣寫(xiě):- (void)funName {}
。個(gè)人是不贊成這樣寫(xiě)了擎淤,除非方法內(nèi)部使用了self嘴拢∠猓總之,類(lèi)似于這樣的方法泥兰,其實(shí)跟我們的業(yè)務(wù)并沒(méi)有太大的關(guān)系。我進(jìn)入一個(gè)控制器的文件中撕捍,目光應(yīng)該集中在業(yè)務(wù)代碼上才對(duì)。
在 AFNetworking 中狮腿,一般都會(huì)把私有方法缘厢,也可以叫函數(shù),放到頭部,你即使不看這些代碼删壮,對(duì)于整個(gè)業(yè)務(wù)的理解也不會(huì)受到影響央碟。所以,這種寫(xiě)法值得推薦洛勉∈蘸粒可以適當(dāng)?shù)氖褂脙?nèi)聯(lián)函數(shù)昔搂,提高效率.
示例代碼:
/**
* 把枚舉的值轉(zhuǎn)換成字符串
*/
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(網(wǎng)絡(luò)監(jiān)控核心實(shí)現(xiàn))
SCNetworkReachabilityRef 是獲取網(wǎng)絡(luò)狀態(tài)的核心對(duì)象,創(chuàng)建這個(gè)對(duì)象有兩個(gè)方法:
- SCNetworkReachabilityCreateWithName
- SCNetworkReachabilityCreateWithAddress
我們看看實(shí)現(xiàn)網(wǎng)絡(luò)監(jiān)控的核心代碼:
示例代碼:
- (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 的知識(shí),我們來(lái)看看:
SCNetworkReachabilityContext點(diǎn)進(jìn)去,會(huì)發(fā)現(xiàn)這是一個(gè)結(jié)構(gòu)體炉菲,一般c語(yǔ)言的結(jié)構(gòu)體是對(duì)要保存的數(shù)據(jù)的一種描述
示例代碼:
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;
- 第一個(gè)參數(shù)接受一個(gè)signed long 的參數(shù)
- 第二個(gè)參數(shù)接受一個(gè)void * 類(lèi)型的值拍霜,相當(dāng)于oc的id類(lèi)型祠饺,void * 可以指向任何類(lèi)型的參數(shù)
- 第三個(gè)參數(shù) 是一個(gè)函數(shù) 目的是對(duì)info做retain操作
- 第四個(gè)參數(shù)是一個(gè)函數(shù),目的是對(duì)info做release操作
- 第五個(gè)參數(shù)是 一個(gè)函數(shù)勺鸦,根據(jù)info獲取Description字符串
設(shè)置網(wǎng)絡(luò)監(jiān)控分為下邊幾個(gè)步驟:
1.我們先新建上下文
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
2.設(shè)置回調(diào)
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
3.加入RunLoop池
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
9.鍵值依賴
注冊(cè)鍵值依賴,這個(gè)可能大家平時(shí)用的比較少军拟⌒赶ⅲ可以了解一下阁最。舉個(gè)例子:
比如說(shuō)一個(gè)類(lèi)User中有兩個(gè)屬性還有一個(gè)卡片的類(lèi)card
我們寫(xiě)一個(gè)info的setter 和 getter 方法,
這么做的目的是,如果我監(jiān)聽(tīng)info這個(gè)屬性棋傍,當(dāng)user中的name或者age有一個(gè)改變了,能夠出發(fā)info的這個(gè)監(jiān)聽(tīng)事件麸拄。
示例代碼:
@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協(xié)議用于客戶端和服務(wù)器端之間的通信
- 通過(guò)請(qǐng)求和相應(yīng)的交換達(dá)成通信
- HTTP是不保存狀態(tài)的協(xié)議
- HTTP自身不會(huì)對(duì)請(qǐng)求和相應(yīng)之間的通信狀態(tài)進(jìn)行保存。什么意思呢?就是說(shuō)主穗,當(dāng)有新的請(qǐng)求到來(lái)的時(shí)候忽媒,HTTP就會(huì)產(chǎn)生新的響應(yīng)陆错,對(duì)之前的請(qǐng)求和響應(yīng)的保溫信息不做任何存儲(chǔ)对嚼。這也是為了快速的處理事務(wù)绳慎,保持良好的可伸展性而特意設(shè)計(jì)成這樣的靡砌。
- 請(qǐng)求URI定位資源
- URI算是一個(gè)位置的索引度液,這樣就能很方便的訪問(wèn)到互聯(lián)網(wǎng)上的各種資源。
- 告知服務(wù)器意圖的HTTP方法
- ①GET: 直接訪問(wèn)URI識(shí)別的資源霹购,也就是說(shuō)根據(jù)URI來(lái)獲取資源齐疙。
- ②POST: 用來(lái)傳輸實(shí)體的主體乍丈。
- ③PUT: 用來(lái)傳輸文件剂碴。
- ④HEAD: 用來(lái)獲取報(bào)文首部,和GET方法差不多轻专,只是響應(yīng)部分不會(huì)返回主體內(nèi)容忆矛。
- ⑤DELETE: 刪除文件,和PUT恰恰相反请垛。按照請(qǐng)求的URI來(lái)刪除指定位置的資源催训。
- ⑥OPTIONS: 詢問(wèn)支持的方法混稽,用來(lái)查詢針對(duì)請(qǐng)求URI指定的資源支持的方法洽洁。
- ⑦TRACE: 追蹤路徑复唤,返回服務(wù)器端之前的請(qǐng)求通信環(huán)信息跟磨。
- ⑧CONNECT: 要求用隧道協(xié)議連接代理,要求在與代理服務(wù)器通信時(shí)建立隧道衣形,實(shí)現(xiàn)用隧道協(xié)議進(jìn)行TCP通信。SSL(Secure Sockets Layer)和TLS(Transport Layer Security)就是把通信內(nèi)容加密后進(jìn)行隧道傳輸?shù)摹?/li>
- 管線化讓服務(wù)器具備了相應(yīng)多個(gè)請(qǐng)求的能力
- Cookie讓HTTP有跡可循
11.HTTPS
HTTPS是一個(gè)通信安全的解決方案,可以說(shuō)相對(duì)已經(jīng)非常安全骏令。為什么它會(huì)是一個(gè)很安全的協(xié)議呢凰兑?下邊會(huì)做出解釋。大家可以看看這篇文章昌妹,解釋的很有意思 。《簡(jiǎn)單粗暴系列之HTTPS原理》.
HTTP + 加密 + 認(rèn)證 + 完整性保護(hù) = HTTPS
其實(shí)HTTPS是身披SSL外殼的HTTP贰健,這句話怎么理解呢?
大家應(yīng)該都知道HTTP是應(yīng)用層的協(xié)議枚赡,但HTTPS并非是應(yīng)用層的一種新協(xié)議疲迂,只是HTTP通信接口部分用SSL或TLS協(xié)議代替而已。
通常 HTTP 直接和TCP通信,當(dāng)使用SSL時(shí)就不同了。要先和SSL通信,再由SSL和TCP通信组砚。
這里再說(shuō)一些關(guān)于加密的題外話:
現(xiàn)如今,通常加密和解密的算法都是公開(kāi)的。舉個(gè)例子: a * b = 200,加入a是你知道的密碼艾君,b是需要被加密的數(shù)據(jù)洲炊,200 是加密后的結(jié)果。那么這里這個(gè)*號(hào)就是一個(gè)很簡(jiǎn)單的加密算法。這個(gè)算法是如此簡(jiǎn)單。但是如果想要在不知道a和b其中一個(gè)的情況下進(jìn)行破解也是很困難的。就算我們知道了200 然后得到a b 這個(gè)也很難。假設(shè)知道了密碼a 那么b就很容易算出b = 200 / a 。
實(shí)際中的加密算法比這個(gè)要復(fù)雜的多。
介紹兩種常用加密方法:
共享密鑰加密
公開(kāi)密鑰加密
共享密鑰加密就是加密和解密通用一個(gè)密鑰,也稱為對(duì)稱加密歉闰。優(yōu)點(diǎn)是加密解密速度快,缺點(diǎn)是一旦密鑰泄露,別人也能解密數(shù)據(jù)怔毛。
公開(kāi)密鑰加密恰恰能解決共享密鑰加密的困難,過(guò)程是這樣的:
①發(fā)文方使用對(duì)方的公開(kāi)密鑰進(jìn)行加密
②接受方在使用自己的私有密鑰進(jìn)行解密
關(guān)于公開(kāi)密鑰代箭,也就是非對(duì)稱加密 可以看看這篇文章 RSA算法原理
原理都是一樣的碑幅,這個(gè)不同于剛才舉得a和b的例子雷猪,就算知道了結(jié)果和公鑰准潭,破解出被機(jī)密的數(shù)據(jù)是非常難的趁俊。這里邊主要涉及到了復(fù)雜的數(shù)學(xué)理論域仇。
HTTPS采用混合加密機(jī)制
HTTPS采用共享密鑰加密和公開(kāi)密鑰加密兩者并用的混合加密機(jī)制刑然。
注意黃色的部分,這個(gè)指明了暇务,我們平時(shí)使用的一個(gè)場(chǎng)景泼掠。這篇文章會(huì)很長(zhǎng),不僅僅是為了解釋HTTPS垦细,還為了能夠增加記憶择镇,當(dāng)日后想看看的時(shí)候,就能通過(guò)讀這邊文章想起大部分的HTTPS的知識(shí)括改。下邊解釋一些更加詳細(xì)的HTTPS過(guò)程腻豌。
12.如何獲取證書(shū)中的PublicKey
// 在證書(shū)中獲取公鑰
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. 根據(jù)二進(jìn)制的certificate生成SecCertificateRef類(lèi)型的證書(shū)
// NSData *certificate 通過(guò)CoreFoundation (__bridge CFDataRef)轉(zhuǎn)換成 CFDataRef
// 看下邊的這個(gè)方法就可以知道需要傳遞參數(shù)的類(lèi)型
/*
SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef __nullable allocator,
CFDataRef data) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
*/
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
// 2.如果allowedCertificate為空,則執(zhí)行標(biāo)記_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.創(chuàng)建SecTrustRef對(duì)象嘱能,如果出錯(cuò)就跳到_out標(biāo)記處
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
// 7.校驗(yàn)證書(shū)的過(guò)程吝梅,這個(gè)不是異步的。
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
// 8.在SecTrustRef對(duì)象中取出公鑰
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;
}
在二進(jìn)制的文件中獲取公鑰的過(guò)程是這樣
- ① NSData *certificate -> CFDataRef -> (SecCertificateCreateWithData) -> SecCertificateRef allowedCertificate
- ②判斷SecCertificateRef allowedCertificate 是不是空惹骂,如果為空苏携,直接跳轉(zhuǎn)到后邊的代碼
- ③allowedCertificate 保存在allowedCertificates數(shù)組中
- ④allowedCertificates -> (CFArrayCreate) -> SecCertificateRef allowedCertificates[1]
- ⑤根據(jù)函數(shù)SecPolicyCreateBasicX509() -> SecPolicyRef policy
- ⑥SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust) -> 生成SecTrustRef allowedTrust
- ⑦SecTrustEvaluate(allowedTrust, &result) 校驗(yàn)證書(shū)
- ⑧(__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust) -> 得到公鑰id allowedPublicKey
這個(gè)過(guò)程我們平時(shí)也不怎么用,了解下就行了对粪,真需要的時(shí)候知道去哪里找資料就行了右冻。
這里邊值得學(xué)習(xí)的地方是:
__Require_Quiet 和 __Require_noErr_Quiet 這兩個(gè)宏定義装蓬。
我們看看他們內(nèi)部是怎么定義的
可以看出這個(gè)宏的用途是:當(dāng)條件返回false時(shí),執(zhí)行標(biāo)記以后的代碼
可以看出這個(gè)宏的用途是:當(dāng)條件拋出異常時(shí)纱扭,執(zhí)行標(biāo)記以后的代碼
這樣就有很多使用場(chǎng)景了牍帚。當(dāng)必須要對(duì)條件進(jìn)行判斷的時(shí)候,我們有下邊幾種方案了
#ifdef
這個(gè)是編譯特性if else
代碼層次的判斷__Require_XXX
宏
_out 就是一個(gè)標(biāo)記跪但,這段代碼__Require_Quiet 到_out之間的代碼不會(huì)執(zhí)行
13.URL編碼
關(guān)于什么叫URI編碼和為什么要編碼履羞,請(qǐng)看我轉(zhuǎn)載的這篇文章url 編碼(percentcode 百分號(hào)編碼)
- ':' '#' '[' ']' '@' '?' '/'
- '!' '$' '&' ''' '(' ')' '*' '+' ',' ';' '='
在對(duì)查詢字段百分比編碼時(shí),'?'和'/'可以不用編碼屡久,其他的都要進(jìn)行編碼忆首。我記得在使用支付寶支付時(shí),在對(duì)數(shù)據(jù)進(jìn)行URL編碼時(shí)要求編碼'/'.
NSString * AFPercentEscapedStringFromString(NSString *string) {
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
// '?'和'/'在query查詢?cè)试S不被轉(zhuǎn)譯被环,因此!$&'()*+,;=和:#[]@都要被轉(zhuǎn)譯糙及,也就是在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.reibang.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;
}
上邊的這個(gè)方法可以作為URL編碼的通用方法,可以直接使用筛欢,也可以寫(xiě)到NSString的分類(lèi)中浸锨。YYModel就有這個(gè)方法。
這里值得注意的是:
- 字符串需要經(jīng)過(guò)過(guò)濾 版姑,過(guò)濾法則通過(guò) NSMutableCharacterSet 實(shí)現(xiàn)柱搜。添加規(guī)則后,只對(duì)規(guī)則內(nèi)的因子進(jìn)行編碼剥险。
- 為了處理類(lèi)似emoji這樣的字符串聪蘸,rangeOfComposedCharacterSequencesForRange 使用了while循環(huán)來(lái)處理,也就是把字符串按照batchSize分割處理完再拼回表制。
14.HTTPBody
我們有必要了解下請(qǐng)求提body的組成部分健爬。先看下一個(gè)HTTTP請(qǐng)求是什么樣的?
某app的一個(gè)登錄POST請(qǐng)求:
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 /// 開(kāi)始
Content-Disposition: form-data; name="app_version"
6.1.0
--Boundary+6D3E56AA6EAA83B7
HTTP請(qǐng)求頭我們就暫時(shí)不說(shuō)了么介,看這個(gè)body的內(nèi)容
--Boundary+6D3E56AA6EAA83B7 /// 開(kāi)始
Content-Disposition: form-data; name="app_version"
6.1.0
--Boundary+6D3E56AA6EAA83B7
組成分為4個(gè)部分: 1.初始邊界 2.body頭 3.body 4.結(jié)束邊界娜遵。 下邊就會(huì)用著這些知識(shí)。
15.保證方法在主線程執(zhí)行
有時(shí)候我們必須要確保某個(gè)方法在主線程調(diào)用壤短,就可以使用下邊的思路來(lái)做设拟。
- (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: // 打開(kāi)流,準(zhǔn)備接受數(shù)據(jù)
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.inputStream open];
_phase = AFBodyPhase;
break;
case AFBodyPhase: // 關(guān)閉流
[self.inputStream close];
_phase = AFFinalBoundaryPhase;
break;
case AFFinalBoundaryPhase:
default:
_phase = AFEncapsulationBoundaryPhase;
break;
}
// 重置offset
_phaseReadOffset = 0;
#pragma clang diagnostic pop
return YES;
}
回過(guò)頭來(lái)看這段代碼久脯,我又有新的想法纳胧。原本對(duì)數(shù)據(jù)的操作,對(duì)body的操作桶现,是一件很復(fù)雜的事情躲雅。但作者的思路非常清晰。就像上邊這個(gè)方法一樣骡和,它只實(shí)現(xiàn)一個(gè)功能相赁,就是切換body組成部分相寇。它只做了這一件事,我們?cè)陂_(kāi)發(fā)中钮科,如遇到有些復(fù)雜的功能唤衫,在寫(xiě)方法的時(shí)候,可能考慮了很多東西绵脯,當(dāng)時(shí)所有的考慮可能都寫(xiě)到一個(gè)方法中了佳励。
能不能寫(xiě)出一個(gè)思路圖,先不管思路的實(shí)現(xiàn)如何蛆挫,先一一列出來(lái)赃承,最后在一一實(shí)現(xiàn),一一拼接起來(lái)悴侵。
17.NSInputStream
NSInputStream有好幾種類(lèi)型瞧剖,根據(jù)不同的類(lèi)型返回不同方法創(chuàng)建的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.對(duì)文件的操作
- NSParameterAssert() 用來(lái)判斷參數(shù)是否為空,如果為空就拋出異常
- 使用isFileURL 判斷一個(gè)URL是否為fileURL 使用checkResourceIsReachableAndReturnError判斷路徑能夠到達(dá)
- 使用 [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error] 獲取本地文件屬性
- lastPathComponent 可免,https://www.baidu.com/abc.html 結(jié)果就是abc.html
- pathExtension https://www.baidu.com/abc.html 結(jié)果就是html
19.NSURLRequestCachePolicy緩存策略
這個(gè)要仔細(xì)介紹下抓于,在某些特殊的場(chǎng)景下還是能用到的。我們點(diǎn)開(kāi)NSURLRequestCachePolicy 可以看到是一個(gè)枚舉值
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,
NSURLRequestReloadIgnoringLocalCacheData = 1,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
NSURLRequestReturnCacheDataElseLoad = 2,
NSURLRequestReturnCacheDataDontLoad = 3,
NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
};
NSURLRequestUseProtocolCachePolicy 這個(gè)是默認(rèn)的緩存策略浇借,緩存不存在捉撮,就請(qǐng)求服務(wù)器,緩存存在妇垢,會(huì)根據(jù)response中的Cache-Control字段判斷下一步操作巾遭,如: Cache-Control字段為must-revalidata, 則詢問(wèn)服務(wù)端該數(shù)據(jù)是否有更新,無(wú)更新的話直接返回給用戶緩存數(shù)據(jù)修己,若已更新恢总,則請(qǐng)求服務(wù)端迎罗。
- NSURLRequestReloadIgnoringLocalCacheData 這個(gè)策略是不管有沒(méi)有本地緩存睬愤,都請(qǐng)求服務(wù)器。
- NSURLRequestReloadIgnoringLocalAndRemoteCacheData 這個(gè)策略會(huì)忽略本地緩存和中間代理 直接訪問(wèn)源server
- NSURLRequestReturnCacheDataElseLoad 這個(gè)策略指纹安,有緩存就是用尤辱,不管其有效性,即Cache-Control字段 厢岂,沒(méi)有就訪問(wèn)源server
- NSURLRequestReturnCacheDataDontLoad 這個(gè)策略只加載本地?cái)?shù)據(jù)光督,不做其他操作,適用于沒(méi)有網(wǎng)路的情況
- NSURLRequestReloadRevalidatingCacheData 這個(gè)策略標(biāo)示緩存數(shù)據(jù)必須得到服務(wù)器確認(rèn)才能使用塔粒,未實(shí)現(xiàn)结借。
20.管線化
在HTTP連接中,一般都是一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)連接卒茬,每次建立tcp連接是需要一定時(shí)間的船老。管線化咖熟,允許一次發(fā)送一組請(qǐng)求而不必等到相應(yīng)。但由于目前并不是所有的服務(wù)器都支持這項(xiàng)功能柳畔,因此這個(gè)屬性默認(rèn)是不開(kāi)啟的馍管。管線化使用同一tcp連接完成任務(wù),因此能夠大大提交請(qǐng)求的時(shí)間薪韩。但是響應(yīng)要和請(qǐng)求的順序 保持一致才行确沸。使用場(chǎng)景也有,比如說(shuō)首頁(yè)要發(fā)送很多請(qǐng)求俘陷,可以考慮這種技術(shù)罗捎。但前提是建立連接成功后才可以使用。