網(wǎng)絡(luò)請(qǐng)求中的block

場(chǎng)景

block和delegate是iOS開發(fā)者經(jīng)常用到的技術(shù)榕堰,也常常出現(xiàn)在各種面試題里厌衔,你經(jīng)常聽到他們之間的對(duì)比蜡歹。

我的態(tài)度是每個(gè)成熟的技術(shù)并沒有明顯的優(yōu)劣赡突,不應(yīng)該用誰好誰劣來評(píng)判他們,而應(yīng)該看誰更適合應(yīng)用場(chǎng)景袁辈,在合適的場(chǎng)合選擇合適的技術(shù)菜谣。

本篇文章將討論在 網(wǎng)絡(luò)層調(diào)用和回調(diào) 這個(gè)場(chǎng)景下的技術(shù)選擇。

本文涉及代碼

Block回調(diào)

一個(gè)常見的Block回調(diào)晚缩,通常是業(yè)務(wù)代碼調(diào)用請(qǐng)求尾膊,然后在回調(diào)中獲得返回的數(shù)據(jù),然后執(zhí)行業(yè)務(wù)邏輯荞彼,如下:

// 業(yè)務(wù)層代碼
- (void)blockDemo {
    [self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
        // 處理業(yè)務(wù)邏輯代碼
        // ...
        NSLog(@"Result from block:%@", data);
    }];
}

考慮點(diǎn)1:內(nèi)存

之所以會(huì)考慮這個(gè)問題冈敛,是因?yàn)橛脩羰褂脮r(shí)常常會(huì)剛進(jìn)入一個(gè)頁面,就立刻點(diǎn)返回卿泽,此時(shí)網(wǎng)絡(luò)請(qǐng)求剛發(fā)出去莺债,數(shù)據(jù)返回有可能還沒回來滋觉,那么這個(gè)網(wǎng)絡(luò)請(qǐng)求會(huì)怎么樣呢?

當(dāng)前頁面controller能夠被pop出棧齐邦,釋放掉嗎椎侠?畢竟網(wǎng)絡(luò)請(qǐng)求還沒結(jié)束

我們一一來驗(yàn)證

controller銷毀

如何驗(yàn)證內(nèi)存釋放已經(jīng)釋放,很簡(jiǎn)單措拇,首先我寫的網(wǎng)絡(luò)請(qǐng)求并不是真的網(wǎng)絡(luò)請(qǐng)求我纪,他只會(huì)延時(shí)5s返回一個(gè)假的數(shù)據(jù),用來方便模擬網(wǎng)絡(luò)請(qǐng)求

- (void)requestWithParms:(NSDictionary *)parms WithResult:(ResultBlock)result {

    int delay = NET_DELAY; // 默認(rèn)是5s丐吓,可以傳參數(shù)改變

    if ([parms valueForKey:@"delayTime"] != nil) {
        delay = (int)[parms valueForKey:@"delayTime"];
    }

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSString *resultData = @"This is a Mock Data!!";
        NSLog(@"Network Finish:%@",resultData);
        if (result) {
            result(resultData, nil);
        }
    });
}

再在viewcontroller的dealloc方法里打印log浅悉,就能知道dealloc是否被調(diào)用,如果調(diào)用說明可以釋放

- (void)dealloc {
    NSLog(@"NextPageViewController has been dealloc!")
}

這樣驗(yàn)證起來就是很簡(jiǎn)單券犁,只需要執(zhí)行blockDemo方法术健,然后立刻點(diǎn)返回,退出當(dāng)前vc粘衬,等5秒后如果看結(jié)果

結(jié)果是vc可以釋放的

2018-03-25 19:36:07.345523+0800 NetworkCallback[4580:3600834] NextPageViewController has been dealloc!

Block捕獲外界變量銷毀

我們?cè)龠M(jìn)一步想想荞估,Block有個(gè)最大的特點(diǎn)是可以訪問當(dāng)前的作用域,我們隨便創(chuàng)建一個(gè)數(shù)組稚新,重復(fù)上面操作勘伺,是否能夠銷毀

