block詳解

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

1.block是什么?block是對(duì)象嗎?

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

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

4.block的生命周期?

5.block對(duì)于以參數(shù)形式傳進(jìn)來(lái)的對(duì)象,會(huì)不會(huì)強(qiáng)引用??

block是什么?block是對(duì)象嗎?
先介紹一下什么是閉包。在 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.

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

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

block是不是對(duì)象?答案顯而易見(jiàn):是的。

下圖是block的數(shù)據(jù)結(jié)構(gòu)定義,顯而易見(jiàn),在Block_layout里,我們看到了isa指針,這里我們不具體對(duì)isa指針展開(kāi),也不對(duì)block具體數(shù)據(jù)結(jié)構(gòu)展開(kāi),想了解詳細(xì)可以看唐巧的文章。

回到上文,為什么說(shuō)block是對(duì)象呢,原因就在于isa指針。那么這個(gè)isa指針是何物呢?

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

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


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

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

分為三種,即NSConcreteGlobalBlock、NSConcreteStackBlock、NSConcreteMallocBlock。

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

簡(jiǎn)單地講,如果一個(gè)block中沒(méi)有引用外部變量,就是NSConcreteGlobalBlock。

如下圖所示:


NSConcreteGlobalBlock

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

什么是NSConcreteStackBlock呢:

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


NSConcreteStackBlock

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

NSConcreteMallocBlock:

看似最為神秘的NSConcreteMallocBlock其實(shí)就是一個(gè)block被copy時(shí),將生成NSConcreteMallocBlock(block沒(méi)有retain)。怎么樣,是不是很簡(jiǎn)單


NSConcreteMallocBlock

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


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

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

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

Block_copy()

首先我們來(lái)看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ù)都拷貝過(guò)來(lái),比如descriptor。

  7. 接下來(lái),更新標(biāo)志位。第一行確保引用計(jì)數(shù)為0。注釋表明這行其實(shí)不需要——大概這個(gè)時(shí)候引用計(jì)數(shù)已經(jīng)是0了。我猜保留這行是因?yàn)橐郧坝袀€(gè)bug導(dǎo)致這里的引用計(jì)數(shù)不是0(所以說(shuō)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,說(shuō)明這是個(gè)堆block。

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

哈哈,已經(jīng)十分清晰了?,F(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)換。

我們來(lái)看看_Block_release()的源碼(簡(jiǎn)明起見(jiàn),重新整理了代碼順序,并刪除了垃圾回收相關(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,說(shuō)明有其他東西在引用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í)行相反的操作,比如釋放被捕獲的對(duì)象。最后調(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ì)走到這一步。

下面來(lái)說(shuō)說(shuō)__block這個(gè)關(guān)鍵字:

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

__block example1

沒(méi)錯(cuò),前文說(shuō)過(guò),block引用外部是以捕獲的形式來(lái)捕捉的,而沒(méi)有聲明__block,則會(huì)將外部變量copy進(jìn)block,若用了__block,則是復(fù)制其引用地址來(lái)實(shí)現(xiàn)訪問(wèn)。這就是為什么聲明了__block,在block內(nèi)部改變就會(huì)對(duì)外有影響的原因了。

注意??!這里需要知道的是,在MRC環(huán)境下,如果沒(méi)有用__block,會(huì)對(duì)外部對(duì)象采用copy的操作,而用了__block則不會(huì)用copy的操作。

上代碼:


__block example2

哈哈哈,怎么樣,所以從更底層的角度來(lái)說(shuō),在MRC環(huán)境下,__block根本不會(huì)對(duì)指針?biāo)赶虻膶?duì)象執(zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制。而這一點(diǎn)往往是很多新手&老手所不知道的!

而在ARC環(huán)境下,對(duì)于聲明為_(kāi)_block的外部對(duì)象,在block內(nèi)部會(huì)進(jìn)行retain,以至于在block環(huán)境內(nèi)能安全的引用外部對(duì)象,所以要謹(jǐn)防循環(huán)引用的問(wèn)題!

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

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

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

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

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

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

讓我們來(lái)聊聊block的生命周期!

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

ps:接下來(lái)的例子都用ARC來(lái)展示了

首先展示:


循環(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)引用的場(chǎng)景。而此時(shí)你就需要注意了!

那么,我是如何來(lái)處理block的生命周期相關(guān)問(wèn)題的呢,首先前文提到,block是一個(gè)對(duì)象,既然是一個(gè)對(duì)象,它必然有著和對(duì)象一樣的生命周期即如果沒(méi)有被引用就會(huì)被釋放。

所以block的生命周期歸結(jié)起來(lái)很簡(jiǎn)單,只要看持有block的對(duì)象是不是也被block持有,如果沒(méi)有持有,就不用擔(dān)心循環(huán)引用問(wèn)題了。

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

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


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

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

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

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

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

證據(jù)如下


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

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

一、整體介紹

  • 定義:C語(yǔ)言的匿名函數(shù),??提前準(zhǔn)備一段代碼,在需要的時(shí)候調(diào)用。
  • 底層:是一個(gè)指針結(jié)構(gòu)體,在終端下可以通過(guò)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ū),對(duì)于MRC情況下代碼還可能存在棧區(qū)(NSConcreteStackBlock)。關(guān)于內(nèi)存分區(qū)詳細(xì)參考:http://www.itdecent.cn/p/d85a5e56c505

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

