Block 梳理與疑問

Block 梳理與疑問

時隔一年凹蜈,再次讀 《Objective-C 高級編程》限寞,看到 block 一章,這一次從頭至尾的跟著編譯了一次仰坦,理清楚了很多之前不理解的地方履植,但是也同時多出了許多疑問。本文是在和學渣裙的朋友們分享以后的梳理筆記悄晃,有問題歡迎指出玫霎,如果能解決最后的幾個小疑問,就更好了妈橄。

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

一個最基本的 block

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

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

    // 定義一個參數(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; // 實現(xiàn)
struct __main_block_desc_0* Desc; // 描述

// 在定義 block 時沙热,所調(diào)用的 block 初始化方法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; // block 的類型(之后會談到)
impl.Flags = flags;
impl.FuncPtr = fp; // block 實現(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 描述的初始化方法普舆,可以看出這里的大小計算恬口,僅僅是進行了 sizeof

// block 實現(xiàn)編譯過后的函數(shù)
// 即在 block 初始化方法中,賦值給 impl.FuncPtr 的函數(shù)指針
// 參數(shù) cself 是 __main_block_impl_0 類型沼侣,即與 block 類型相同祖能,其實這里的參數(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 定義的時候养铸,就調(diào)用了 __main_block_impl_0,即 block 的構(gòu)造方法
// 傳的參數(shù)分別為 __main_block_func_0轧膘,即 block 對應(yīng)的編譯后的實現(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)用了谎碍,初始化時拿到的 FuncPtr 函數(shù)指針
// FuncPtr 函數(shù)有一個參數(shù)鳞滨,即傳入的 block 自身
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

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

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

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

isa 指針

在 block 調(diào)用構(gòu)造方法時拯啦,編譯器已經(jīng)自動給 isa 指針賦了初值。我們知道熔任,isa 指針其實很形象褒链,就稱作 is a,在 OC 中表達了對象是什么類型疑苔,類所屬哪個元類甫匹,其實 block 也是對象,所以它的 isa 指針也是說明它是什么的惦费。如果直接打印 block兵迅,則可看到以下三種情況:

<NSStackBlock: 0x1000010c0>,存儲在棧上的 block
<NSMallocBlock: 0x1000010c0>薪贫,存儲在堆上的 block
<NSGlobalBlock: 0x1000010c0>喷兼,存儲在全局區(qū)的 block
但是你會發(fā)現(xiàn),如果直接打印上面我們所寫的 block后雷,輸出的是 NSGlobalBlock 類型,而我們看到的編譯代碼吠各,明明是 stack 的臀突。這是因為 block 的存儲區(qū)域,與定義在什么位置贾漏、是否引用外部變量候学、是否作為范圍值、是被哪種類型的變量所接收等等情況相關(guān)纵散,這個會在下一小節(jié)談到梳码。

引用外部變量的 block

之前介紹了一個空(并未引用變量)的 block隐圾,下面來看一個稍微復(fù)雜一點的:

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;

}
在學習 block 的基礎(chǔ)知識時,就知道掰茶,此時如果在 block 定義之后暇藏,去修改 a 的值,block 中的輸出依然不會改變濒蒋,我們來看一下為什么盐碱。

編譯文件:

// 下面僅標注了有變化的變量

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

// block 構(gòu)造方法也多了一個 _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;
}
};

// 描述依然沒有變沪伙,size 是直接計算的 __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 的實現(xiàn)函數(shù)中瓮顽,訪問 block 結(jié)構(gòu)體中的 a 變量,并且編譯器在此還說明了是 bound by copy围橡,即值拷貝
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_0, a);
}
從編譯代碼可以看出暖混,在 block 定義時,就傳入了 block 內(nèi)部需要用到的 a 變量的值翁授,而并不是引用拣播,所以即使在 block 定義之后,a 變量怎么變黔漂,之前 block 所有的 a 的瞬時值诫尽,是沒有變化的。

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

多出了捕獲的變量 a 的存儲空間牧嫉,并且,捕獲的變量會接在 Desc 內(nèi)存后面减途。

被 __block 修飾的外部變量