- (void)blockDemo {
    NSArray *outsideArray = @[@1, @2, @3];

    [self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
        // 處理業(yè)務(wù)邏輯
        // ...

        NSLog(@"Result from block:%@", data);
        NSLog(@"outsideArray :%@", outsideArray);
    }];
}

打印結(jié)果:

2018-03-25 19:55:40.997535+0800 NetworkCallback[4970:3641450] NextPageViewController has been dealloc!
2018-03-25 19:55:44.831721+0800 NetworkCallback[4970:3641450] outsideArray :(
    1,
    2,
    3
)

神奇不?褂删!

注意飞醉,vc先銷毀了,但是5s后屯阀,這個(gè)臨時(shí)變量竟然還沒有銷毀缅帘。那么這個(gè)變量存儲(chǔ)在哪里呢?留個(gè)懸念

考慮一下蹲盘,如果你在Block代碼里訪問了一個(gè)超大的文件股毫,這個(gè)文件必然是保存內(nèi)存的,然后此時(shí)你遇上了網(wǎng)絡(luò)慢召衔,接口好久沒有返回,那么這個(gè)超大的文件就會(huì)一直占用內(nèi)存

繼續(xù)祭陷,如果我在Block中訪問self呢苍凛?此時(shí)的self就是當(dāng)前的controller,這時(shí)候可以銷毀嗎兵志?

- (void)blockDemo {
    NSArray *outsideArray = @[@1, @2, @3];
    [self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
        // 處理業(yè)務(wù)邏輯
        // ...

        NSLog(@"Result from block:%@", data);
        NSLog(@"outsideArray :%@", outsideArray);
        NSLog(@"self :%@", self);
    }];
}

打印結(jié)果:

2018-03-25 20:04:21.012135+0800 NetworkCallback[5224:3659292] Network Finish:This is a Mock Data!!
2018-03-25 20:04:21.012476+0800 NetworkCallback[5224:3659292] Result from block:This is a Mock Data!!
2018-03-25 20:04:21.013186+0800 NetworkCallback[5224:3659292] outsideArray :(
    1,
    2,
    3
)
2018-03-25 20:04:21.013675+0800 NetworkCallback[5224:3659292] self :<NextPageViewController: 0x7f993662cf20>
2018-03-25 20:04:21.013858+0800 NetworkCallback[5224:3659292] NextPageViewController has been dealloc!

看到了嗎醇蝴?dealloc是最后打印出來的,也就是說Block不返回想罕,controller就釋放不了了悠栓!

看起來是在Block內(nèi)訪問誰霉涨,誰就無法釋放啊惭适!

有人會(huì)想這是不是就是循環(huán)引用呢笙瑟?

請(qǐng)大家回憶一下:Block循環(huán)引用是self強(qiáng)引用Block,Block里面再?gòu)?qiáng)引用self癞志,這里Block確實(shí)強(qiáng)引用了self往枷,但是self并沒有強(qiáng)引用Block,這個(gè)Block是一個(gè)參數(shù)傳給了NetService凄杯,跟self并無關(guān)系

而且是循環(huán)引用的話错洁,那么vc會(huì)一直釋放不掉,但看上面的log戒突,其實(shí)是可以釋放的屯碴,只是釋放的時(shí)機(jī)被延后了

但是,無論如何膊存,我們?cè)囋嚀Q成 weakself 會(huì)怎么樣呢窿锉? 打印結(jié)果:

2018-03-25 20:07:10.944557+0800 NetworkCallback[5294:3665537] NextPageViewController has been dealloc!
2018-03-25 20:07:15.228961+0800 NetworkCallback[5294:3665537] Network Finish:This is a Mock Data!!
2018-03-25 20:07:15.230836+0800 NetworkCallback[5294:3665537] Result from block:This is a Mock Data!!
2018-03-25 20:07:15.231074+0800 NetworkCallback[5294:3665537] outsideArray :(
    1,
    2,
    3
)
2018-03-25 20:07:15.231190+0800 NetworkCallback[5294:3665537] weakSelf :(null)

