block詳解

開始之前颓鲜,我想先提幾個(gè)問題尊浪,看看大家是否對此有疑惑。唐巧已經(jīng)寫過一篇對block很有研究的文章城看,大家可以去看看(本文會(huì)部分引用巧哥文中出現(xiàn)的圖和代碼)匈棘。在巧哥的基礎(chǔ)上,我補(bǔ)充一些block相關(guān)的知識(shí)點(diǎn)和代碼析命,并且概括并修正一些觀點(diǎn)。

1.block是什么逃默?block是對象嗎鹃愤?

2.block分為哪幾種?__blcok關(guān)鍵字的作用完域?

3.block在ARC和MRC下的區(qū)別软吐?

4.block的生命周期?

5.block對于以參數(shù)形式傳進(jìn)來的對象吟税,會(huì)不會(huì)強(qiáng)引用凹耙?姿现?

block是什么?block是對象嗎肖抱?
先介紹一下什么是閉包备典。在 wikipedia 上,閉包的定義是:

In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.

翻譯過來意述,閉包是一個(gè)函數(shù)(或指向函數(shù)的指針)提佣,再加上該函數(shù)執(zhí)行的外部的上下文變量(有時(shí)候也稱作自由變量)。

block 實(shí)際上就是 Objective-C 語言對于閉包的實(shí)現(xiàn)荤崇。

block是不是對象拌屏?答案顯而易見:是的。

下圖是block的數(shù)據(jù)結(jié)構(gòu)定義术荤,顯而易見倚喂,在Block_layout里,我們看到了isa指針瓣戚,這里我們不具體對isa指針展開端圈,也不對block具體數(shù)據(jù)結(jié)構(gòu)展開,想了解詳細(xì)可以看唐巧的文章带兜。

回到上文枫笛,為什么說block是對象呢,原因就在于isa指針刚照。那么這個(gè)isa指針是何物呢刑巧?

所有對象的都有isa 指針,用于實(shí)現(xiàn)對象相關(guān)的功能无畔。

看到這啊楚,你應(yīng)該明白,block其實(shí)就是objc對于閉包的對象實(shí)現(xiàn)浑彰。


block的數(shù)據(jù)結(jié)構(gòu)

block分為哪幾種恭理?__blcok關(guān)鍵字的作用?

分為三種郭变,即NSConcreteGlobalBlock颜价、NSConcreteStackBlock、NSConcreteMallocBlock诉濒。

詳細(xì)剖析這三種block周伦,首先是NSConcreteGlobalBlock:

簡單地講,如果一個(gè)block中沒有引用外部變量未荒,就是NSConcreteGlobalBlock专挪。

如下圖所示:


NSConcreteGlobalBlock

需要注意的是,NSConcreteGlobalBlock是全局的block,在編譯期間就已經(jīng)決定了寨腔,如同宏一樣速侈。

什么是NSConcreteStackBlock呢:

可以這么理解,NSConcreteStackBlock就是引用了外部變量的block迫卢,上代碼:


NSConcreteStackBlock

NSConcreteStackBlock不會(huì)持有外部對象
從打印的日志可以看出倚搬,引用計(jì)數(shù)始終沒變。

NSConcreteMallocBlock:

看似最為神秘的NSConcreteMallocBlock其實(shí)就是一個(gè)block被copy時(shí)靖避,將生成NSConcreteMallocBlock(block沒有retain)潭枣。怎么樣,是不是很簡單


NSConcreteMallocBlock

NSConcreteMallocBlock
需要注意的是幻捏,NSConcreteMallocBlock會(huì)持有外部對象盆犁!


NSConcreteMallocBlock會(huì)持有外部對象

通過調(diào)用Block_copy()方法或者直接向他發(fā)送OC的copy消息完成。這就是所謂的Block_copy()篡九。

看到了吧谐岁,只要這個(gè)NSConcreteMallocBlock存在,內(nèi)部對象的引用計(jì)數(shù)就會(huì)+1榛臼。

