Block 梳理與疑問(wèn)

Block 梳理與疑問(wèn)

時(shí)隔一年,再次讀 《Objective-C 高級(jí)編程》,看到 block 一章,這一次從頭至尾的跟著編譯了一次,理清楚了很多之前不理解的地方,但是也同時(shí)多出了許多疑問(wèn)。本文是在和學(xué)渣裙的朋友們分享以后的梳理筆記,有問(wèn)題歡迎指出,如果能解決最后的幾個(gè)小疑問(wèn),就更好了。

環(huán)境信息
macOS 10.12.1
Xcode 8.2.1
iOS 10.12

一個(gè)最基本的 block

Block 編譯后,有兩個(gè)最為重要的部分,impl 結(jié)構(gòu)體 與 desc 結(jié)構(gòu)體指針。我們從最為簡(jiǎn)單基礎(chǔ)的開(kāi)始:

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

    // 定義一個(gè)參數(shù)列表與返回值均為空的 block
    dispatch_block_t block = ^{
        // 僅輸出一句話
        NSLog(@"123");
    };
    // 調(diào)用
    block();
}
return 0;

}
使用 clang -rewrite-objc xxx.m 命令,編譯后(已刪除一些影響閱讀的字符,用 xxx 代替):

// block 結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl; // 實(shí)現(xiàn)
struct __main_block_desc_0* Desc; // 描述

// 在定義 block 時(shí),所調(diào)用的 block 初始化方法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; // block 的類型(之后會(huì)談到)
impl.Flags = flags;
impl.FuncPtr = fp; // block 實(shí)現(xiàn)編譯后的函數(shù)指針
Desc = desc; // 描述信息
}
};

// block 的描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block 所占的內(nèi)存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; // block 描述的初始化方法,可以看出這里的大小計(jì)算,僅僅是進(jìn)行了 sizeof

// block 實(shí)現(xiàn)編譯過(guò)后的函數(shù)
// 即在 block 初始化方法中,賦值給 impl.FuncPtr 的函數(shù)指針
// 參數(shù) cself 是 __main_block_impl_0 類型,即與 block 類型相同,其實(shí)這里的參數(shù),本身就是 block 自己
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 輸出
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_main_c44db5_mi_0);
}

// main 函數(shù)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

// block 的定義
// 可以看出,在 block 定義的時(shí)候,就調(diào)用了 __main_block_impl_0,即 block 的構(gòu)造方法
// 傳的參數(shù)分別為 __main_block_func_0,即 block 對(duì)應(yīng)的編譯后的實(shí)現(xiàn)函數(shù)
// __main_block_desc_0_DATA,即 block 描述
dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
                
// block 的調(diào)用
// 這里也可以看出,block 的調(diào)用即是調(diào)用了,初始化時(shí)拿到的 FuncPtr 函數(shù)指針
// FuncPtr 函數(shù)有一個(gè)參數(shù),即傳入的 block 自身
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}
return 0;
}
block 內(nèi)存結(jié)構(gòu)

通過(guò)編譯后得到的 block 結(jié)構(gòu)體,能大致看出,在沒(méi)有引用外部變量的 block 是這樣的:

struct __block_impl impl;
struct __main_block_desc_0 *Desc;
此時(shí)的內(nèi)存結(jié)構(gòu)如下:

isa 指針

在 block 調(diào)用構(gòu)造方法時(shí),編譯器已經(jīng)自動(dòng)給 isa 指針賦了初值。我們知道,isa 指針其實(shí)很形象,就稱作 is a,在 OC 中表達(dá)了對(duì)象是什么類型,類所屬哪個(gè)元類,其實(shí) block 也是對(duì)象,所以它的 isa 指針也是說(shuō)明它是什么的。如果直接打印 block,則可看到以下三種情況:

<NSStackBlock: 0x1000010c0>,存儲(chǔ)在棧上的 block
<NSMallocBlock: 0x1000010c0>,存儲(chǔ)在堆上的 block
<NSGlobalBlock: 0x1000010c0>,存儲(chǔ)在全局區(qū)的 block
但是你會(huì)發(fā)現(xiàn),如果直接打印上面我們所寫的 block,輸出的是 NSGlobalBlock 類型,而我們看到的編譯代碼,明明是 stack 的。這是因?yàn)?block 的存儲(chǔ)區(qū)域,與定義在什么位置、是否引用外部變量、是否作為范圍值、是被哪種類型的變量所接收等等情況相關(guān),這個(gè)會(huì)在下一小節(jié)談到。

