Objective-C iOS之Block深究

在了解Block之前贝奇,我們有必要先了解一下一些基礎(chǔ)知識。
  我們都知道靠胜,Objective-C是由C語言擴(kuò)展而來的掉瞳。在Objective-C中毕源,引用是指向?qū)ο蟮囊粋€指針。即引用是一個變量陕习,也是一個指針霎褐,存儲的是對象的地址。那么该镣,引用本身其實也是存在地址的冻璃。所以引用和引用指向的對象是兩個不同的概念。
  1. 在Objective-C中损合,引用一般分為強(qiáng)引用類型和弱引用類型省艳。即由__strong__weak修飾的引用,不顯式指定引用類型時嫁审,默認(rèn)為__strong修飾的強(qiáng)引用類型跋炕。強(qiáng)引用會使指向的對象引用計數(shù)加1,而弱引用則不會律适,且弱引用在指向的對象被釋放時枣购,會被置為nil。
  2. Block塊分為定義時和運(yùn)行時兩種狀態(tài)擦耀。定義時指的是Block塊作為類進(jìn)行編譯處理和Block塊作為對象進(jìn)行創(chuàng)建的時候(Block塊代碼相當(dāng)于對象方法)棉圈,這個時候Block塊還沒有運(yùn)行。運(yùn)行時指的是Block塊代碼執(zhí)行的時候眷蜓。(此概念是為了理解Block而定義的分瘾,并不是官方定義的。)
  3. 非局部引用指的是定義在Block塊外的局部引用吁系,但是在Block塊中有該引用的作用域德召。
  了解完這些基礎(chǔ)知識后,我們就可以開始深入了解Block汽纤。這篇文章主要研究的是Block塊的正確使用上岗,以及為什么要這樣使用,并不對Block的實現(xiàn)原理做深入了解蕴坪。
  首先肴掷,對于在Block塊中使用非局部引用的情況,我們先看一下下面這個例子:

    NSString *string = @"Smith";
    
    void(^block)() = ^(){
        NSLog(@"%@",string);
    };
    
    string = @"Jackyson";
    block();

我們知道背传,運(yùn)行結(jié)果是Smith呆瞻。那么,為什么會是這樣呢径玖?我們可以嘗試的打印string引用的地址痴脾,注意,是string引用的地址梳星,而不是string指向的對象的地址赞赖,兩者是有很大的不同的滚朵。通過下面這個例子:

    NSString *string = @"Smith";
    NSLog(@"1-----string對象:%@,string引用的地址:%p",string,&string);
    
    void(^block)() = ^(){
        NSLog(@"3-----string對象:%@,string引用的地址:%p",string,&string);
    };
    
    string = @"Jackyson";
    NSLog(@"2-----string對象:%@,string引用的地址:%p",string,&string);
    block();

下面是我的運(yùn)行結(jié)果:

2016-09-11 16:43:23.179 Block[73629:3669954] 1-----string對象:Smith,string引用的地址:0x7fff5e6f3a18
2016-09-11 16:43:23.179 Block[73629:3669954] 2-----string對象:Jackyson,string引用的地址:0x7fff5e6f3a18
2016-09-11 16:43:23.180 Block[73629:3669954] 3-----string對象:Smith,string引用的地址:0x7fca60c37260

從上面我們可以看到,在Block塊內(nèi)部的string和Block塊外面定義的string是不一樣的前域,它們是兩個不同的引用始绍,只是都指向了“Smith”這個字符串對象。
  其實话侄,在Block塊內(nèi)部通過非局部引用訪問了外部對象時亏推,因為非局部引用在當(dāng)前作用域結(jié)束時會失效,Block如果不持有外部對象年堆,外部對象會被回收吞杭,而為了Block在真正運(yùn)行時能正確訪問外部對象,所以在Block塊定義時变丧,會在Block塊內(nèi)部復(fù)制非局部引用定義一個內(nèi)部引用(Block成員變量)芽狗。默認(rèn)情況下沒有使用__block修飾,則內(nèi)部引用是const修飾的引用痒蓬,這意味著內(nèi)部引用的值是不可改變的(無法指向新的對象)童擎;反之使用__block修飾,則內(nèi)部引用是沒有const修飾的引用攻晒,這意味著內(nèi)部引用的值是可以改變的(可以指向新的對象)顾复。同時,內(nèi)部引用和非局部引用的強(qiáng)弱類型一致鲁捏。
  讓我們回到上面的例子中芯砸,在Block塊中通過string訪問了“Smith”對象,那么Block塊定義時给梅,會自動生成名稱為string的內(nèi)部引用(作用域在Block塊內(nèi))假丧,且該指針的值不可修改(const),即引用不能指向新的對象动羽,所以內(nèi)部引用string一直指向字符串“Smith”包帚。而非局部引用string指向新的字符串對象“Jackyson”,與內(nèi)部引用string沒有關(guān)系运吓,所以就相當(dāng)于將“Smith”字符串復(fù)制一份到Block內(nèi)渴邦,且無法修改。
  那么羽德,我們?nèi)绻M麅?nèi)部引用指向新的對象几莽,或者在Block塊內(nèi)外修改引用值能互相影響迅办,應(yīng)該怎么做呢宅静?答案就是使用__block修飾引用,代碼例子如下:

    __block NSString *string = @"Smith";
    NSLog(@"1-----string對象:%@,string引用的地址:%p",string,&string);
    
    void(^block)() = ^(){
        NSLog(@"3-----string對象:%@,string引用的地址:%p",string,&string);
        string = @"SmithJackyson";
        NSLog(@"4-----string對象:%@,string引用的地址:%p",string,&string);
    };
    
    string = @"Jackyson";
    NSLog(@"2-----string對象:%@,string引用的地址:%p",string,&string);
    block();
    NSLog(@"5-----string對象:%@,string引用的地址:%p",string,&string);