Block_copy()

首先我們來看Block.h伊佃。其中有下面的定義:

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))

void *_Block_copy(const void *arg);

所以Block_copy是一個(gè)宏,它將傳入的參數(shù)轉(zhuǎn)換為一個(gè)const void *然后傳遞給_Block_copy()方法沛善。_Block_copy()的實(shí)現(xiàn)在runtime.c

void *_Block_copy(const void *arg) {
    return _Block_copy_internal(arg, WANTS_ONE);
}

所以也就是調(diào)用_Block_copy_internal方法航揉,傳入block自己和WANTS_ONE。為了明白這什么意思金刁,我們需要看一下實(shí)現(xiàn)代碼帅涂。也在runtime.c。下面是方法的實(shí)現(xiàn)尤蛮,已經(jīng)刪掉不想干的部分(主要是垃圾收集的部分):

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;

    // 1
    if (!arg) return NULL;

    // 2
    aBlock = (struct Block_layout *)arg;

    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }

    // 4
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;

    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first

    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;

    // 8
    result->isa = _NSConcreteMallocBlock;

    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }

    return result;
}

主要做了以下工作:

  1. 如果傳入?yún)?shù)是NULL就直接返回NULL媳友。防止傳入一個(gè)NULL的Block。

  2. 將參數(shù)轉(zhuǎn)換為一個(gè)struct Block_layout類型的指針产捞。你也許還記得第一篇文章中提到它醇锚。它就是block內(nèi)部一個(gè)包含了實(shí)現(xiàn)函數(shù)和一些元數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。

  3. 如果block的flags字段包含BLOCK_NEEDS_FREE坯临,那么這是一個(gè)堆block(稍后你就明白)焊唬。這里只需要增加引用計(jì)數(shù)然后返回原blcok。

  4. 如果這是一個(gè)全局block(回看第一篇文章)看靠,那么不需要做任何事求晶,直接返回原block。因?yàn)槿謆lock是一個(gè)單例衷笋。

  5. 如果走到這里,那么這一定是一個(gè)棧上分配的block。那樣的話辟宗,block需要拷貝到堆上爵赵。這才是有趣的部分。第一步泊脐,調(diào)用malloc()創(chuàng)建一塊特定的內(nèi)存空幻。如果創(chuàng)建失敗,返回NULL容客;否則秕铛,繼續(xù)。

  6. 調(diào)用memmove()方法將當(dāng)前棧上分配的block按位拷貝到我們剛剛創(chuàng)建的堆內(nèi)存上缩挑。這樣可以保證所有的元數(shù)據(jù)都拷貝過來但两,比如descriptor。

  7. 接下來供置,更新標(biāo)志位谨湘。第一行確保引用計(jì)數(shù)為0。注釋表明這行其實(shí)不需要——大概這個(gè)時(shí)候引用計(jì)數(shù)已經(jīng)是0了芥丧。我猜保留這行是因?yàn)橐郧坝袀€(gè)bug導(dǎo)致這里的引用計(jì)數(shù)不是0(所以說runtime的代碼也會(huì)偷懶)紧阔。下一行設(shè)置了BLOCK_NEEDS_FREE標(biāo)志位,表明這是一個(gè)堆block续担,一旦引用計(jì)數(shù)減為0擅耽,它所占用的內(nèi)存將被釋放。|1操作設(shè)置block的引用計(jì)數(shù)為1物遇。

  8. block的isa指針被設(shè)置為_NSConcreteMallocBlock乖仇,說明這是個(gè)堆block。

  9. 最后挎挖,如果block有一個(gè)拷貝輔助函數(shù)这敬,那么它將被調(diào)用。必要的時(shí)候編譯器會(huì)生成拷貝輔助函數(shù)蕉朵。比如一個(gè)捕獲了對象的block就需要崔涂。那么拷貝輔助函數(shù)將持有被捕獲的對象。