引用外部變量的 block

之前介紹了一個(gè)空(并未引用變量)的 block,下面來(lái)看一個(gè)稍微復(fù)雜一點(diǎn)的:

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

    // 定義局部變量 a
    int a = 10;
    // 定義 block
    dispatch_block_t block = ^{
        // 輸出 a 變量
        NSLog(@"%d", a);
    };
    // 調(diào)用 block
    block();
}
return 0;

}
在學(xué)習(xí) block 的基礎(chǔ)知識(shí)時(shí),就知道,此時(shí)如果在 block 定義之后,去修改 a 的值,block 中的輸出依然不會(huì)改變,我們來(lái)看一下為什么。

編譯文件:

// 下面僅標(biāo)注了有變化的變量

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 在 block 結(jié)構(gòu)體中,多了一個(gè)名為 a 的變量

// block 構(gòu)造方法也多了一個(gè) _a 參數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

// 描述依然沒(méi)有變,size 是直接計(jì)算的 __main_block_impl_0
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 在 block 的實(shí)現(xiàn)函數(shù)中,訪問(wèn) block 結(jié)構(gòu)體中的 a 變量,并且編譯器在此還說(shuō)明了是 bound by copy,即值拷貝
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_0, a);
}
從編譯代碼可以看出,在 block 定義時(shí),就傳入了 block 內(nèi)部需要用到的 a 變量的值,而并不是引用,所以即使在 block 定義之后,a 變量怎么變,之前 block 所有的 a 的瞬時(shí)值,是沒(méi)有變化的。

此時(shí),block 的內(nèi)存結(jié)構(gòu)為:

多出了捕獲的變量 a 的存儲(chǔ)空間,并且,捕獲的變量會(huì)接在 Desc 內(nèi)存后面。

被 __block 修飾的外部變量

如果沒(méi)有 __block 修飾,除了在 block 定義之后,就不能拿到變量最新的值以外,我們還不能對(duì)變量進(jìn)行重新賦值(如果是堆上的內(nèi)存,就是改變地址,即 NSMutableArray 是可以 addObject 的,只是不能 array = @[])。那么,想要解決這兩個(gè)問(wèn)題,我們就需要引入 __block 修飾符:

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

    // 定義變量 a,并使用 __block 修飾
    __block int a = 10;
    dispatch_block_t block = ^{
        // 輸出 a
        NSLog(@"%d", a); // 輸出 100
        // 在 block 內(nèi)部對(duì) a 重新賦值
        a = 50;
    };
    // 在 block 定義后,對(duì) a 重新賦值
    a = 100;
    // 調(diào)用 block
    block();
    // 輸出 a
    NSLog(@"%d", a); // 輸出 50
}
return 0;

}
這一次,編譯后的代碼變得很復(fù)雜了:

// block 結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;

// 比起沒(méi)有被 __block 修飾的變量(編譯后,block 中是 int a),這里 block 卻不是簡(jiǎn)單的拿到 a 地址,即 int *a,而是一個(gè)類型為 __Block_byref_a_0 的結(jié)構(gòu)體指針
__Block_byref_a_0 *a; // by ref

// 構(gòu)造方法多出的參數(shù)也變成了,__Block_byref_a_0 結(jié)構(gòu)體指針,并且 a 的值是 a->__forwarding,這個(gè) __forwarding 指針的作用,會(huì)在之后介紹
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

// a 變量的結(jié)構(gòu)體定義
struct __Block_byref_a_0 {
void *__isa; // isa 指針
__Block_byref_a_0 *__forwarding; // 類型與 a 變量一模一樣的 __forwarding 結(jié)構(gòu)體指針
int __flags;
int __size;
int a; // a 真正的值
};

// block 描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;