如果沒有 __block 修飾酣藻,除了在 block 定義之后,就不能拿到變量最新的值以外鳍置,我們還不能對變量進行重新賦值(如果是堆上的內(nèi)存辽剧,就是改變地址,即 NSMutableArray 是可以 addObject 的税产,只是不能 array = @[])怕轿。那么,想要解決這兩個問題辟拷,我們就需要引入 __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)部對 a 重新賦值
        a = 50;
    };
    // 在 block 定義后,對 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;

// 比起沒有被 __block 修飾的變量(編譯后诀紊,block 中是 int a),這里 block 卻不是簡單的拿到 a 地址隅俘,即 int *a邻奠,而是一個類型為 __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碌宴,這個 __forwarding 指針的作用杀狡,會在之后介紹
__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;

// 這是比起以前,多出的兩個函數(shù)指針唧喉,一個 copy捣卤,一個 dispose
// 這也是 block 中尤為重要的兩個函數(shù)
// copy 負責將 block 復(fù)制到堆
// dispose 負責在 block 釋放時,釋放 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 實現(xiàn)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 訪問到 block 中的 a 結(jié)構(gòu)體指針變量
__Block_byref_a_0 *a = __cself->a; // bound by ref

// 輸出
// 可以看到八孝,這里訪問的 a 的值董朝,是通過 __forwarding 指針訪問的,包括之后的賦值干跛,也是用的 __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ù)進行拷貝
_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));

    // 對 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)過了這一坨編譯后代碼的理解楼入,現(xiàn)在已經(jīng)腦子已經(jīng)是一團糨糊了哥捕,莫名多出的結(jié)構(gòu)體、__forwarding 指針嘉熊,copy 與 dispose 函數(shù)無一不在提高理解的門檻遥赚。代碼都看得懂,但是就是不知道這樣做的目的阐肤,下面我們便來將多出的東西凫佛,重新整理一次,然后注意理解:

現(xiàn)在的 block 內(nèi)存結(jié)構(gòu)是怎樣的孕惜?
為什么 a 被 __block 修飾以后愧薛,就變成了 __Block_byref_a_0 結(jié)構(gòu)體?
多出的 __forwarding 指針是什么衫画?
為什么之后無論是在 block 內(nèi)部毫炉,還是在 block 外部,訪問 a 都變成了 a->__forwarding->a 削罩?
既然定義了 copy 與 dispose 函數(shù)瞄勾,為什么沒有看到顯式調(diào)用?如果是隱式調(diào)用弥激,那么調(diào)用時機是什么時候进陡?
多個 block 對 __block int a = 10; 進行使用,指針會怎樣指向秆撮?
當前的 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,我們并不能控制它的釋放時機舒裤,但是如果 block 在堆中喳资,就可以由我們來控制了。所以腾供,大多數(shù)情況下仆邓,比如將 block 作為回調(diào)方法等時候,block 一般都是在堆上的伴鳖。

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

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

block 的 copy 時機

那么 block 何時會 copy 到堆上呢拒贱?是顯式宛徊,還是隱式?

顯式

在作為屬性定義時柜思,用 copy 和 strong 修飾岩调;
手動調(diào)用 [block copy];
隱式

賦值給 __strong 修飾的變量時赡盘。因為 ARC 下号枕,__strong 是缺省值,所以只要不是顯式標記了 __unsafe_unretained 或 __weak陨享,block 均會被拷貝到堆上葱淳;
作為函數(shù)返回值;
含有 usingBlock 的 Cocoa 框架中的方法抛姑,如枚舉器赞厕;
GCD 的 block。
__forwarding 指針存在的意義

在閱讀編譯代碼時可以發(fā)現(xiàn)定硝,block 在讀寫被 __block 標記的變量時皿桑,均使用 var->__forwarding->var 來訪問。var 是指針能理解,因為它肯定是對臨時變量進行地址引用诲侮,要不然也不能獲得最新的值镀虐。但是為什么要在中間加一個 __forwarding 呢?而且 __forwarding 指針還是指向的自己沟绪。