下面是我的運(yùn)行結(jié)果:

2016-09-11 17:15:33.063 Block[74333:3683201] 1-----string對象:Smith,string引用的地址:0x7fff5510aa18
2016-09-11 17:15:33.066 Block[74333:3683201] 2-----string對象:Jackyson,string引用的地址:0x7fb351c1fb08
2016-09-11 17:15:33.066 Block[74333:3683201] 3-----string對象:Jackyson,string引用的地址:0x7fb351c1fb08
2016-09-11 17:15:33.067 Block[74333:3683201] 4-----string對象:SmithJackyson,string引用的地址:0x7fb351c1fb08
2016-09-11 17:15:33.068 Block[74333:3683201] 5-----string對象:SmithJackyson,string引用的地址:0x7fb351c1fb08

這里站欺,我對__block修飾符的影響做出了一個假設(shè):
  非局部引用是否使用__block修飾會影響內(nèi)部引用的訪問修飾符姨夹,因為Block塊也可以作為對象處理纤垂,所以當(dāng)不使用__block修飾時,內(nèi)部引用是@protected或者@private修飾的成員變量磷账,當(dāng)使用__block修飾時峭沦,內(nèi)部引用是@public修飾的成員變量。
  這一假設(shè)能較好的解釋為什么使用__block修飾后逃糟,在Block塊內(nèi)外修改引用值能相互影響吼鱼。如果有人有更好的解釋,歡迎與我分享4卵省9剿唷!
  從結(jié)果分析來看取募,使用__block修飾string引用琐谤,那么在Block塊定義時,在Block塊內(nèi)部定義一個內(nèi)部引用string(@public修飾的成員變量)玩敏。那么string指向“Jackyson”字符串對象為什么會影響B(tài)lock塊內(nèi)string的值呢斗忌?因為Block定義后,編譯器對Block定義之后使用的string進(jìn)行一些處理旺聚,使用的string是訪問到Block塊中的內(nèi)部引用string(類似block->string)织阳,所以string指向字符串“Jackyson”就是將Block塊內(nèi)string指向字符串“Jackyson”。同樣砰粹,在Block內(nèi)將string指向字符串“SmithJackyson”就是將Block塊外的string指向字符串“SmithJackyson”陈哑。
  所以__block修飾引用后,在Block塊內(nèi)外改變引用值會相互影響伸眶,因為它們是同一個引用惊窖。
  值得注意的是,基本數(shù)據(jù)類型的內(nèi)存管理不同于對象厘贼。對于Block塊使用定義在Block塊外面的局部變量(基本數(shù)據(jù)類型)的情況界酒,當(dāng)不使用__block修飾的時候,那么Block塊會定義一個const修飾的內(nèi)部變量嘴秸,并將Block塊外面的局部變量的值拷貝過來毁欣,所以Block塊外面修改局部變量的值并不影響B(tài)lock塊的內(nèi)部變量的值。當(dāng)使用__block修飾的時候岳掐,那么Block塊會直接使用外面的局部變量凭疮,這樣相當(dāng)于局部變量變?yōu)槿肿兞浚贐lock塊內(nèi)外修改變量的值會相互影響串述。
  另外执解,對于在Block塊中使用成員變量的情況,我們再來看一下下面的例子:

@interface ViewController ()
{
    NSString *_string;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _string = @"Smith";
    NSLog(@"1-----string對象:%@,string引用的地址:%p",_string,&_string);
    NSLog(@"1*****self引用的地址:%p",&self);
    