// 這是比起以前,多出的兩個(gè)函數(shù)指針,一個(gè) copy,一個(gè) dispose
// 這也是 block 中尤為重要的兩個(gè)函數(shù)
// copy 負(fù)責(zé)將 block 復(fù)制到堆
// dispose 負(fù)責(zé)在 block 釋放時(shí),釋放 block 所持有的內(nèi)存
void (copy)(struct __main_block_impl_0, struct __main_block_impl_0);
void (
dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

// block 實(shí)現(xiàn)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 訪問(wèn)到 block 中的 a 結(jié)構(gòu)體指針變量
__Block_byref_a_0 *a = __cself->a; // bound by ref

// 輸出
// 可以看到,這里訪問(wèn)的 a 的值,是通過(guò) __forwarding 指針訪問(wèn)的,包括之后的賦值,也是用的 __forwarding
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xxx_0, (a->__forwarding->a));

// 將 a 的值重新賦為 50
(a->__forwarding->a) = 50;
}

// copy 函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {
// 使用 _Block_object_assign 函數(shù)進(jìn)行拷貝
_Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
}

// dispose 函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0src) {
_Block_object_dispose((void
)src->a, 8/BLOCK_FIELD_IS_BYREF/);
}

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

    // 初始化 a 的結(jié)構(gòu)體變量
    // 傳入的參數(shù)分別是:
    // isa: void *0; 
    // __forwarding: &a,即 a 結(jié)構(gòu)體變量的地址
    // __flags: 0
    // __size: sizeof(結(jié)構(gòu)體)
    // a: 10,即 a 的值
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};

    // block 定義
    dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

    // 對(duì) a 變量賦值
    (a.__forwarding->a) = 100;

    // block 的調(diào)用
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
                        
    // 輸出 a
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_xxx_1, (a.__forwarding->a));
}
return 0;

}
經(jīng)過(guò)了這一坨編譯后代碼的理解,現(xiàn)在已經(jīng)腦子已經(jīng)是一團(tuán)糨糊了,莫名多出的結(jié)構(gòu)體、__forwarding 指針,copy 與 dispose 函數(shù)無(wú)一不在提高理解的門檻。代碼都看得懂,但是就是不知道這樣做的目的,下面我們便來(lái)將多出的東西,重新整理一次,然后注意理解:

現(xiàn)在的 block 內(nèi)存結(jié)構(gòu)是怎樣的?
為什么 a 被 __block 修飾以后,就變成了 __Block_byref_a_0 結(jié)構(gòu)體?
多出的 __forwarding 指針是什么?
為什么之后無(wú)論是在 block 內(nèi)部,還是在 block 外部,訪問(wèn) a 都變成了 a->__forwarding->a ?
既然定義了 copy 與 dispose 函數(shù),為什么沒(méi)有看到顯式調(diào)用?如果是隱式調(diào)用,那么調(diào)用時(shí)機(jī)是什么時(shí)候?
多個(gè) block 對(duì) __block int a = 10; 進(jìn)行使用,指針會(huì)怎樣指向?
當(dāng)前的 block 內(nèi)存結(jié)構(gòu)

從 main 函數(shù)中 _Block_byref_a_0 的初始化可以看出,給 __forwarding 指針賦的值就是 (__Block_byref_a_0 *)&a,所以 __forwarding 指針是同樣是指向 a 結(jié)構(gòu)體變量本身的。

為什么在 block 中的變量,超出作用域還能使用

在全局區(qū)或者是棧上的 block,我們并不能控制它的釋放時(shí)機(jī),但是如果 block 在堆中,就可以由我們來(lái)控制了。所以,大多數(shù)情況下,比如將 block 作為回調(diào)方法等時(shí)候,block 一般都是在堆上的。

那么,block 是如何拷貝到堆上的呢?這就和 copy 函數(shù)有關(guān)了。在 ARC 環(huán)境下,如果將 block 聲明為:

@property (copy) block;
@property (strong) block;
在賦值時(shí),其實(shí)都會(huì)調(diào)用 Block_copy() 函數(shù),將棧上的 block 拷貝到堆中,此時(shí),block 中所持有的變量就都在堆中了,我們通過(guò)管理 block 的生命周期,就能間接管理到 block 持有的變量的生命周期。

block 的 copy 時(shí)機(jī)

那么 block 何時(shí)會(huì) copy 到堆上呢?是顯式,還是隱式?

顯式

在作為屬性定義時(shí),用 copy 和 strong 修飾;
手動(dòng)調(diào)用 [block copy];
隱式