哈哈始衅,已經(jīng)十分清晰了±渎欤現(xiàn)在你知道拷貝一個(gè)block到底發(fā)生了什么事!但那只是圖片展示的一半內(nèi)容汛闸,釋放一個(gè)block又會(huì)怎么樣呢蝙茶?

Block_release()

Block_copy()圖的另一半是Block_release()。實(shí)際上這又是一個(gè)宏:

#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

Block_copy()一樣诸老,Block_release()也是轉(zhuǎn)換傳入的參數(shù)然后調(diào)用一個(gè)方法隆夯。這一定程度上解放了程序員的雙手,他們不用自己做轉(zhuǎn)換。

我們來看看_Block_release()的源碼(簡明起見蹄衷,重新整理了代碼順序忧额,并刪除了垃圾回收相關(guān)的代碼):

    // 1
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;

    // 2
    int32_t newCount;
    newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;

    // 3
    if (newCount > 0) return;

    // 4
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
        _Block_deallocator(aBlock);
    }

    // 5
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        ;
    }

    // 6
    else {
        printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
    }
}

這段代碼做了這些事:

  1. 首先,參數(shù)被轉(zhuǎn)換為一個(gè)指向struct Block_layout的指針愧口。如果傳入NULL睦番,直接返回。

  2. 標(biāo)志位部分表示引用計(jì)數(shù)減1(之前Block_copy()中標(biāo)志位操作代表的是引用計(jì)數(shù)置為1)耍属。

  3. 如果新的引用計(jì)數(shù)值大于0托嚣,說明有其他東西在引用block,所以block不應(yīng)該被釋放厚骗。

  4. 否則示启,如果標(biāo)志位包含BLOCK_NEEDS_FREE,那么這是一個(gè)堆block而且引用計(jì)數(shù)為0溯捆,應(yīng)該被釋放丑搔。首先block的處理輔助函數(shù)(dispose helper)被調(diào)用,它是拷貝輔助函數(shù)(copy helper)的反義詞提揍,執(zhí)行相反的操作啤月,比如釋放被捕獲的對象。最后調(diào)用_Block_deallocator方法釋放block劳跃。如果你查找runtime.c你就會(huì)發(fā)現(xiàn)這個(gè)方法最后就是一個(gè)free的函數(shù)指針谎仲,釋放malloc分配的內(nèi)存。

  5. 如果到這一步且lock是全局的刨仑,什么也不做郑诺。

  6. 如果到這一步,一定是發(fā)生了未知狀況杉武,因?yàn)橐粋€(gè)棧block試圖在這里釋放辙诞,輸出一行警告。實(shí)際上轻抱,你應(yīng)該永遠(yuǎn)不會(huì)走到這一步飞涂。

下面來說說__block這個(gè)關(guān)鍵字:

先上一個(gè)例子,你們很快就會(huì)明白了

__block example1

沒錯(cuò)祈搜,前文說過较店,block引用外部是以捕獲的形式來捕捉的,而沒有聲明__block容燕,則會(huì)將外部變量copy進(jìn)block梁呈,若用了__block,則是復(fù)制其引用地址來實(shí)現(xiàn)訪問蘸秘。這就是為什么聲明了__block官卡,在block內(nèi)部改變就會(huì)對外有影響的原因了蝗茁。

注意!味抖!這里需要知道的是评甜,在MRC環(huán)境下,如果沒有用__block仔涩,會(huì)對外部對象采用copy的操作,而用了__block則不會(huì)用copy的操作粘舟。

上代碼:


__block example2

哈哈哈熔脂,怎么樣,所以從更底層的角度來說柑肴,在MRC環(huán)境下霞揉,__block根本不會(huì)對指針?biāo)赶虻膶ο髨?zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制晰骑。而這一點(diǎn)往往是很多新手&老手所不知道的适秩!

而在ARC環(huán)境下,對于聲明為__block的外部對象硕舆,在block內(nèi)部會(huì)進(jìn)行retain秽荞,以至于在block環(huán)境內(nèi)能安全的引用外部對象,所以要謹(jǐn)防循環(huán)引用的問題抚官!

