FLEX源碼分析三(網(wǎng)絡(luò)監(jiān)測(cè)記錄FLEXNetworkRecorder)

接著上次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í)就是想說NSMutableArrayNSMutableDictionary都是線程不安全的门扇,因?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ì)NSMutableArrayNSMutableDictionary操作霸奕,就是線程安全的了。

使用方式:

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>

  • 序言:七十年代末套利,一起剝皮案震驚了整個(gè)濱河市推励,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肉迫,老刑警劉巖吹艇,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異昂拂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)抛猖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門格侯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人财著,你說我怎么就攤上這事联四。” “怎么了撑教?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵朝墩,是天一觀的道長(zhǎng)泡一。 經(jīng)常有香客問我矛辕,道長(zhǎng),這世上最難降的妖魔是什么喉酌? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任愤兵,我火速辦了婚禮鹿霸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秆乳。我一直安慰自己懦鼠,他們只是感情好钻哩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肛冶,像睡著了一般街氢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睦袖,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天珊肃,我揣著相機(jī)與錄音,去河邊找鬼扣泊。 笑死近范,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的延蟹。 我是一名探鬼主播评矩,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼阱飘!你這毒婦竟也來了斥杜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤沥匈,失蹤者是張志新(化名)和其女友劉穎蔗喂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體高帖,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缰儿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了散址。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乖阵。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖预麸,靈堂內(nèi)的尸體忽然破棺而出瞪浸,到底是詐尸還是另有隱情,我是刑警寧澤吏祸,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布对蒲,位于F島的核電站,受9級(jí)特大地震影響贡翘,放射性物質(zhì)發(fā)生泄漏蹈矮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一床估、第九天 我趴在偏房一處隱蔽的房頂上張望含滴。 院中可真熱鬧,春花似錦丐巫、人聲如沸谈况。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碑韵。三九已至赡茸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祝闻,已是汗流浹背占卧。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留联喘,地道東北人华蜒。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像豁遭,于是被迫代替她去往敵國(guó)和親叭喜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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

  • AFHTTPRequestOperationManager 網(wǎng)絡(luò)傳輸協(xié)議UDP蓖谢、TCP捂蕴、Http、Socket闪幽、X...
    Carden閱讀 4,319評(píng)論 0 12
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理啥辨,服務(wù)發(fā)現(xiàn),斷路器盯腌,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 從三月份找實(shí)習(xí)到現(xiàn)在溉知,面了一些公司,掛了不少腕够,但最終還是拿到小米着倾、百度、阿里燕少、京東、新浪蒿囤、CVTE客们、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,184評(píng)論 11 349
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,497評(píng)論 25 707
  • 中國(guó)有1000多萬人從事傳銷,相比中國(guó)的幾億腦殘材诽,幾億噴子底挫,幾億騙子,千萬坐臺(tái)小姐艾滋病脸侥,千萬吸毒建邓,傳銷在中國(guó)的十...
    天上文曲星閱讀 611評(píng)論 0 0