來看一個例子(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 時 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 的地址是一樣。

這里我還對 block 特地標記了 __unsafe_unretained坝疼,防止在定義賦值的時候搜贤,就拷貝到堆。而這之后的 heapBlock 則是因為被 __strong 修飾所以將 block 拷貝到了堆裙士。

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

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

這樣就保證了棧上和堆上的 block铆隘,都能訪問到同一個 a 變量,這也是 __forwarding 指針的作用南用。

block 的存儲區(qū)域

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

NSStackBlock:block 被定義為臨時變量雳窟,并且引用了外部變量;
NSMallocBlock:調(diào)用了 copy 函數(shù)匣屡,被拷貝到堆上的 block封救;
NSGlobalBlock:定義為全局變量,或者臨時變量但是沒有引用外部變量的 block捣作。
多個 block 對 __block 變量的引用

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

之所以這樣做惩坑,也是為了能在多個 block 引用時掉盅,能夠給對 __Block_byref_a_0 進行復(fù)用。所以以舒,當多個 block 引用該變量時怔接,并不會重復(fù)生成結(jié)構(gòu)體,而是對該結(jié)構(gòu)體內(nèi)存進行持有稀轨,在 block 銷毀,調(diào)用 dispose 時岸军,對內(nèi)存進行釋放奋刽。

循環(huán)引用

OC 中的循環(huán)引用是一個老生常談的問題,其中最容易出現(xiàn)循環(huán)引用的地方艰赞,就是 block佣谐。都知道,出現(xiàn)循環(huán)引用的原因方妖,是因為兩個變量的相互持有狭魂,導(dǎo)致誰也無法釋放。斷開循環(huán)引用鏈党觅,最常見的方式是:

在源頭斷開:一方不持有另一方雌澄;
通過置空斷開:在已經(jīng)對象使用完畢,需要釋放的時候杯瞻,將一方置空镐牺。
根據(jù)這兩種解決方案,block 解決循環(huán)引用對應(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)部對變量置空旗唁。
__block typeof(self) blockSelf = self;
self.block = ^{
NSLog(@"%@", blockSelf);
blockSelf = nil;
};
self.block();
使用第二種方式有一個弊端畦浓,就是必須要保證 block 會調(diào)用,這樣才有機會斷開循環(huán)引用检疫,否則無法解決問題讶请。當然,也有優(yōu)點电谣,即可以控制另一方的釋放時機秽梅,保證不調(diào)用,就不會釋放。

__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 標記是為什么汤徽?這樣會造成循環(huán)引用嗎口糕?我們來看看 block 實現(xiàn)編譯過后的代碼:

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)用時谷饿,才將 self 的值拷貝賦值給臨時變量 weakSelf未荒,之后被 strongSelf 引用
// 根據(jù) ARC 的規(guī)則荧降,使用 __strong 修飾的變量接箫,出作用域以后,會插入 release 語句朵诫,所以在 block 實現(xiàn)結(jié)束后辛友,strongSelf 會釋放,并不會造成循環(huán)引用
__attribute__((objc_ownership(strong))) Test * strongSelf =  weakSelf;
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_0, strongSelf);

}
也是因為 ARC 對 __strong 修飾的變量剪返,出作用域才插入 release 的機制废累,我們可以知道,之所以在 block 內(nèi)部使用 __strong 修飾變量脱盲,是因為防止在 block 執(zhí)行過程中邑滨,變量被釋放的情況。

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

再看下面這段代碼:

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

  • (void)run {
    NSLog(@"%@", self);
    }
    之前有人問到這個問題哎壳,說如果 block 中調(diào)用的方法又用到了 self,會造成循環(huán)引用嗎尚卫?想想如果會造成耳峦,那豈不是很可怕,好像一直這樣寫焕毫,都沒什么問題蹲坷。那這是為什么呢?

別忘了 OC 是消息機制邑飒,發(fā)送完消息之后就不管了循签,所以,并不影響消息的實現(xiàn)疙咸。

疑問

雖然能將 block 編譯出來看到代碼县匠,但是還是有很多疑問的,希望大家能解答一下撒轮。

ARC 與 MRC 下乞旦,有無 __block 標識,對 block 持有對象的影響

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

ARC + 無 __block
MRC + 無 __block
ARC + 有 __block
MRC + 有 __block
首先來看問題 1题山、2:

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

      // 定義一個可變數(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 時,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愕秫,則手動 release
      [arr release];
    

    }
    return self;
    }