block在ARC和MRC下的區(qū)別扬跋?
首先要指正下巧哥博客的觀點(diǎn):

在 ARC 開啟的情況下,將只會(huì)有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block凌节。

在上面介紹NSConcreteStackBlock的時(shí)候钦听,是在ARC環(huán)境下跑的,而打印出來的日志明確的顯示出倍奢,當(dāng)時(shí)的block類型為NSConcreteStackBlock朴上。

而實(shí)際上,為什么大家普遍會(huì)認(rèn)為ARC下不存在NSConcreteStackBlock呢卒煞?

這是因?yàn)楸旧砦覀兂3lock賦值給變量痪宰,而ARC下默認(rèn)的賦值操作是strong的,到了block身上自然就成了copy跷坝,所以常常打印出來的block就是NSConcreteMallocBlock了酵镜。

so,在ARC下柴钻,大部分的應(yīng)用場景下淮韭,幾乎可以說是全部都為NSConcreteMallocBlock或者是NSConcreteGlobalBlock。那么問題來了贴届,我們知道NSConcreteMallocBlock是會(huì)持有外部變量的靠粪,而此時(shí)如果它所持有的外部變量正好又持有它蜡吧,就會(huì)產(chǎn)生循環(huán)引用的問題。

讓我們來聊聊block的生命周期占键!

block的生命周期昔善?
談到block生命周期,其實(shí)這是一個(gè)非常嚴(yán)肅的話題畔乙,雖然block簡單易用君仆,老少皆宜,但是一旦使用不慎容易造成“強(qiáng)擼灰飛煙滅”的后果(內(nèi)存泄露)牲距。

ps:接下來的例子都用ARC來展示了

首先展示:


循環(huán)引用

不用看了返咱,這個(gè)object永遠(yuǎn)也不會(huì)被釋放,這是一個(gè)很典型的循環(huán)引用情形牍鞠。object持有了block(讀者可以想象此處為何為NSConcreteMallocBlock咖摹,提示:在ARC環(huán)境下),而block又持有了object难述,于是造成死鎖萤晴,object再也不會(huì)被釋放了。此時(shí)機(jī)智的編譯器給了你warning胁后,但是在很多復(fù)雜的情況下店读,編譯器并不能識(shí)別出循環(huán)引用的場景。而此時(shí)你就需要注意了择同!

那么两入,我是如何來處理block的生命周期相關(guān)問題的呢,首先前文提到敲才,block是一個(gè)對象裹纳,既然是一個(gè)對象,它必然有著和對象一樣的生命周期即如果沒有被引用就會(huì)被釋放紧武。

所以block的生命周期歸結(jié)起來很簡單剃氧,只要看持有block的對象是不是也被block持有,如果沒有持有阻星,就不用擔(dān)心循環(huán)引用問題了朋鞍。

但是像上面的情況,如果產(chǎn)生相互持有的情況該腫么辦妥箕!

你可以用__weak(ARC)或__block(MRC)來解決:


weak解決循環(huán)引用

看滥酥,現(xiàn)在就可以愉快的釋放了。

block對于以參數(shù)形式傳進(jìn)來的對象畦幢,會(huì)不會(huì)強(qiáng)引用坎吻?

唉,不知不覺已經(jīng)快半夜2點(diǎn)了宇葱,對于這部分的話瘦真,其實(shí)也是閑著蛋疼在想這個(gè)問題刊头。

其實(shí)block與函數(shù)和方法一樣,對于傳進(jìn)來的參數(shù)诸尽,并不會(huì)持有

證據(jù)如下


block不會(huì)持有參數(shù)對象

總結(jié):
到這里原杂,對于block的介紹結(jié)束了。實(shí)際運(yùn)用中其實(shí)不用太關(guān)心這些原理的您机,只需要正確掌握好block的生命周期就可以靈活地運(yùn)用block了穿肄。但是對于一個(gè)資深開發(fā)者來說,block的深層次掌握還是必須的际看!