沒問題,果然換成weakself就解決了

原因是什么膝舅?

為什么在Block內(nèi)訪問誰嗡载,誰就無法釋放呢?為什么用weakself就解決了呢仍稀?

Block的本質(zhì)是個(gè)對(duì)象

Block看起來像一個(gè)函數(shù)洼滚,其實(shí)在objectice-c中,它是個(gè)對(duì)象技潘,之所以Block可以捕獲外部變量遥巴,正是因?yàn)樗莻€(gè)對(duì)象,他有自己的屬性享幽,他用屬性強(qiáng)引用了外部變量铲掐,導(dǎo)致外部變量(就是上面的self和outsideArray)的引用計(jì)數(shù)不為0,也就不能釋放了

weakself做了什么

當(dāng)Block中訪問weakself的時(shí)候值桩,強(qiáng)引用并沒有指向self摆霉,而是指向weakself,所以self可以被釋放

內(nèi)存小結(jié)

  1. 使用Block無論是否有循環(huán)引用的可能奔坟,都要使用weakself携栋,來防止vc被持有,而延遲釋放
  2. Block會(huì)導(dǎo)致對(duì)象的生命周期被延長(zhǎng)咳秉,特別是當(dāng)某些大文件被Block訪問時(shí)婉支,有幾率導(dǎo)致內(nèi)存不足

考慮點(diǎn)2:代碼安全

這是基于上面的考慮,我們已經(jīng)知道要用weakself來保證controller被及時(shí)釋放澜建,也可以在上面log中看到weakself變成了nil向挖,此時(shí)有可能導(dǎo)致crash蝌以,因?yàn)槲覀冋诓僮饕粋€(gè)nil對(duì)象 想象一個(gè)業(yè)務(wù)場(chǎng)景:分頁請(qǐng)求,你拉取了前面幾頁何之,比如page=3跟畅,然后去拉下一頁數(shù)據(jù),此時(shí)網(wǎng)絡(luò)請(qǐng)求尚未返回帝美,用戶就退出當(dāng)前頁面碍彭,此時(shí)

  1. 如果頁面能夠被釋放,那么Block中的業(yè)務(wù)邏輯代碼被執(zhí)行嗎?
  2. 如果可以執(zhí)行會(huì)有什么危險(xiǎn)悼潭?

其實(shí)第一個(gè)問題上面的log已經(jīng)回答了庇忌,log之所以被打印出來,其實(shí)就是Block中的代碼被執(zhí)行了嘛舰褪。

也就是說即使controller已經(jīng)銷毀皆疹,Block中的代碼還是會(huì)被執(zhí)行

第二個(gè)問題,執(zhí)行了會(huì)有什么危險(xiǎn)

  1. 通常這里會(huì)做json轉(zhuǎn)model占拍,會(huì)做某些數(shù)據(jù)轉(zhuǎn)換略就,如果返回?cái)?shù)據(jù)很大,比如是個(gè)三千多個(gè)元素的數(shù)組晃酒,那么勢(shì)必浪費(fèi)CPU去執(zhí)行表牢,注意,此時(shí)controller已經(jīng)銷毀了贝次,執(zhí)行代碼是無意義的崔兴,這里的CPU是確確實(shí)實(shí)的浪費(fèi)掉了。
  2. 想像一下蛔翅,此時(shí)weakself是nil敲茄,如果是分頁請(qǐng)求的數(shù)據(jù),你通常是把新的數(shù)據(jù)加到某個(gè)數(shù)組里山析,然后你就crash了堰燎,因?yàn)槟惆裯il加到數(shù)組去了
- (void)blockDemo {
    __weak typeof(self) weakSelf = self;
    NSArray *outsideArray = @[@1, @2, @3];
    [self.service requestWithParms:nil WithResult:^(id data, NSError *error) {
        // 處理業(yè)務(wù)邏輯
        // ...
        [data addObject:weakSelf.pageArray]; // weakSelf是nil
    }];
}