// 調(diào)用 block

  • (void)run {
    self.block();
    }
    可以看到,同樣沒有使用 __block 修飾焰络,ARC 在 block 定義完以后戴甩,arr 的 retainCount 要比 MRC 下多 1,這是因為在 block 的結(jié)構(gòu)體中闪彼,所定義的 NSMutableArray *arr甜孤,默認的缺省值是 __strong,而導(dǎo)致的持有畏腕,而 MRC 下课蔬,缺省值不是 __strong 造成的。

再來看問題 2郊尝、4,還是借助上面的例子战惊,只是在 arr 定義時流昏,在前面使用 __block 進行修飾,但這一次的 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)况凉,無論是 ARC,還是 MRC各拷,arr 的 retainCount 始終為 1刁绒,在查閱資料后,找到這樣一句話:

在 MRC 下烤黍,__block 說明符可被用來避免循環(huán)引用知市,是因為當 block 從棧復(fù)制到堆上時,如果變量被 __block 修飾速蕊,則不會再次 retain嫂丙,如果沒有被 __block 修飾,則會被 retain规哲。
但是跟啤,從上面的代碼輸出來看,ARC 和 MRC唉锌,block 是否拷貝到堆上隅肥,都沒有再次對變量進行持有,retainCount 始終為 1袄简,所以腥放,到這里我遇到幾個不太理解的地方:

__block 修飾符不再持有對象,僅僅是在 MRC 下有效绿语,還是 ARC 與 MRC 下效果是相同的捉片?
如果效果是相同的平痰,為什么 __block 不能解決 ARC 下的循環(huán)引用問題?
不能解決 ARC 下的循環(huán)引用問題伍纫,是否是因為 ARC 下宗雇,arr 定義時,缺省值是 __strong 導(dǎo)致的莹规?
在 ARC 下赔蒲,變量出作用域,編譯器插入 release良漱,為什么 arr 的 retainCount 是 1舞虱,經(jīng)過一次 release 以后,并未出現(xiàn)問題母市,而在 MRC 下矾兜,在 block 調(diào)用的時候,就會出現(xiàn) crash患久?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椅寺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蒋失,更是在濱河造成了極大的恐慌返帕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篙挽,死亡現(xiàn)場離奇詭異荆萤,居然都是意外死亡,警方通過查閱死者的電腦和手機铣卡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門链韭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人煮落,你說我怎么就攤上這事梧油。” “怎么了州邢?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵儡陨,是天一觀的道長。 經(jīng)常有香客問我量淌,道長骗村,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任呀枢,我火速辦了婚禮胚股,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘裙秋。我一直安慰自己琅拌,他們只是感情好缨伊,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著进宝,像睡著了一般刻坊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上党晋,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天谭胚,我揣著相機與錄音,去河邊找鬼未玻。 笑死灾而,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的扳剿。 我是一名探鬼主播旁趟,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼庇绽!你這毒婦竟也來了锡搜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤敛劝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纷宇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夸盟,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年像捶,在試婚紗的時候發(fā)現(xiàn)自己被綠了上陕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡拓春,死狀恐怖释簿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硼莽,我是刑警寧澤庶溶,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站懂鸵,受9級特大地震影響偏螺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匆光,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一套像、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧终息,春花似錦夺巩、人聲如沸贞让。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喳张。三九已至,卻和暖如春征绎,著一層夾襖步出監(jiān)牢的瞬間蹲姐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工人柿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留柴墩,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓凫岖,卻偏偏與公主長得像江咳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哥放,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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

  • 前言 Blocks是C語言的擴充功能歼指,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,757評論 0 23
  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動變量值 int main(){ ...
    南京小伙閱讀 911評論 1 3
  • 摘要block是2010年WWDC蘋果為Objective-C提供的一個新特性,它為我們開發(fā)提供了便利甥雕,比如GCD...
    西門吹雪123閱讀 903評論 0 4
  • Block基礎(chǔ)回顧 1.什么是Block踩身? 帶有局部變量的匿名函數(shù)(名字不重要,知道怎么用就行)社露,差不多就與C語言...
    Bugfix閱讀 6,748評論 5 61
  • 序言:翻閱資料挟阻,學習,探究峭弟,總結(jié)附鸽,借鑒,謝謝探路者瞒瘸,我只是個搬運工坷备。參考、轉(zhuǎn)發(fā)資料:http://www.cnbl...
    Init_ZSJ閱讀 899評論 0 1