一被碗、整體介紹

  • 定義:C語言的匿名函數(shù),??提前準(zhǔn)備一段代碼,在需要的時(shí)候調(diào)用。
  • 底層:是一個(gè)指針結(jié)構(gòu)體,在終端下可以通過clang -rewrite-objc 文件名(會(huì)在當(dāng)前目錄生成.cpp文件)指令看看c++代碼仿村,它的實(shí)現(xiàn)底層。

注意:容易造成循環(huán)引用,經(jīng)常是在 block 里面使用了 self.,然后形成強(qiáng)引用,我們打斷循 環(huán)鏈即可,如果 MRC 下用__block,ARC 下用__weak(下文會(huì)有詳細(xì)介紹)兴喂。

二蔼囊、內(nèi)存位置(ARC情況)

block塊的存儲(chǔ)位置(block塊入口地址):可能存放在2個(gè)地方:代碼區(qū)(NSConcreteGlobalBlock)、堆區(qū)(NSConcreteMallocBlock)衣迷,程序分5個(gè)區(qū)畏鼓,還有常量區(qū)、全局區(qū)和棧區(qū)壶谒,對于MRC情況下代碼還可能存在棧區(qū)(NSConcreteStackBlock)云矫。關(guān)于內(nèi)存分區(qū)詳細(xì)參考:http://www.reibang.com/p/d85a5e56c505

  • 情況1:代碼區(qū)

不訪問處于棧區(qū)的變量(例如局部變量),且不訪問處于堆區(qū)的變量(例如alloc創(chuàng)建的對象)汗菜。也就是說訪問全局變量也可以让禀。

/**
  沒有訪問任何變量 
*/
int main(int argc, char * argv[]) { void (^block)(void) = ^{
        NSLog(@"===");
    };
    block();
}
/**
  訪問了全局(靜態(tài))變量 
*/
int  iVar = 10; int main(int argc, char * argv[]) { void (^block)(void) = ^{
        NSLog(@"===%d",iVar);
    };
    block();
}
  • 情況2:堆區(qū)

如果訪問了處于棧區(qū)的變量(例如局部變量),或處于堆區(qū)的變量(例如alloc創(chuàng)建的對象)陨界。都會(huì)存放在堆區(qū)巡揍。(實(shí)際是放在棧區(qū),然后ARC情況下自動(dòng)又拷貝到堆區(qū))

/**
  訪問局部變量 
*/
int main(int argc, char * argv[]) { int iVar = 10; void (^block)(void) = ^{
        NSLog(@"===%d",iVar);
    };
    block();
}

總結(jié)下:

  • 代碼區(qū):不訪問處于棧區(qū)的變量(例如局部變量)菌瘪,且不訪問處于堆區(qū)的變量(例如alloc創(chuàng)建的對象)腮敌。也就是說訪問全局變量(靜態(tài)變量)也可以,或者是什么變量都不訪問
  • 堆區(qū):如果訪問了處于棧區(qū)的變量(例如局部變量)俏扩,或處于堆區(qū)的變量(例如alloc創(chuàng)建的對象)糜工,即便也訪問了全局變量

三、注意事項(xiàng)

1 block為空

代碼存放在堆區(qū)時(shí)录淡,就需要特別注意捌木,因?yàn)槎褏^(qū)不像代碼區(qū)不變化,堆區(qū)是不斷變化的(不斷創(chuàng)建銷毀)赁咙。因此代碼有可能會(huì)被銷毀(當(dāng)沒有強(qiáng)指針指向時(shí))钮莲,如果這時(shí)再訪問此段代碼則會(huì)程序崩潰免钻。因此,對于這種情況崔拥,我們在定義一個(gè)block屬性時(shí)應(yīng)指定為strong极舔,或copy:

  • @property (nonatomic, strong) void (myBlock)(void); // 這樣就有強(qiáng)指針指向它
  • @property (nonatomic, copy) void (myBlock)(void); // 并不會(huì)在堆區(qū)copy一份,原因見 四

