本文主要根據《Objective-C高級編程》這本書中的第二章來進行的一個總結,其中包含了查看其它文章后的總結和自己的一些理解,關于 block 的一些定義在這里就不說了,這里主要講一下 block 中的截獲自動變量和 __block
關鍵字的實現
文章主要內容如下:
- 一個普通的 block
- 截獲自動變量值
- __block 修飾符
- 循環引用
一個普通的 block
int main(int argc, char * argv[]) {
void(^blk)() = ^{
printf("Hello World");
};
blk();
return 0;
}
一個最普通的 block,內部沒有使用到任何變量,讓我們來看一下它的內部結構,通過 clang -rewrite-objc
命令將上面的代碼解析為一份 C++ 代碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello World");
}
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)};
int main(int argc, char * argv[]) {
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
main 函數
對 main 函數進行分解處理,這樣可以更加直觀的查看
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
分解為
struct __main_block_impl_0 tempBlock = __main_block_impl_0(__main_block_func_0, &_main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tempBlock;
可以看出,在編譯之后 blk 是一個指向一個 __main_block_impl_0
類型結構體的指針,接下來根據函數調用順序進行進一步了解
結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
從上面的 main 函數可以看到,創建的就是這樣一個類型的結構體,我們來看一下這個結構體的內部構造:
- impl:
__block_impl
類型,成員變量之一,內部含有函數指針 - Desc:
__main_block_desc_0
類型指針,成員變量之一,用來描述當前 block 的一些附加信息,例如:結構體的大小 -
__main_block_impl_0
:結構體初始化函數,在結構體中進行成員變量的初始化
接下來我們看一下 impl 所屬結構體的主要構造
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
- isa:非常常見的指向類的指針
- FuncPtr:函數指針,在 main 函數的初始化過程中,可以觀察到是將一個靜態函數
__main_block_func_0
對其賦值
接下來看一下 Desc 指針的結構
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)};
- reserved:今后升級版本所需要區域
- Block_size:block 的大小
調用結構體初始化函數
從源碼可以看出,在 main 函數中通過調用結構體的初始化函數來創建一個結構體
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
我們可以看出,isa 指針保持所屬類的結構體的實例的指針,這也說明了 block 其實也就是一個 OC 對象,因為其包含了 isa 指針
截獲自動變量值
先來看一段代碼:
int a = 10;
void(^blk)() = ^{
printf("%d\n", a);
};
a = 20;
blk();//輸出結果:10
我們可以發現,當我們定義一個變量之后,又定義一個 block,并且在 block 內部引用了這個變量,然后在外面修改變量,然后調用 block,得到的變量還是修改前的變量,這是為什么呢?好像是我們定義 block 的時候將使用的變量 copy 了一份然后自己存起來,當調用 block 的時候,使用的變量是我們 copy 的那一份,所以不管外面對變量如何進行修改,都不會影響我們保存的那一份,當然這只是猜想,帶著問題走進源碼看一看.
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("%d\n", a);
}
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)};
int main(int argc, char * argv[]) {
int a = 10;
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
對比前面那個普通的 block,我們發現幾個不同點
- 在
__main_block_impl_0
結構體中多了一個成員變量a
, 并且在初始化函數中多了一個參數a
,這個a
就是 block 截獲的自動變量值 - 在靜態函數
__main_block_func_0
使用截獲的自動變量的時候,用到了一個指針_cself
, 這是什么呢?在 main 可以發現這個參數是在進行 block 調用的時候傳入的,傳入的就是 block 本身,所以說在靜態函數中操作的變量也就是在 block 創建的時候的那個成員變量,而不是操作的外部變量,也就是說,block 只是截獲的自動變量的值,而并不是截獲了其地址,所以外部變量在 block 創建之后進行修改在這里也沒什么效果,前面的猜想得證.
這里需要注意,block 只會截獲使用到的變量,而對于沒有使用到的變量是不會進行截獲的.
那是不是所有的變量都是如此呢?我們做測試的是局部變量,但是我們知道,在 C 語言中變量還有全局變量、全局靜態變量和局部靜態變量,我們使用這些來嘗試一下,以下使用局部靜態變量做測試,至于全局變量和全局靜態變量留給讀者自己探索,后面也會進行解釋:
static int a = 10;
void(^blk)() = ^{
printf("%d\n", a);
};
a = 20;
blk();//輸出20
我們發現輸出變了,怎么回事呢?還是看源碼
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *a;
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *a = __cself->a; // bound by copy
printf("%d\n", (*a));
}
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)};
int main(int argc, char * argv[]) {
static int a = 10;
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
和上面的局部變量作對比,可以發現,在 __main_block_impl_0
結構體中,變成了存儲變量 a
的指針,那么我們訪問的時候訪問的是指針 a
指向的變量,那么就可以解釋輸出為 20 的原因.
__block 修飾符
當我們修改截獲的變量的時候,示例如下:
int a = 10;
void(^blk)() = ^{
a = 20;//報錯:Variable is not assignable(missing,__block type specifier)
printf("%d\n", a);
};
blk();
因為在底層實現中,即使在 block 內部對對象進行改變,外部的對象沒有任何改變,原因也就是前面的解釋的自動變量的截獲,所以可能蘋果就在編譯階段就告訴開發者這樣是無效的,也就是直接編譯報錯。
那么,現在可以得出一個結論:被截獲的自動變量值不能直接進行修改,但是,有兩種方法可以解決這個問題:
- 改變存儲于特殊存儲區域的變量
- 使用
__block
修飾符
我們先來說一下第一種,在 C 語言中變量一般分為五種,分別是:
- 自動變量(局部變量)
- 函數參數
- 靜態變量(局部靜態變量)
- 靜態全局變量
- 全局變量
這里需要去除函數參數這一項,那么寫一個測試代碼,因為前面已經做過自動變量和靜態變量的測試,所以這里只進行全局變量和靜態全局變量的測試,如下:
int globalA = 10;
static int intGlobalA = 20;
int main(int argc, char * argv[]) {
void(^blk)() = ^{
globalA += 10;
intGlobalA += 20;
printf("%d %d\n", globalA, intGlobalA);//輸出:20 40
};
blk();
return 0;
}
查看輸出,發現可以進行改變,我們來看一下源碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
int globalA = 10;
static int intGlobalA = 20;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
globalA += 10;
intGlobalA += 20;
printf("%d %d\n", globalA, intGlobalA);
}
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)};
int main(int argc, char * argv[]) {
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
通過源碼我們發現,block 并未對兩個變量進行截獲,而進行訪問和修改的時候,并未經過 block,也就是并未使用到 __cself
指針,而是直接進行訪問和修改,如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
globalA += 10;
intGlobalA += 20;
printf("%d %d\n", globalA, intGlobalA);
}
至此,可以得出一個結論:
- 全局變量:可以直接進行訪問,修改
- 靜態全局變量:可以直接進行訪問,修改
- 靜態變量:指針引用,可以進行訪問,修改
- 自動變量:值引用,不可進行修改
然后,我們再來看一下第二種,使用 __block
修飾符,使用 __block
修飾符修飾的變量稱為 block 變量
測試代碼如下:
int main(int argc, char * argv[]) {
__block int a = 10;
void(^blk)() = ^{
a = 20;
printf("%d\n", a);//輸出:20
};
blk();
printf("%d\n", a);//輸出:20
return 0;
}
可以發現,在使用 __block
修飾符之后 block 內可以修改自動變量了,同樣的,我們來查看一下源碼的實現:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 20;
printf("%d\n", (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
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};
int main(int argc, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
printf("%d\n", (a.__forwarding->a));
return 0;
}
可以發現,在使用 __block
修飾符之后,源碼中多了很多東西,我們來一個個進行解析
首先,還是從 __main_block_impl_0
這個結構體開始,可以發現,此時的 block 并不是將截獲的 a
直接作為成員變量,而是使用一個結構體 __Block_byref_a_0
類型的同名變量來代替,我們來看一下這個結構體:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
可以看到,這個結構體中含有一個成員變量 a
,即是截獲的自動變量,也就是 __block
修飾的自動變量,這里可以看出,當一個自動變量使用 __block
修飾之后,原來的自動變量被生成一個結構體,而結構體中的一個成員變量持有原來的自動變量
在這個結構體中,可以看到一個 __forwarding
指針,這個指針是指向自己的,它的作用就是無論當前 block 是處于棧中,還是堆中,都能夠準確的進行訪問,所以后面就是通過 __forwarding
指針來訪問成員變量 a
的,這里引用《Objective-C 高級編程》中的一張圖片來解釋一下:
- 最初 block 在棧上,那么 forwarding 指針指向自身的
__block
變量結構體 - 在 block 被復制到堆上時,會將 forwarding 的值替換為堆上的目標 block 變量用結構體實例的地址,而在堆上的目標 block 變量自己的 forwarding 的值就指向它自己
而在使用變量的時候,如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 20;
printf("%d\n", (a->__forwarding->a));
}
可以看出,在調用 block 之后,會生成一個 __Block_byref_a_0
類型的指針指向 block 結 構體中的封裝截獲變量的結構體成員變量,然后通過 __Block_byref_a_0
結構體中的 __forwarding
指向目標 block 變量進行修改
從源碼中我們看到多了兩個函數:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
在 OC 中,C 語言結構體不能含有附有 __strong
修飾符的變量,因為編譯器不知道應該什么時候進行 C 語言結構體的初始化和廢棄操作,不能很好的管理內存,但是 OC 中的運行時庫能夠準確把握 block 從棧復制到堆以及堆上的 block 被廢棄的時機,實際上就是使用了 __main_block_copy_0
和 __main_block_dispose_0
這兩個函數.
其中,_Block_object_assign
相當于 retain 操作,將對象賦值在對象類型的結構體成員變量中。
_Block_object_dispose
相當于 release 操作。
調用 copy 函數和 dispose 函數的時機:
函數 | 調用時機 |
---|---|
copy | 棧上的 block 復制到堆上時 |
dispose | 堆上的 block 被廢棄時 |
棧上的 block 復制到堆上的情況
- 調用 block 的 copy 函數時
在調用 block 的 copy 實例方法時,如果此時 block 在棧上,那么會被復制到堆上 - Block 作為函數返回值返回時
調用_Block_copy
函數 - 將 Block 賦值給附有 __strong 修飾符 id 類型的類或者 Block 類型成員變量時
調用_Block_copy
函數 - 方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中傳遞 Block 時
調用_Block_copy
函數或者 block 的 copy 實例方法
堆上的 block 被廢棄的情況
- 堆上的 Block 被釋放后,誰都不再持有Block時調用 dispose 函數
block 的存儲域
前面說到,block 可以看做是一個 OC 對象,從源碼中,我們可以看到 block 中的 isa 指針指向的是 &_NSConcreteStackBlock
, 那么就可以說,此時 block 的類為 _NSConcreteStackBlock
,和這個類相似的還有:
- _NSConcreteStackBlock(棧區)
- _NSConcreteMallocBlock(堆區)
- _NSConcreteGlobalBlock(數據區)
NSConcreteGlobalBlock
在兩種情況下,block 會存在于數據區,即isa 指針指向_NSConcreteGlobalBlock:
- 記述全局變量的地方有 block 語法時
測試代碼:
void(^globalBlk)() = ^{};
int main(int argc, char * argv[]) {
return 0;
}
- block 的語法表達式中不使用應該截獲的自動變量時
NSConcreteStackBlock
除了以上兩種情況外生成的 block 語法生成的 block 都是 NSConcreteStackBlock 類對象
NSConcreteMallocBlock
配置在全局變量上的 block,從變量作用域外也可以通過指針安全的使用,但是設置在棧上的 block,如果其所屬的變量作用域結束,該 block 就會被廢棄,由于 __block
變量也是在棧上的,同理,如果其所屬的變量作用域結束,該變量就會被廢棄.
為了解決這個問題,通過將 block 和 __block
變量復制到堆上來解決,這樣即使其所屬的變量作用域結束,堆上的 block 依然存在,復制到堆上的 block 的 isa 變為 _NSConcreteMallocBlock
三種類型 block 被復制之后:
類型 | 存儲位置 | 復制之后 |
---|---|---|
NSConcreteGlobalBlock | 數據區 | 什么也不做 |
NSConcreteStackBlock | 棧上 | 從棧上復制到堆上 |
NSConcreteMallocBlock | 堆上 | 引用計數增加 |
在 block 中使用對象類型自動變量時,除以下幾種情況外,推薦手動調用 block 的 copy 方法:
- block 作為函數值返回的時候
- 將 Block 賦值給附有 __strong 修飾符 id 類型的類或者 Block 類型成員變量時
- 向方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中傳遞 Block 時
當一個 block 被復制到堆上的時候,__block
變量也會被復制到堆上,并且被這個 block 所持有,如果又有 block 被復制到堆上,那么 __block
變量的引用計數會增加,然后如果 block 被廢棄,那么就不再持有 __block
變量.
block 循環引用
如果在 block 中使用附有 __strong
修飾符的對象類型自動變量,那么當 block 從棧上復制到堆上的時候,該對象被 block 所持有,這樣就容易發生循環引用
例如下面這份代碼:
typedef void(^blk)(void);
interface BlockTest : NSObject
{
blk blk_;
}
@implementation BlockTest
- (instancetype)init
{
self = [super init];
blk_ = ^{
NSLog(@"self----------------%@",self);
};
return self;
}
@end
上面這份代碼中就出現了循環引用,即BlockTest 類對象持有 block,而在 block 中使用持有 __strong
修飾符的 id 類型變量 self,二者互相持有,導致都無法釋放,這就是循環引用
如下圖所示:
在編譯器進行代碼編譯的時候,可以檢測出這種循環引用,并且發出警告,但是并不是任何情況下都能有警告,這里需要注意
為了避免循環引用,這里有幾種方法,我們一個一個看
__weak 修飾符
即如下代碼:
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
blk_ = ^{
NSLog(@"self---------------%@",weakSelf);
};
return self;
}
加上 __weak
修飾符之后,block 不再是持有 self,而是持有對對象的弱引用,也就是 self 強引用 block,而 block 弱引用 self,這樣就不會發生循環引用
__block 修飾符
先看一下如下代碼:
- (instancetype)init
{
self = [super init];
__block id tempSelf = self;
blk_ = ^{
NSLog(@"self = %@",tempSelf);
tempSelf = nil;
};
return self;
}
- (void)dellocBlk
{
blk_();
}
在上面的代碼中,乍一看二者還是互相引用,但是在 block 內部執行完畢之后有了一個將 tempSelf 置為 nil 的操作,這樣就是將 self 對 block 的強引用切斷,那么循環引用被破壞,但是這樣有一個缺點,那就是如果不執行 blk_ 這個 block,這個循環引用將會永遠存在,只有執行了這個 block,tempSelf 才會被置為 nil,循環引用才會被破壞,但是這種方案并非沒有優點,很明顯的一個優點就是我們可以依靠這個 block 來控制對象的持有時間,這樣可以控制對象的釋放時間.
Strong-Weak Dance
在解決 block 的循環引用時,除了使用 __weak
修飾符外,還有一個被蘋果稱為:“Strong-Weak Dance” 的方法,那這個是什么呢?先看一下代碼:
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
_blk = ^{
id __strong strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"self---------------%@", strongSelf);
}
};
return self;
}
是不是很奇怪,我們好不容易使用 __weak
消除的強引用,為什么在里面又進行了強引用呢?也就是強引用->弱引用->強引用這個過程的作用是什么?我們先來看一下普通的 __weak
有什么問題
__weak BlockTest *weakSelf = self;
self.completionHandler = ^(NSInteger result) {
[weakSelf.property removeObserver: weakSelf forKeyPath:@"pathName"];
};
在上面的代碼中,__weak
確實解決了循環引用的問題,但是假設當前 block 在子線程中執行,但是在執行的時候 self 被釋放了,這時候 weakSelf 也被置為 nil,但是我們使用 KVO 來進行移除操作,這時候就會發生崩潰
這時候就需要 Strong-Weak Dance 登場了,還是先來看代碼:
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
_blk = ^{
id __strong strongSelf = weakSelf;
NSLog(@"self---------------%@", strongSelf);
};
return self;
}
在 block 內部對 self 又進行一次強引用之后,self 所指對象的引用計數變為 2 ,即使主線程中的 self 被釋放,那么對象的引用計數變為1,此時對象并沒有銷毀
這時候,可能會有人有問題,在里面對 self 的對象進行了強引用,這樣豈不是又變成了互相持有,循環引用?
這個時候需要注意,block 只有截獲外部變量時,才會引用它。如果是內部新建一個,則沒有任何問題。
但是這樣真的能夠解決多線程下,weakSelf 指向的對象在 block 執行之前被釋放的問題嗎?如果在執行 id __strong strongSelf = weakSelf
之前 weakSelf 指向的對象就已經被釋放了,那么這個語句就沒有任何意義了,在開始的那個 KVO 的例子中,崩潰還是依然會發生的,所以此時的 Strong-Weak Dance 沒有任何作用,所以為了保證安全性,我們可以在 block 內部進行 strongSelf 的 nil 檢測,即:
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
_blk = ^{
id __strong strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"self---------------%@", strongSelf);
}
};
return self;
}