賦值給 __strong 修飾的變量時(shí)。因?yàn)?ARC 下,__strong 是缺省值,所以只要不是顯式標(biāo)記了 __unsafe_unretained 或 __weak,block 均會(huì)被拷貝到堆上;
作為函數(shù)返回值;
含有 usingBlock 的 Cocoa 框架中的方法,如枚舉器;
GCD 的 block。
__forwarding 指針存在的意義

在閱讀編譯代碼時(shí)可以發(fā)現(xiàn),block 在讀寫被 __block 標(biāo)記的變量時(shí),均使用 var->__forwarding->var 來(lái)訪問(wèn)。var 是指針能理解,因?yàn)樗隙ㄊ菍?duì)臨時(shí)變量進(jìn)行地址引用,要不然也不能獲得最新的值。但是為什么要在中間加一個(gè) __forwarding 呢?而且 __forwarding 指針還是指向的自己。

來(lái)看一個(gè)例子(ARC 下):

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

    __block int a = 10;
    NSLog(@"1. block 定義之前 a 的地址 : %p", &a);
    dispatch_block_t __unsafe_unretained block = ^{
        a = 100;
        NSLog(@"2. 調(diào)用 block 時(shí) a 的地址 : %p", &a);
    };
    NSLog(@"3. block 定義之后 a 的地址 : %p", &a);
    dispatch_block_t heapBlock = block;
    NSLog(@"4. block 拷貝到堆上 a 的地址 : %p", &a);
    block();
}
return 0;

}
上面的例子中,一共輸出了四次 a 的地址。其中,1 和 3 的地址是一樣的,2 和 4 的地址是一樣。

這里我還對(duì) block 特地標(biāo)記了 __unsafe_unretained,防止在定義賦值的時(shí)候,就拷貝到堆。而這之后的 heapBlock 則是因?yàn)楸?__strong 修飾所以將 block 拷貝到了堆。

在 1、3 輸出的時(shí)候,a 還在棧上,此時(shí)的 block 內(nèi)存為:

而在 block 拷貝到堆上以后, __forwarding 指針則指向堆上的 a 結(jié)構(gòu)體,所以,內(nèi)存變成了這樣:

這樣就保證了棧上和堆上的 block,都能訪問(wèn)到同一個(gè) a 變量,這也是 __forwarding 指針的作用。

block 的存儲(chǔ)區(qū)域

之前談到 block 根據(jù)存儲(chǔ)位置不同,可分為三種,堆、棧、全局區(qū)。那么這三種 block 是怎樣的呢?

NSStackBlock:block 被定義為臨時(shí)變量,并且引用了外部變量;
NSMallocBlock:調(diào)用了 copy 函數(shù),被拷貝到堆上的 block;
NSGlobalBlock:定義為全局變量,或者臨時(shí)變量但是沒(méi)有引用外部變量的 block。
多個(gè) block 對(duì) __block 變量的引用

在 block 引用使用 __block 修飾的外部變量時(shí),編譯器去針對(duì)這個(gè)外部變量生成了結(jié)構(gòu)體,比如我們上面談到的 __Block_byref_a_0 結(jié)構(gòu)體。

之所以這樣做,也是為了能在多個(gè) block 引用時(shí),能夠給對(duì) __Block_byref_a_0 進(jìn)行復(fù)用。所以,當(dāng)多個(gè) block 引用該變量時(shí),并不會(huì)重復(fù)生成結(jié)構(gòu)體,而是對(duì)該結(jié)構(gòu)體內(nèi)存進(jìn)行持有,在 block 銷毀,調(diào)用 dispose 時(shí),對(duì)內(nèi)存進(jìn)行釋放。

循環(huán)引用

OC 中的循環(huán)引用是一個(gè)老生常談的問(wèn)題,其中最容易出現(xiàn)循環(huán)引用的地方,就是 block。都知道,出現(xiàn)循環(huán)引用的原因,是因?yàn)閮蓚€(gè)變量的相互持有,導(dǎo)致誰(shuí)也無(wú)法釋放。斷開(kāi)循環(huán)引用鏈,最常見(jiàn)的方式是:

在源頭斷開(kāi):一方不持有另一方;
通過(guò)置空斷開(kāi):在已經(jīng)對(duì)象使用完畢,需要釋放的時(shí)候,將一方置空。
根據(jù)這兩種解決方案,block 解決循環(huán)引用對(duì)應(yīng)著兩種方式:

使用 __weak 或者 __unsafe_unretained 修飾 block 內(nèi)部要用到的變量。
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf);
};
self.block();
使用 __block 修飾變量,然后在 block 調(diào)用完畢后,在 block 內(nèi)部對(duì)變量置空。
__block typeof(self) blockSelf = self;
self.block = ^{
NSLog(@"%@", blockSelf);
blockSelf = nil;
};
self.block();
使用第二種方式有一個(gè)弊端,就是必須要保證 block 會(huì)調(diào)用,這樣才有機(jī)會(huì)斷開(kāi)循環(huán)引用,否則無(wú)法解決問(wèn)題。當(dāng)然,也有優(yōu)點(diǎn),即可以控制另一方的釋放時(shí)機(jī),保證不調(diào)用,就不會(huì)釋放。

__weak 與 __strong

通常我們能看到以下寫法:

__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"%@", strongSelf);
};
self.block();
__weak 的作用我們剛才已經(jīng)提到了,但是在 block 內(nèi)部又使用 __strong 標(biāo)記是為什么?這樣會(huì)造成循環(huán)引用嗎?我們來(lái)看看 block 實(shí)現(xiàn)編譯過(guò)后的代碼:

static void __Test__init_block_func_0(struct __Test__init_block_impl_0 *__cself) {
// 這里變成了值拷貝,而不是指針引用
typeof (self) weakSelf = __cself->weakSelf; // bound by copy

// 雖然是 strong 的,但是是在 block 調(diào)用時(shí),才將 self 的值拷貝賦值給臨時(shí)變量 weakSelf,之后被 strongSelf 引用
// 根據(jù) ARC 的規(guī)則,使用 __strong 修飾的變量,出作用域以后,會(huì)插入 release 語(yǔ)句,所以在 block 實(shí)現(xiàn)結(jié)束后,strongSelf 會(huì)釋放,并不會(huì)造成循環(huán)引用
__attribute__((objc_ownership(strong))) Test * strongSelf =  weakSelf;
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_0, strongSelf);

}
也是因?yàn)?ARC 對(duì) __strong 修飾的變量,出作用域才插入 release 的機(jī)制,我們可以知道,之所以在 block 內(nèi)部使用 __strong 修飾變量,是因?yàn)榉乐乖?block 執(zhí)行過(guò)程中,變量被釋放的情況。

在 block 中調(diào)用的方法含有 self,是否會(huì)造成循環(huán)引用

再看下面這段代碼:

  • (void)testBlock {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
    __strong typeof(self) strongSelf = weakSelf;
    // 在 block 實(shí)現(xiàn)中,調(diào)用了 run 方法,而 run 方法中,又用到了 self
    [strongSelf run];
    };
    self.block();
    }

  • (void)run {
    NSLog(@"%@", self);
    }
    之前有人問(wèn)到這個(gè)問(wèn)題,說(shuō)如果 block 中調(diào)用的方法又用到了 self,會(huì)造成循環(huán)引用嗎?想想如果會(huì)造成,那豈不是很可怕,好像一直這樣寫,都沒(méi)什么問(wèn)題。那這是為什么呢?

別忘了 OC 是消息機(jī)制,發(fā)送完消息之后就不管了,所以,并不影響消息的實(shí)現(xiàn)。

疑問(wèn)

雖然能將 block 編譯出來(lái)看到代碼,但是還是有很多疑問(wèn)的,希望大家能解答一下。

ARC 與 MRC 下,有無(wú) __block 標(biāo)識(shí),對(duì) block 持有對(duì)象的影響

其實(shí)這里是四種狀態(tài):