而對于block代碼存在代碼區(qū)链瓦,使用strong期贫,copy(不會(huì)復(fù)制一份到堆區(qū))也可以。因此定義block時(shí)最好指定為strong(推薦)或copy连锯。我們在使用時(shí)最后判斷下block是否為空起愈,例如:

- (void)blockTest { // 如果為空則返回
    if (!block) {
        NSLog(@"block is nil"); return;
    }
    block();

}

2 當(dāng)不在使用指向block的指針時(shí),將其置空

當(dāng)有類對象的成員變量pBlock指向block時(shí)贴膘,一方面是調(diào)用方卖子,調(diào)用pBlock調(diào)用完成后,應(yīng)將pBlock置為nil;另一方面是被調(diào)用方即block函數(shù)內(nèi)部使用到self時(shí)要__weak聲明刑峡。其實(shí)__weak聲明有很多注意事項(xiàng)洋闽,下面是一個(gè)經(jīng)典例子(是正確的寫法):

// 弱聲明,防止block強(qiáng)引用self突梦,造成循環(huán)引用
    __weak __typeof(self) weakSelf = self;
    self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"blockTest" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { // 多線程情況下(假設(shè)發(fā)出通知的代碼在另一線程下)诫舅,strong強(qiáng)引用防止后面調(diào)用strongSelf時(shí):前面的strongSelf正常,后面的strongSelf已在其它線程被釋放宫患,造成很奇怪的結(jié)果刊懈,雖然這種情況很少發(fā)生
        __strong __typeof(self) strongSelf = weakSelf; //if (strongSelf == nil) { // return; //} // 下面再對strongSelf進(jìn)行訪問 // 防止block為空
        if (!strongSelf.block) { return;
        }
        strongSelf.block(); // 如果不用應(yīng)置空,養(yǎng)成好習(xí)慣
        strongSelf.block = nil;
        NSLog(@"%@",strongSelf);
    }];
  • 1)我們都知道在使用通知中心時(shí)娃闲,應(yīng)在dealloc函數(shù)中釋放通知虚汛,如果上面沒有使用__weak聲明,那么:通知中心持有self.observer畜吊,observer又強(qiáng)引用 usingBlock泽疆,usingBlock又強(qiáng)引用self,self就不會(huì)被釋放玲献,那么dealloc就不會(huì)被調(diào)用(即使在dealloc中寫了[[NSNotificationCenter defaultCenter] removeObserver:self.observer]也不會(huì)調(diào)用殉疼,因?yàn)閐ealloc沒有被調(diào)用),就造成內(nèi)存泄露捌年;

  • 2)另外瓢娜,我們在第5行看到又使用了__strong聲明,是否瞬間凌亂礼预?下面給出解釋:在多線程情況下眠砾,有可能在usingBlock調(diào)用時(shí),執(zhí)行if (!strongSelf.block)時(shí)strongSelf還沒有釋放托酸,而執(zhí)行到strongSelf.block()的時(shí)候strongSelf就被釋放(現(xiàn)在沒有強(qiáng)引用了褒颈,又開始擔(dān)心self被釋放柒巫,真是操碎了心。谷丸。堡掏。),造成調(diào)用失斉偬邸(最大的問題是不統(tǒng)一泉唁,造成不可預(yù)知的錯(cuò)誤。用__strong操作后保證要么都訪問成功揩慕,要么都訪問失敗或者判斷為空后直接return退出)亭畜。

