AFNetworking 3.0 源碼解讀 總結(jié)(干貨)(上)

養(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;
  1. 第一個(gè)參數(shù)接受一個(gè)signed long 的參數(shù)
  2. 第二個(gè)參數(shù)接受一個(gè)void * 類(lèi)型的值拍霜,相當(dāng)于oc的id類(lèi)型祠饺,void * 可以指向任何類(lèi)型的參數(shù)
  3. 第三個(gè)參數(shù) 是一個(gè)函數(shù) 目的是對(duì)info做retain操作
  4. 第四個(gè)參數(shù)是一個(gè)函數(shù),目的是對(duì)info做release操作
  5. 第五個(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

  1. HTTP協(xié)議用于客戶端和服務(wù)器端之間的通信
  2. 通過(guò)請(qǐng)求和相應(yīng)的交換達(dá)成通信
  3. 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ì)成這樣的靡砌。
  4. 請(qǐng)求URI定位資源
    • URI算是一個(gè)位置的索引度液,這樣就能很方便的訪問(wèn)到互聯(lián)網(wǎng)上的各種資源。
  5. 告知服務(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>
  6. 管線化讓服務(wù)器具備了相應(yīng)多個(gè)請(qǐng)求的能力
  7. 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ù)雜的多。

介紹兩種常用加密方法:

  1. 共享密鑰加密

  2. 公開(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í)候,我們有下邊幾種方案了

  1. #ifdef 這個(gè)是編譯特性

  2. if else 代碼層次的判斷

  3. __Require_XXX


_out 就是一個(gè)標(biāo)記跪但,這段代碼__Require_Quiet 到_out之間的代碼不會(huì)執(zhí)行

13.URL編碼

關(guān)于什么叫URI編碼和為什么要編碼履羞,請(qǐng)看我轉(zhuǎn)載的這篇文章url 編碼(percentcode 百分號(hào)編碼)

根據(jù)RFC 3986的規(guī)定:URL百分比編碼的保留字段分為:

  • ':' '#' '[' ']' '@' '?' '/'
  • '!' '$' '&' ''' '(' ')' '*' '+' ',' ';' '='

在對(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ì)文件的操作

  1. NSParameterAssert() 用來(lái)判斷參數(shù)是否為空,如果為空就拋出異常
  2. 使用isFileURL 判斷一個(gè)URL是否為fileURL 使用checkResourceIsReachableAndReturnError判斷路徑能夠到達(dá)
  3. 使用 [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error] 獲取本地文件屬性
  4. lastPathComponent 可免,https://www.baidu.com/abc.html 結(jié)果就是abc.html
  5. 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ù)罗捎。但前提是建立連接成功后才可以使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拉盾,一起剝皮案震驚了整個(gè)濱河市宛逗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盾剩,老刑警劉巖雷激,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異告私,居然都是意外死亡屎暇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)驻粟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)根悼,“玉大人,你說(shuō)我怎么就攤上這事蜀撑〖费玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵酷麦,是天一觀的道長(zhǎng)矿卑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)沃饶,這世上最難降的妖魔是什么母廷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮糊肤,結(jié)果婚禮上琴昆,老公的妹妹穿的比我還像新娘。我一直安慰自己馆揉,他們只是感情好业舍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般舷暮。 火紅的嫁衣襯著肌膚如雪蟋座。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天脚牍,我揣著相機(jī)與錄音向臀,去河邊找鬼。 笑死诸狭,一個(gè)胖子當(dāng)著我的面吹牛券膀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驯遇,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼芹彬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了叉庐?” 一聲冷哼從身側(cè)響起舒帮,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陡叠,沒(méi)想到半個(gè)月后玩郊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枉阵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年译红,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兴溜。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侦厚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拙徽,到底是詐尸還是另有隱情刨沦,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布膘怕,位于F島的核電站想诅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淳蔼。R本人自食惡果不足惜侧蘸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一裁眯、第九天 我趴在偏房一處隱蔽的房頂上張望鹉梨。 院中可真熱鬧,春花似錦穿稳、人聲如沸存皂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旦袋。三九已至骤菠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疤孕,已是汗流浹背商乎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祭阀,地道東北人鹉戚。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像专控,于是被迫代替她去往敵國(guó)和親抹凳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理伦腐,服務(wù)發(fā)現(xiàn)赢底,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,501評(píng)論 25 707
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 10,869評(píng)論 6 13
  • 1.OkHttp源碼解析(一):OKHttp初階2 OkHttp源碼解析(二):OkHttp連接的"前戲"——HT...
    隔壁老李頭閱讀 20,813評(píng)論 24 176
  • 我喜歡我們系的canteen,因?yàn)槟芸匆?jiàn)港口和海.這樣我就忘記了自己身處半島上的一個(gè)點(diǎn),這個(gè)點(diǎn)不夠開(kāi)闊,面對(duì)的是一...
    jupitergranit閱讀 183評(píng)論 0 0