不訪問(wèn)處于棧區(qū)的變量(例如局部變量),且不訪問(wèn)處于堆區(qū)的變量(例如alloc創(chuàng)建的對(duì)象)。也就是說(shuō)訪問(wèn)全局變量也可以。

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

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

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

總結(jié)下:

  • 代碼區(qū):不訪問(wèn)處于棧區(qū)的變量(例如局部變量),且不訪問(wèn)處于堆區(qū)的變量(例如alloc創(chuàng)建的對(duì)象)。也就是說(shuō)訪問(wèn)全局變量(靜態(tài)變量)也可以,或者是什么變量都不訪問(wèn)
  • 堆區(qū):如果訪問(wèn)了處于棧區(qū)的變量(例如局部變量),或處于堆區(qū)的變量(例如alloc創(chuàng)建的對(duì)象),即便也訪問(wèn)了全局變量

三、注意事項(xiàng)

1 block為空

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

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

而對(duì)于block代碼存在代碼區(qū),使用strong,copy(不會(huì)復(fù)制一份到堆區(qū))也可以。因此定義block時(shí)最好指定為strong(推薦)或copy。我們?cè)谑褂脮r(shí)最后判斷下block是否為空,例如:

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

}

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

當(dāng)有類對(duì)象的成員變量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; //} // 下面再對(duì)strongSelf進(jìn)行訪問(wèn) // 防止block為空
        if (!strongSelf.block) { return;
        }
        strongSelf.block(); // 如果不用應(yīng)置空,養(yǎng)成好習(xí)慣
        strongSelf.block = nil;
        NSLog(@"%@",strongSelf);
    }];
  • 1)我們都知道在使用通知中心時(shí),應(yīng)在dealloc函數(shù)中釋放通知,如果上面沒(méi)有使用__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沒(méi)有被調(diào)用),就造成內(nèi)存泄露;

  • 2)另外,我們?cè)诘?行看到又使用了__strong聲明,是否瞬間凌亂?下面給出解釋:在多線程情況下,有可能在usingBlock調(diào)用時(shí),執(zhí)行if (!strongSelf.block)時(shí)strongSelf還沒(méi)有釋放,而執(zhí)行到strongSelf.block()的時(shí)候strongSelf就被釋放(現(xiàn)在沒(méi)有強(qiáng)引用了,又開(kāi)始擔(dān)心self被釋放,真是操碎了心。。。),造成調(diào)用失?。ㄗ畲蟮膯?wèn)題是不統(tǒng)一,造成不可預(yù)知的錯(cuò)誤。用__strong操作后保證要么都訪問(wèn)成功,要么都訪問(wèn)失敗或者判斷為空后直接return退出)。

而使用了__strong聲明后:

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

  • 如果執(zhí)行usingBlock時(shí)self沒(méi)有釋放,則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中所說(shuō)即可。

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

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

block里面捕獲的變量,都是副本??聪旅嬉欢未a

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

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

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

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

但是在block里面,類似self.name = xxx,self->_val,卻是很常見(jiàn)的,self也沒(méi)有用__block修飾呀!你是否有過(guò)這樣的迷惑?

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

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

1 簡(jiǎn)單理解:block對(duì)應(yīng)實(shí)例化一個(gè)結(jié)構(gòu)體,里面成員有block里用到的變量,準(zhǔn)備了數(shù)據(jù);調(diào)用block時(shí),執(zhí)行對(duì)應(yīng)函數(shù),使用這些數(shù)據(jù);
2 簡(jiǎn)單理解:拷貝進(jìn)來(lái)時(shí),沒(méi)有__block修飾的直接字節(jié)拷貝進(jìn)來(lái),有__block修飾的,為了引地址進(jìn)來(lái),又定義一個(gè)結(jié)構(gòu)體包了一層;
3 block拷貝時(shí),棧到堆上真拷貝,堆到堆上只是引用計(jì)數(shù);
4 block拷貝時(shí),block使用的變量同樣拷貝,拷貝的原則和上面說(shuō)的有無(wú)__block修飾時(shí)的兩種情況分別一致;
5 block里使用外部OC對(duì)象時(shí),本身就相當(dāng)于一個(gè)賦值拷貝,ARC下就會(huì)給對(duì)象加引用次數(shù),所以才有了循環(huán)引用的事;
6 ARC下,等號(hào)賦值,retain、strong、copy都會(huì)觸發(fā)block拷貝到堆上;非ARC有些不一樣;
7 ARC下,assign不會(huì)把棧上block拷貝到堆上,retain、strong、copy都會(huì);非ARC有些不一樣;
8 __block修飾的變量被拷貝到堆上后,__forwarding指向的包裝結(jié)構(gòu)體都是堆上那個(gè),這樣無(wú)論操作棧上還是堆上的包裝結(jié)構(gòu)體,實(shí)際改變值都會(huì)是堆上那個(gè)里的;
9 一些有block參數(shù)的系統(tǒng)API都有說(shuō)明,當(dāng)把棧上block作為參數(shù)給這些API時(shí)會(huì)不會(huì)拷貝block,一般都會(huì),所以才沒(méi)問(wèn)題;

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容