而使用了__strong聲明后:

  • 如果執(zhí)行usingBlock時(shí)self已經(jīng)被釋放則后面的strongSelf均為nil,因?yàn)閷eakSelf引用計(jì)數(shù)為0再retain一次也不會(huì)有變化迎卤;

  • 如果執(zhí)行usingBlock時(shí)self沒有釋放拴鸵,則strongSelf會(huì)使self引用計(jì)數(shù)+1,那么self在其它線程被release -1也不會(huì)有影響蜗搔,只有到usingBlock全部執(zhí)行完畢后宝踪,strongSelf釋放,然后self引用計(jì)數(shù)-1碍扔,self才會(huì)釋放(weak–strong dance)。

上面的例子是通知中心可能造成的內(nèi)存泄露秕重,而使用block還經(jīng)常出現(xiàn)循環(huán)引用不同,如下:

3 最常出現(xiàn)的循環(huán)引用

@interface BlockViewController ()
@property (nonatomic, strong) void (^block)(void);
@property (nonatomic, copy) NSString *str; @end

@implementation BlockViewController - (void)viewDidLoad {
    [super viewDidLoad];
    self.block = ^{
        self.str = @"123";
    };
} 
@end

上面的代碼,self.block強(qiáng)引用block溶耘,而block中又使用了self.str二拐,所以block強(qiáng)引用self,造成強(qiáng)引用凳兵,解決方法使用2中所說即可百新。

關(guān)于引用計(jì)數(shù)(http://www.reibang.com/p/28b074919df3)

四、關(guān)于捕獲變量

block里面捕獲的變量庐扫,都是副本饭望。看下面一段代碼

int val = 10; void (^block)(void) = ^{
    NSLog(@"val = %d",val); // val = 1; //不允許
};
val = 5;
block();

它的打印結(jié)果是10形庭,而不是5铅辞。

上面代碼中val = 1是不允許的,如果想實(shí)現(xiàn)寫操作萨醒,可以使用__block來修飾val斟珊,之后val會(huì)被拷貝(移動(dòng),便于理解)到堆上,之后無論是在block里面還是在val之前所處的作用域富纸,訪問的都是出于堆區(qū)的val囤踩。

為什么非要__block呢旨椒,因?yàn)槿绻挥胈_block,如果出了val所在的“}”,那么val就會(huì)被釋放堵漱,而block的調(diào)用時(shí)機(jī)是不定的综慎,可能調(diào)用時(shí)機(jī)已經(jīng)超出了block和val本身所處的"{}",再訪問val就可能壞地址訪問(val已經(jīng)被釋放)怔锌。所以這樣做是合理的寥粹。

但是在block里面,類似self.name = xxx,self->_val埃元,卻是很常見的涝涤,self也沒有用__block修飾呀!你是否有過這樣的迷惑岛杀?

self.name = xxx——>[self setName:xxx];是發(fā)送消息阔拳,函數(shù)調(diào)用,很好理解类嗤。那self->_val呢糊肠?因?yàn)開val本身是處于堆區(qū)的。

Block如果沒有引用外部變量
保存在全局區(qū)(MRC/ARC一樣)
Block如果引用外部變量
ARC保存在 堆區(qū)遗锣; MRC保存在 棧區(qū)必須用copy修飾block货裹;

