接著上次Swizzle部分繼續(xù)。上次已經(jīng)講到通過Swizzle方法hook住NSURLConnection和NSURLSession的代理。那么Hook住之后就需要插入自定義代碼劫映。今天主要講的就是FLEXNetworkRecorder坪郭。
FLEXNetworkTransaction
FLEXNetworkTransaction起得作用類似于一個(gè)模型類奢方。包含的都是數(shù)據(jù)颁督。里面的內(nèi)容也比較簡(jiǎn)單。不過也有一些地方值得學(xué)些的赚哗。
枚舉的命名方式及習(xí)慣
命名對(duì)于寫代碼來說非常重要她紫。比較正規(guī)的枚舉命名應(yīng)該是如下這樣:
typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
FLEXNetworkTransactionStateUnstarted,
FLEXNetworkTransactionStateAwaitingResponse,
FLEXNetworkTransactionStateReceivingData,
FLEXNetworkTransactionStateFinished,
FLEXNetworkTransactionStateFailed
};
枚舉類型名+不同狀態(tài)來表示具體的枚舉值。
通常情況下應(yīng)該為枚舉值返回不同的描述屿储。所以最好在額外定義一個(gè)類方法贿讹。比如:+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
。根據(jù)不同的枚舉值返回不同的字符串描述够掠。這樣可讀性比較好民褂。
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state
{
NSString *readableString = nil;
switch (state) {
case FLEXNetworkTransactionStateUnstarted:
readableString = @"Unstarted";
break;
case FLEXNetworkTransactionStateAwaitingResponse:
readableString = @"Awaiting Response";
break;
case FLEXNetworkTransactionStateReceivingData:
readableString = @"Receiving Data";
break;
case FLEXNetworkTransactionStateFinished:
readableString = @"Finished";
break;
case FLEXNetworkTransactionStateFailed:
readableString = @"Failed";
break;
}
return readableString;
}
盡量重寫方法- (NSString *)description
這條原則最先是在《Effective Objective-C 》這本書看到的。之后學(xué)習(xí)在《Effective Java 》同樣看到了這個(gè)原則。這樣在打印類信息的時(shí)候能夠提供更為全面的信息赊堪。
- (NSString *)description
{
NSString *description = [super description];
description = [description stringByAppendingFormat:@" id = %@;", self.requestID];
description = [description stringByAppendingFormat:@" url = %@;", self.request.URL];
description = [description stringByAppendingFormat:@" duration = %f;", self.duration];
description = [description stringByAppendingFormat:@" receivedDataLength = %lld", self.receivedDataLength];
return description;
}
FLEXNetworkRecorder
全局只有一個(gè)FLEXNetworkRecorder用于記錄請(qǐng)求過程面殖。
唯一標(biāo)示符
因?yàn)檎?qǐng)求的數(shù)量很多,為了區(qū)分每一個(gè)請(qǐng)求的話哭廉,必須做到對(duì)每一個(gè)請(qǐng)求進(jìn)行標(biāo)識(shí)脊僚。在FLEX中使用[[NSUUID UUID] UUIDString]
標(biāo)識(shí)每一個(gè)請(qǐng)求。關(guān)于iOS中的唯一標(biāo)示相關(guān)知識(shí)遵绰,可以參考這篇文章辽幌。
NSUUID在iOS 6中才出現(xiàn),這個(gè)值系統(tǒng)也不會(huì)存儲(chǔ)街立,每次調(diào)用的時(shí)候都會(huì)獲得一個(gè)新的唯一標(biāo)示符舶衬。如果要存儲(chǔ)的話,你需要自己存儲(chǔ)赎离。形如"E621E1F8-C36C-495A-93FC-0C247A3E6E5F"
。
在swillze替換的block中進(jìn)行創(chuàng)建requestID端辱。之后在請(qǐng)求準(zhǔn)備發(fā)出的時(shí)候會(huì)把requestID加入到FLEXNetworkRecorder的字典networkTransactionsForRequestIdentifiers中梁剔,用于保存所有請(qǐng)求。由于字典是無序存儲(chǔ)的舞蔽,所以額外定義了一個(gè)orderedTransactions來安裝請(qǐng)求的順序保存transaction荣病。
緩存響應(yīng)結(jié)果
緩存所有網(wǎng)絡(luò)請(qǐng)求的響應(yīng)是通過NSCache保存的。平時(shí)開發(fā)中用NSCache的地方也比較多渗柿。它的好處是可以直接設(shè)置緩存大小限制个盆,并且在內(nèi)存緊張的時(shí)候自動(dòng)釋放掉。
定義屬性@property (nonatomic, strong) NSCache *responseCache;
使用如下:
if (responseCacheLimit) {
[self.responseCache setTotalCostLimit:responseCacheLimit];
} else {
// Default to 25 MB max. The cache will purge earlier if there is memory pressure.
[self.responseCache setTotalCostLimit:25 * 1024 * 1024];
}
什么時(shí)候?qū)⒄?qǐng)求緩存呢朵栖?在網(wǎng)絡(luò)請(qǐng)求成功之后通過- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
緩存
if (shouldCache) {
[self.responseCache setObject:responseBody forKey:requestID cost:[responseBody length]];
}
線程安全
關(guān)于Object-c的線程安全颊亮,Objc.Io有一篇經(jīng)典的文章Thread-Safe Class Design,中文版線程安全類的設(shè)計(jì)
如果對(duì)多線程安全問題不是很清楚建議看看iOS多線程到底不安全在哪里陨溅?终惑。講得比較通俗易懂。
說了這么多其實(shí)就是想說NSMutableArray
和NSMutableDictionary
都是線程不安全的门扇,因?yàn)樗麄兛勺儽⒂小5沁@樣用了兩者來緩存請(qǐng)求的數(shù)據(jù)。在FLEXNetworkRecorder的init方法有如下這段:
// Serial queue used because we use mutable objects that are not thread safe
self.queue = dispatch_queue_create("com.flex.FLEXNetworkRecorder", DISPATCH_QUEUE_SERIAL);
創(chuàng)建了一個(gè)串行隊(duì)列臼寄,然后在這個(gè)串行隊(duì)列里面對(duì)NSMutableArray
和NSMutableDictionary
操作霸奕,就是線程安全的了。
使用方式:
dispatch_async(self.queue, ^{
FLEXNetworkTransaction *transaction = [[FLEXNetworkTransaction alloc] init];
transaction.requestID = requestID;
transaction.request = request;
transaction.startTime = startDate;
// 串行隊(duì)列里面執(zhí)行吉拳,保證同步质帅,也就到達(dá)線程安全
[self.orderedTransactions insertObject:transaction atIndex:0];
[self.networkTransactionsForRequestIdentifiers setObject:transaction forKey:requestID];
transaction.transactionState = FLEXNetworkTransactionStateAwaitingResponse;
[self postNewTransactionNotificationWithTransaction:transaction];
});
注意所謂異步執(zhí)行和同步執(zhí)行區(qū)分很多同學(xué)理解有偏差,正確的應(yīng)該是:是否阻塞當(dāng)前線程,如果阻塞則是同步临梗,不阻塞就是異步涡扼。
為了達(dá)到可變?nèi)萜鞯木€程安全,除了創(chuàng)建一個(gè)串行的隊(duì)列之外盟庞,還有通過加鎖吃沪,將容器的操作切換到同一個(gè)線程∈膊或者繼承可變數(shù)組票彪,自己實(shí)現(xiàn)一套線程安全的方法。已經(jīng)有前輩實(shí)現(xiàn)過類似的思路不狮,這里貼個(gè)地址降铸,有興趣的同學(xué)可以具體實(shí)現(xiàn)。SafeContainer
發(fā)送通知
這里為什么會(huì)把發(fā)送通知列出來了摇零,因?yàn)樵?jīng)因?yàn)榘l(fā)送通知犯過錯(cuò)推掸。
大家需要知道,默認(rèn)情況下驻仅,如果在哪一個(gè)線程發(fā)送通知谅畅,則接受處理方法就會(huì)在哪個(gè)線程執(zhí)行。大部分情況下都會(huì)涉及到UI的操作噪服,而UI操作應(yīng)該是在主線程中執(zhí)行的毡泻。所以如果通知涉及到UI操作的應(yīng)該用如下的方式:
;
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
[[NSNotificationCenter defaultCenter] postNotificationName:kFLEXNetworkRecorderNewTransactionNotification object:self userInfo:userInfo];
});
在控制器FLEXNetworkHistoryTableViewController中注冊(cè)通知[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTransactionsClearedNotification:) name:kFLEXNetworkRecorderTransactionsClearedNotification object:nil];
粘优,收到通知之后刷新界面
- (void)handleTransactionsClearedNotification:(NSNotification *)notification
{
[self updateTransactions];
[self.tableView reloadData];
}
思路總結(jié)
記錄網(wǎng)絡(luò)請(qǐng)求的關(guān)鍵部分如下:
- Swizzle住NSURLConnection和NSURLSession的代理方法仇味,將定義的Block替換為原有執(zhí)行。在Block里面注入監(jiān)測(cè)代碼雹顺。
- 將需要顯示的信息丹墨,model化,定義數(shù)據(jù)模型无拗。比如FLEXNetworkTransaction
- 定義可變數(shù)組带到,以及可變字典用于記錄的請(qǐng)求狀態(tài)以及內(nèi)容,注意線程安全問題英染。比如networkTransactionsForRequestIdentifiers
- 監(jiān)測(cè)過程揽惹,為每一個(gè)請(qǐng)求分配唯一標(biāo)示,加入到緩存中四康。下次直接從緩存中取搪搏。
- 在監(jiān)測(cè)中Block回調(diào)中,更新transaction的狀態(tài)闪金,比如在發(fā)出請(qǐng)求的時(shí)候疯溺,設(shè)置requestID论颅、request,startTime囱嫩。在接收到相應(yīng)的Block回調(diào)中恃疯,設(shè)置transaction的response、latency墨闲。
- 把更新后的transaction通過發(fā)送通知的方式今妄,告訴給控制器,更新界面鸳碧。注意發(fā)送會(huì)更新UI的通知應(yīng)該放在主線程中發(fā)送盾鳞。
THE END
這部分不是很難理解,到這里瞻离,基本上關(guān)于網(wǎng)絡(luò)這塊就寫完了腾仅。下篇準(zhǔn)備介紹一些FLEX里面有關(guān)系統(tǒng)日志以及文件瀏覽的具體實(shí)現(xiàn)。
擴(kuò)展閱讀
iOS中的唯一標(biāo)示
NSUUID /CFUUIDRef /UIDevice -unique?Identifier /-identifier?For?Vendor
線程安全的可變?nèi)萜黝?/a>