因此,你必須小心翼翼笋轨,寫上的保護(hù)代碼

- (void)blockDemo {
    __weak typeof(self) weakSelf = self;
    NSArray *outsideArray = @[@1, @2, @3];
    [self.service requestWithParms:nil WithResult:^(id data, NSError *error) {

        // 保護(hù)代碼
        if (weakSelf == nil) {
            return;
        }

        // 處理業(yè)務(wù)邏輯
        // ...
    }];
}

代碼安全總結(jié)

  1. Block會(huì)有執(zhí)行無意義代碼的可能秆剪,浪費(fèi)CPU
  2. Block會(huì)有操作nil對(duì)象導(dǎo)致crash的可能,因此要寫保護(hù)代碼

Delegate回調(diào)

經(jīng)過上面的驗(yàn)證翩腐,看起來好像Block有挺多麻煩了鸟款,那么delegate怎么樣呢?我們也來試一試

首先是模擬網(wǎng)絡(luò)請(qǐng)求茂卦,然后通過delegate回調(diào)

- (void)requestWithParms:(NSDictionary *)parms {
    int delay = NET_DELAY;

    if ([parms valueForKey:@"delayTime"] != nil) {
        delay = (int)[parms valueForKey:@"delayTime"];
    }

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        // 判斷成功
        // 判斷失敗

        NSString *resultData = @"This is a Mock Data!!";
        NSLog(@"Network Finish:%@",resultData);

        // ***這里加了判斷***
        if (self.delegate && [self.delegate respondsToSelector:@selector(networkFinishWithSuccess:AndError:)]) {
            [self.delegate networkFinishWithSuccess:resultData AndError:nil];
        }
    });
}

注意我用***標(biāo)注的注釋,這里有個(gè)delegate的判斷组哩,這里保證了Block考慮點(diǎn)2不會(huì)出現(xiàn)等龙,因?yàn)楫?dāng)delegate為nil的時(shí)候处渣,絕對(duì)不會(huì)執(zhí)行delegate的方法.

演示一下,代碼如下

#pragma mark - Delegate request
- (void)delegateDemo {
    self.service.delegate = self;
    [self.service requestWithParms:nil];
}

#pragma mark - NetWorkDelegate
- (void)networkFinishWithSuccess:(id)data AndError:(NSError *)error {
    NSLog(@"Result from Delegate:%@", data);
}

驗(yàn)證block存在的問題

同樣的蛛砰,一進(jìn)入Nextpage就立刻點(diǎn)返回罐栈,等5s看看代碼會(huì)不會(huì)執(zhí)行

2018-03-25 21:08:23.422103+0800 NetworkCallback[6399:3784145] NextPageViewController has been dealloc!

只有一條log,說明內(nèi)存釋放沒有問題泥畅,而且在回調(diào)前對(duì)delegate的判斷荠诬,使得我們非常方便的得知業(yè)務(wù)層是否還存在了,而如果用Block來實(shí)現(xiàn)就很麻煩位仁,在網(wǎng)絡(luò)回調(diào)前是無法得知的柑贞,一定要在Block里面加判斷代碼

總結(jié):

  1. 在業(yè)務(wù)層delegate比Block更加優(yōu)雅,可以在網(wǎng)絡(luò)層回調(diào)前就中斷邏輯聂抢,把錯(cuò)誤發(fā)生的可能提前中斷钧嘶,而不必進(jìn)入業(yè)務(wù)層才做判斷,這是一個(gè)很好的隔斷
  2. 沒有延長(zhǎng)某個(gè)對(duì)象生命周期琳疏,代碼更加清晰有决,易于管理

delegate自己的問題

那么難道delegate就沒有缺點(diǎn)了嗎?

多個(gè)業(yè)務(wù)層請(qǐng)求