1 簡單理解:block對應(yīng)實(shí)例化一個(gè)結(jié)構(gòu)體,里面成員有block里用到的變量精偿,準(zhǔn)備了數(shù)據(jù)弧圆;調(diào)用block時(shí),執(zhí)行對應(yīng)函數(shù)笔咽,使用這些數(shù)據(jù)搔预;
2 簡單理解:拷貝進(jìn)來時(shí),沒有__block修飾的直接字節(jié)拷貝進(jìn)來叶组,有__block修飾的拯田,為了引地址進(jìn)來,又定義一個(gè)結(jié)構(gòu)體包了一層甩十;
3 block拷貝時(shí)船庇,棧到堆上真拷貝,堆到堆上只是引用計(jì)數(shù)侣监;
4 block拷貝時(shí)溢十,block使用的變量同樣拷貝,拷貝的原則和上面說的有無__block修飾時(shí)的兩種情況分別一致达吞;
5 block里使用外部OC對象時(shí)张弛,本身就相當(dāng)于一個(gè)賦值拷貝,ARC下就會(huì)給對象加引用次數(shù),所以才有了循環(huán)引用的事吞鸭;
6 ARC下寺董,等號賦值,retain刻剥、strong遮咖、copy都會(huì)觸發(fā)block拷貝到堆上;非ARC有些不一樣造虏;
7 ARC下御吞,assign不會(huì)把棧上block拷貝到堆上,retain漓藕、strong陶珠、copy都會(huì);非ARC有些不一樣享钞;
8 __block修飾的變量被拷貝到堆上后揍诽,__forwarding指向的包裝結(jié)構(gòu)體都是堆上那個(gè),這樣無論操作棧上還是堆上的包裝結(jié)構(gòu)體栗竖,實(shí)際改變值都會(huì)是堆上那個(gè)里的暑脆;
9 一些有block參數(shù)的系統(tǒng)API都有說明,當(dāng)把棧上block作為參數(shù)給這些API時(shí)會(huì)不會(huì)拷貝block狐肢,一般都會(huì)添吗,所以才沒問題;

鏈接:
http://www.reibang.com/p/e03292674e60
http://www.reibang.com/p/93f96c6aa530
http://www.cnblogs.com/mddblog/p/4754190.html
https://blog.csdn.net/demondev/article/details/53199785

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末份名,一起剝皮案震驚了整個(gè)濱河市根资,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌同窘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件部脚,死亡現(xiàn)場離奇詭異想邦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)委刘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門丧没,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锡移,你說我怎么就攤上這事呕童。” “怎么了淆珊?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵夺饲,是天一觀的道長。 經(jīng)常有香客問我,道長往声,這世上最難降的妖魔是什么擂找? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮浩销,結(jié)果婚禮上贯涎,老公的妹妹穿的比我還像新娘。我一直安慰自己慢洋,他們只是感情好塘雳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著普筹,像睡著了一般败明。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斑芜,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天肩刃,我揣著相機(jī)與錄音,去河邊找鬼杏头。 笑死盈包,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的醇王。 我是一名探鬼主播呢燥,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寓娩!你這毒婦竟也來了叛氨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤棘伴,失蹤者是張志新(化名)和其女友劉穎寞埠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焊夸,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仁连,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了阱穗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饭冬。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖揪阶,靈堂內(nèi)的尸體忽然破棺而出昌抠,到底是詐尸還是另有隱情,我是刑警寧澤鲁僚,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布炊苫,位于F島的核電站裁厅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏劝评。R本人自食惡果不足惜姐直,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒋畜。 院中可真熱鬧声畏,春花似錦、人聲如沸姻成。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽科展。三九已至均牢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間才睹,已是汗流浹背徘跪。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琅攘,地道東北人垮庐。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像坞琴,于是被迫代替她去往敵國和親哨查。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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

  • 轉(zhuǎn)自李峰峰博客 一剧辐、概述 閉包 = 一個(gè)函數(shù)「或指向函數(shù)的指針」+ 該函數(shù)執(zhí)行的外部的上下文變量「也就是自由變量」...
    Joshua520閱讀 965評論 0 0
  • 前言 Blocks是C語言的擴(kuò)充功能寒亥,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,757評論 0 23
  • 《Objective-C高級編程》這本書就講了三個(gè)東西:自動(dòng)引用計(jì)數(shù)、block荧关、GCD溉奕,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 9,795評論 10 69
  • 1、概述 閉包 = 一個(gè)函數(shù)「或指向函數(shù)的指針」+ 該函數(shù)執(zhí)行的外部的上下文變量「也就是自由變量」忍啤;Block 是...
    DeerRun閱讀 657評論 0 0
  • block的定義加勤,調(diào)用等就不介紹了,自行去查資料檀轨。 本文介紹內(nèi)容: 1.block的底層數(shù)據(jù)結(jié)構(gòu)2.block的類...
    倫倫子_f7b3閱讀 1,100評論 0 0