    void(^block)() = ^(){
        NSLog(@"3-----string對象:%@,string引用的地址:%p",_string,&_string);
        NSLog(@"3*****self引用的地址:%p",&self);
        _string = @"SmithJackyson";
        NSLog(@"4-----string對象:%@,string引用的地址:%p",_string,&_string);
        NSLog(@"4*****self引用的地址:%p",&self);
    };
    
    _string = @"Jackyson";
    NSLog(@"2-----string對象:%@,string引用的地址:%p",_string,&_string);
    NSLog(@"2*****self引用的地址:%p",&self);
    
    block();
    NSLog(@"5-----string對象:%@,string引用的地址:%p",_string,&_string);
    NSLog(@"5*****self引用的地址:%p",&self);
}

@end

下面是我的運(yùn)行結(jié)果:

2016-09-13 17:37:11.162 Block[98324:4200964] 1-----string對象:Smith,string引用的地址:0x7f90b0cf9280
2016-09-13 17:37:11.162 Block[98324:4200964] 1*****self引用的地址:0x7fff57817a28
2016-09-13 17:37:11.163 Block[98324:4200964] 2-----string對象:Jackyson,string引用的地址:0x7f90b0cf9280
2016-09-13 17:37:11.163 Block[98324:4200964] 2*****self引用的地址:0x7fff57817a28
2016-09-13 17:37:11.163 Block[98324:4200964] 3-----string對象:Jackyson,string引用的地址:0x7f90b0cf9280
2016-09-13 17:37:11.163 Block[98324:4200964] 3*****self引用的地址:0x7f90b0d86fb0
2016-09-13 17:37:11.163 Block[98324:4200964] 4-----string對象:SmithJackyson,string引用的地址:0x7f90b0cf9280
2016-09-13 17:37:11.163 Block[98324:4200964] 4*****self引用的地址:0x7f90b0d86fb0
2016-09-13 17:37:11.163 Block[98324:4200964] 5-----string對象:SmithJackyson,string引用的地址:0x7f90b0cf9280
2016-09-13 17:37:11.163 Block[98324:4200964] 5*****self引用的地址:0x7fff57817a28

從結(jié)果可以看到纲酗,Block中使用的_string引用就是成員變量_string衰腌,這是因為其實在Block塊中是通過self->_string來訪問成員變量_string新蟆,所以在Block中定義了內(nèi)部引用self,并通過內(nèi)部引用self->_string來訪問成員變量_string右蕊。所以Block塊中_string和Block塊外_string兩個引用是同一個琼稻,Block塊內(nèi)外改變引用值會互相影響。
  分析完__block修飾符的作用后饶囚,我們再來看看__strong__weak為什么可以避免循環(huán)引用呢帕翻?
  在前面我們已經(jīng)了解到了,當(dāng)Block通過外部引用訪問外部對象時萝风,在Block定義的時候熊咽,會復(fù)制外部引用定義一個內(nèi)部引用,且內(nèi)部引用強(qiáng)弱類型與外部引用一致闹丐。當(dāng)對象本身存在一個成員變量持有Block横殴,而Block內(nèi)部又訪問了對象本身,那么可能會造成循環(huán)引用卿拴。這里我們以UIViewController為例衫仑,如下圖:

Block塊通過self訪問外部對象.png

因為可能會出現(xiàn)這么一種情況,當(dāng)沒有外部強(qiáng)引用持有UIViewController對象和Block塊時堕花,那么成員變量block和成員變量self默認(rèn)是無法被置空的文狱,因為一般情況下兩個成員變量在Block塊和UIViewController對象dealloc時才被置為nil,那么兩個對象相互持有缘挽,無法被釋放瞄崇。但是如果成員變量block在某時刻可以被置為nil,那么兩個對象是能夠被釋放的壕曼。
  那么苏研,為什么使用weakSelf能夠防止循環(huán)引用呢?因為這樣Block塊成員變量為weakSelf(弱類型)腮郊,當(dāng)沒有強(qiáng)引用持有UIViewController時摹蘑,那么,UIViewController對象會被釋放轧飞,成員變量block和weakSelf會被置為nil衅鹿,Block塊也會被釋放。如下圖:


Block塊通過weakSelf訪問外部對象.png

如果UIViewController對象沒有成員變量block持有Block塊對象時过咬,為什么我們也需要使用weakSelf呢大渤?這里其實并不是為了防止循環(huán)引用,而是希望Block塊不持有UIViewController對象掸绞,讓UIViewController對象能夠及時被釋放泵三。因為如果Block塊持有UIViewController對象,而持有Block塊對象的引用沒有被置為nil的話,那么Block塊沒有被釋放切黔,也就會一直持有UIViewController對象砸脊,造成UIViewController對象無法被及時釋放具篇。而我們希望當(dāng)點擊返回按鈕時纬霞,UIViewController對象能夠被及時釋放,所以需要使用weakSelf驱显。如下圖:


Block塊通過weakSelf使外部對象能夠被及時釋放.png

那么诗芜,我們又為什么要使用strongSelf呢?這是因為當(dāng)Block塊回調(diào)時埃疫,有可能在回調(diào)過程中伏恐,UIViewController對象被釋放了,造成數(shù)據(jù)的狀態(tài)和邏輯出現(xiàn)錯誤栓霜。所以需要在Block塊執(zhí)行時翠桦,定義一個局部強(qiáng)引用strongSelf來持有UIViewController對象,讓Block塊執(zhí)行過程中胳蛮,UIViewController對象即使沒有被其他強(qiáng)引用持有销凑,也無法被釋放,確保數(shù)據(jù)的狀態(tài)和邏輯不會出現(xiàn)錯誤仅炊。當(dāng)Block執(zhí)行完成后斗幼,strongSelf就會被置為nil,這時UIViewController對象引用計數(shù)為0抚垄,就會被釋放了蜕窿。如下圖:


Block塊使用strongSelf防止外部對象被釋放.png

那么,什么情況下需要使用weakSelf和strongSelf呢呆馁?一般來說桐经,當(dāng)我們將Block塊作為參數(shù)傳入方法中,而方法本身進(jìn)行的是耗時操作(文件讀寫或網(wǎng)絡(luò)請求)浙滤,如果需要Block塊執(zhí)行時對象還存在次询,那么直接使用self;如果Block塊代碼是更新界面數(shù)據(jù)這種非必需的操作瓷叫,那么使用weakSelf屯吊;如果是回調(diào)時對象還存在需要執(zhí)行必要的數(shù)據(jù)操作和邏輯操作,那么使用weakSelf和strongSelf確保Block執(zhí)行過程中對象不會被釋放掉摹菠。即根據(jù)Block塊代碼要進(jìn)行的操作和操作是不是必要的來決定是使用self盒卸、weakSelf或者weakSelf和strongSelf。但是有一點需要注意的是次氨,必須確保Block塊在某一時刻會被置為nil蔽介,這樣持有的對象才能夠被釋放掉。確保Block執(zhí)行完后置為nil,或者持有Block塊的對象會被釋放掉虹蓄。
  總結(jié)來說犀呼,當(dāng)Block塊通過外部引用訪問外部對象時,在Block塊定義的時候薇组,會復(fù)制外部引用定義一個強(qiáng)弱類型一致的內(nèi)部引用外臂。外部引用如果使用__block修飾,則內(nèi)部引用是可變的律胀,即可以指向新的對象宋光;且訪問修飾符為@public,即Block塊定義后炭菌,使用的不再是外部引用罪佳,而是通過類似Block->_internalReference使用內(nèi)部引用。如果不使用__block修飾黑低,則內(nèi)部引用是不可變的赘艳,即不可以指向新的對象;且訪問修飾符為@protected或者@private克握,即Block塊定義后蕾管,使用的還是外部引用,與內(nèi)部引用沒有聯(lián)系玛荞,兩者互不影響娇掏。


轉(zhuǎn)載請注明:作者SmithJackyson

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市勋眯,隨后出現(xiàn)的幾起案子婴梧,更是在濱河造成了極大的恐慌,老刑警劉巖客蹋,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塞蹭,死亡現(xiàn)場離奇詭異,居然都是意外死亡讶坯,警方通過查閱死者的電腦和手機(jī)番电,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辆琅,“玉大人漱办,你說我怎么就攤上這事⊥裱蹋” “怎么了娩井?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長似袁。 經(jīng)常有香客問我洞辣,道長咐刨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任扬霜,我火速辦了婚禮定鸟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘著瓶。我一直安慰自己联予,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布蟹但。 她就那樣靜靜地躺著躯泰,像睡著了一般谭羔。 火紅的嫁衣襯著肌膚如雪华糖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天瘟裸,我揣著相機(jī)與錄音客叉,去河邊找鬼。 笑死话告,一個胖子當(dāng)著我的面吹牛兼搏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沙郭,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼佛呻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了病线?” 一聲冷哼從身側(cè)響起吓著,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎送挑,沒想到半個月后绑莺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡惕耕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年纺裁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片司澎。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡欺缘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挤安,到底是詐尸還是另有隱情谚殊,我是刑警寧澤,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布漱受,位于F島的核電站络凿,受9級特大地震影響骡送,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜絮记,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一摔踱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧怨愤,春花似錦派敷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至差导,卻和暖如春试躏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背设褐。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工颠蕴, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人助析。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓犀被,卻偏偏與公主長得像,于是被迫代替她去往敵國和親外冀。 傳聞我的和親對象是個殘疾皇子寡键,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,566評論 2 349

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