之前的demo中只有一個(gè)業(yè)務(wù)層空盼,工程中絕對(duì)不會(huì)只有一個(gè)书幕,而NetService的delegate只能指向一個(gè)對(duì)象,豈不是只有一個(gè)請(qǐng)求能夠拿到回調(diào)揽趾,這豈不是滑天下之大稽台汇?

當(dāng)然不能這樣,如果使用delegate但骨,就必須對(duì)每個(gè)請(qǐng)求封裝成一個(gè)對(duì)象励七,而不能統(tǒng)一的用一個(gè)NetService

@interface RequestAPI : NSObject

@property (nonatomic, weak, nullable) id<NetWorkDelegate> delegate;

/**
 模擬Delegate請(qǐng)求方法

 @param parms 請(qǐng)求參數(shù)
 */
- (void)requestWithParms:(NSDictionary *)parms;

可是每個(gè)對(duì)象都去實(shí)現(xiàn)一篇請(qǐng)求邏輯豈不是很傻?奔缠!

所以底層還是調(diào)用NetService


#import "RequestAPI.h"

@implementation RequestAPI

- (void)requestWithParms:(NSDictionary *)parms {
    __weak typeof(self) weakSelf = self;
    [[NetService alloc] requestWithParms:parms WithResult:^(id  _Nonnull data, NSError * _Nonnull error) {
        if (weakSelf && [weakSelf respondsToSelector:@selector(networkFinishWithSuccess:AndError:)]) {
            [self.delegate networkFinishWithSuccess:resultData AndError:nil];
        }
    }];
}

總結(jié)

所以其實(shí)掠抬,使用delegate和Block的結(jié)合使用,由此我們可以看出

  1. Block適合做集約型調(diào)用校哎,每個(gè)業(yè)務(wù)邏輯不一樣两波,但是我們可以通過把代碼封裝在Block中,然后發(fā)給統(tǒng)一的方法來處理闷哆,實(shí)現(xiàn)了統(tǒng)一方法處理不同的邏輯
  2. delegate適合離散型調(diào)用腰奋,每次返回是同樣的邏輯
  3. 網(wǎng)絡(luò)層調(diào)用要delegate和Block結(jié)合使用,在業(yè)務(wù)層回調(diào)適合delegate抱怔,在底層網(wǎng)絡(luò)處理適合Block

引用于

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劣坊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子屈留,更是在濱河造成了極大的恐慌局冰,老刑警劉巖测蘑,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異康二,居然都是意外死亡碳胳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門沫勿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挨约,“玉大人,你說我怎么就攤上這事产雹〗氩眩” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵洽故,是天一觀的道長(zhǎng)贝攒。 經(jīng)常有香客問我,道長(zhǎng)时甚,這世上最難降的妖魔是什么隘弊? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮荒适,結(jié)果婚禮上梨熙,老公的妹妹穿的比我還像新娘。我一直安慰自己刀诬,他們只是感情好咽扇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陕壹,像睡著了一般质欲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糠馆,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天嘶伟,我揣著相機(jī)與錄音,去河邊找鬼又碌。 笑死九昧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毕匀。 我是一名探鬼主播铸鹰,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼皂岔!你這毒婦竟也來了蹋笼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姓建,沒想到半個(gè)月后诞仓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缤苫,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡速兔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了活玲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涣狗。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖舒憾,靈堂內(nèi)的尸體忽然破棺而出镀钓,到底是詐尸還是另有隱情,我是刑警寧澤镀迂,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布丁溅,位于F島的核電站,受9級(jí)特大地震影響探遵,放射性物質(zhì)發(fā)生泄漏窟赏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一箱季、第九天 我趴在偏房一處隱蔽的房頂上張望涯穷。 院中可真熱鬧,春花似錦藏雏、人聲如沸拷况。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赚瘦。三九已至,卻和暖如春奏寨,著一層夾襖步出監(jiān)牢的瞬間起意,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工服爷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杜恰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓仍源,卻偏偏與公主長(zhǎng)得像心褐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笼踩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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