ARC + 無(wú) __block
MRC + 無(wú) __block
ARC + 有 __block
MRC + 有 __block
首先來(lái)看問(wèn)題 1、2:

  • (instancetype)init {
    self = [super init];
    if (self) {

      // 定義一個(gè)可變數(shù)組 arr
      NSMutableArray *arr = [NSMutableArray new];
      // 輸出 retainCount
      NSLog(@"1. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      // 為減少隱式的 __strong 造成拷貝到堆的影響,所以使用 __unsafe_unretained 修飾
      __unsafe_unretained dispatch_block_t block = ^{
          NSLog(@"%@", arr);
          // 輸出調(diào)用 block 時(shí),arr 的 retainCount
          NSLog(@"2. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      };
      // 在定義完 block 后,arr 的 retainCount
      NSLog(@"3. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 2; MRC: 1
      // 顯示拷貝到堆
      self.block = block;
      // 在 block 拷貝到堆以后,arr 的 retainCount
      NSLog(@"4. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 3; MRC: 2
    
      // 如果是 MRC,則手動(dòng) release
      [arr release];
    

    }
    return self;
    }

// 調(diào)用 block

  • (void)run {
    self.block();
    }
    可以看到,同樣沒(méi)有使用 __block 修飾,ARC 在 block 定義完以后,arr 的 retainCount 要比 MRC 下多 1,這是因?yàn)樵?block 的結(jié)構(gòu)體中,所定義的 NSMutableArray *arr,默認(rèn)的缺省值是 __strong,而導(dǎo)致的持有,而 MRC 下,缺省值不是 __strong 造成的。

再來(lái)看問(wèn)題 2、4,還是借助上面的例子,只是在 arr 定義時(shí),在前面使用 __block 進(jìn)行修飾,但這一次的 retainCount 卻大為不同:

  • (instancetype)init {
    self = [super init];
    if (self) {

      __block NSMutableArray *arr = [NSMutableArray new];
      NSLog(@"1. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      __unsafe_unretained dispatch_block_t block = ^{
          NSLog(@"%@", arr);
          NSLog(@"2. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      };
      NSLog(@"3. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      self.block = block;
      NSLog(@"4. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      
      [arr release];
    

    }
    return self;
    }

  • (void)run {
    self.block();
    }
    這一次,我們發(fā)現(xiàn),無(wú)論是 ARC,還是 MRC,arr 的 retainCount 始終為 1,在查閱資料后,找到這樣一句話:

在 MRC 下,__block 說(shuō)明符可被用來(lái)避免循環(huán)引用,是因?yàn)楫?dāng) block 從棧復(fù)制到堆上時(shí),如果變量被 __block 修飾,則不會(huì)再次 retain,如果沒(méi)有被 __block 修飾,則會(huì)被 retain。
但是,從上面的代碼輸出來(lái)看,ARC 和 MRC,block 是否拷貝到堆上,都沒(méi)有再次對(duì)變量進(jìn)行持有,retainCount 始終為 1,所以,到這里我遇到幾個(gè)不太理解的地方:

__block 修飾符不再持有對(duì)象,僅僅是在 MRC 下有效,還是 ARC 與 MRC 下效果是相同的?
如果效果是相同的,為什么 __block 不能解決 ARC 下的循環(huán)引用問(wèn)題?
不能解決 ARC 下的循環(huán)引用問(wèn)題,是否是因?yàn)?ARC 下,arr 定義時(shí),缺省值是 __strong 導(dǎo)致的?
在 ARC 下,變量出作用域,編譯器插入 release,為什么 arr 的 retainCount 是 1,經(jīng)過(guò)一次 release 以后,并未出現(xiàn)問(wèn)題,而在 MRC 下,在 block 調(diào)用的時(shí)候,就會(huì)出現(xiàn) crash?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,553評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,620評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,416評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,940評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,170評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,597評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,029評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,407評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,663評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,403評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,746評(píng)論 2 370

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

  • 前言 Blocks是C語(yǔ)言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,778評(píng)論 0 23
  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動(dòng)變量值 int main(){ ...
    南京小伙閱讀 950評(píng)論 1 3
  • 摘要block是2010年WWDC蘋果為Objective-C提供的一個(gè)新特性,它為我們開(kāi)發(fā)提供了便利,比如GCD...
    西門吹雪123閱讀 931評(píng)論 0 4
  • Block基礎(chǔ)回顧 1.什么是Block? 帶有局部變量的匿名函數(shù)(名字不重要,知道怎么用就行),差不多就與C語(yǔ)言...
    Bugfix閱讀 6,785評(píng)論 5 61
  • 序言:翻閱資料,學(xué)習(xí),探究,總結(jié),借鑒,謝謝探路者,我只是個(gè)搬運(yùn)工。參考、轉(zhuǎn)發(fā)資料:http://www.cnbl...
    Init_ZSJ閱讀 904評(píng)論 0 1