這一章講解了Block相關的知識。由於做者將Objective-C的代碼轉成了C++的代碼,因此第一次看的時候很是吃力,我本身也不記得看了多少遍了。git
這篇總結不只僅只有這本書中的內容,還有一點在其餘博客裏看過的Block的相關知識,並加上了本身的理解,並且文章結構也和原書不太一致,是通過個人整理從新排列出來的。程序員
先看一下本文結構(Blocks部分):github
由於須要看Block操做的C++源碼,因此須要知道轉換的方法,本身轉過來看一看:編程
clang -rewrite-objc block.m
,就會在當前文件夾內自動生成對應的block.cpp文件。c語言的函數中可能使用的變量:數組
並且,因爲存儲區域特殊,這其中有三種變量是能夠在任什麼時候候以任何狀態調用的:多線程
而其餘兩種,則是有各自相應的做用域,超過做用域後,會被銷燬。閉包
好了,知道了這兩點,理解下面的內容就容易一些了。框架
先說結論:Block實質是Objective-C對閉包的對象實現,簡單說來,Block就是對象。ide
下面分別從表層到底層來分析一下:函數
Block是一種類型,一旦使用了Block就至關於生成了可賦值給Block類型變量的值。舉個例子:
int (^blk)(int) = ^(int count){
return count + 1;
};
複製代碼
若是咱們在項目中常用某種相同類型的block,咱們能夠用typedef
來抽象出這種類型的Block:
typedef int(^AddOneBlock)(int count);
AddOneBlock block = ^(int count){
return count + 1;//具體實現代碼
};
複製代碼
這樣一來,block的賦值和傳遞就變得相對方便一些了, 由於block的類型已經抽象了出來。
Block其實就是Objective-C對象,由於它的結構體中含有isa指針。
下面將Objective-C的代碼轉化爲C++的代碼來看一下block的實現。
OC代碼:
int main()
{
void (^blk)(void) = ^{
printf("Block\n");
};
return 0;
}
複製代碼
C++代碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//block結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//Block構造函數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//isa指針
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//未來被調用的block內部的代碼:block值被轉換爲C的函數代碼
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
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)};
//main 函數
int main()
{
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
return 0;
}
複製代碼
首先咱們看一下從原來的block值(OC代碼塊)轉化而來的C++代碼:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
複製代碼
這裏,*__cself 是指向Block的值的指針,也就至關因而Block的值它本身(至關於C++裏的this,OC裏的self)。
並且很容易看出來,__cself 是指向__main_block_impl_0結構體實現的指針。 結合上句話,也就是說Block結構體就是__main_block_impl_0結構體。Block的值就是經過__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_block_impl_0結構體有三個部分:
第一個是成員變量impl,它是實際的函數指針,它指向__main_block_func_0。來看一下它的結構體的聲明:
struct __block_impl {
void *isa;
int Flags;
int Reserved; //從此版本升級所需的區域
void *FuncPtr; //函數指針
};
複製代碼
第二個是成員變量是指向__main_block_desc_0結構體的Desc指針,是用於描述當前這個block的附加信息的,包括結構體的大小等等信息
static struct __main_block_desc_0 {
size_t reserved; //從此升級版本所需區域
size_t Block_size;//block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
複製代碼
第三個部分是__main_block_impl_0結構體的構造函數,__main_block_impl_0 就是該 block 的實現
__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指針保持這所屬類的結構體的實例的指針。__main_block_imlp_0結構體就至關於Objective-C類對象的結構體,這裏的_NSConcreteStackBlock至關於Block的結構體實例,也就是說block其實就是Objective-C對於閉包的對象實現。
使用Block的時候,不只可使用其內部的參數,還可使用Block外部的局部變量。而一旦在Block內部使用了其外部變量,這些變量就會被Block保存。
有趣的是,即便在Block外部修改這些變量,存在於Block內部的這些變量也不會被修改。來看一下代碼:
int a = 10;
int b = 20;
PrintTwoIntBlock block = ^(){
printf("%d, %d\n",a,b);
};
block();//10 20
a += 10;
b += 30;
printf("%d, %d\n",a,b);//20 50
block();//10 20
複製代碼
咱們能夠看到,在外部修改a,b的值之後,再次調用block時,裏面的打印仍然和以前是同樣的。給人的感受是,外部到局部變量和被Block內部截獲的變量並非同一份。
那若是在內部修改a,b的值會怎麼樣呢?
int a = 10;
int b = 20;
PrintTwoIntBlock block = ^(){
//編譯不經過
a = 30;
b = 10;
};
block();
複製代碼
若是不進行額外操做,局部變量一旦被Block保存,在Block內部就不能被修改了。
可是須要注意的是,這裏的修改是指整個變量的賦值操做,變動該對象的操做是容許的,好比在不加上__block修飾符的狀況下,給在block內部的可變數組添加對象的操做是能夠的。
NSMutableArray *array = [[NSMutableArray alloc] init];
NSLog(@"%@",array); //@[]
PrintTwoIntBlock block = ^(){
[array addObject:@1];
};
block();
NSLog(@"%@",array);//@[1]
複製代碼
OK,如今咱們知道了三點:
爲了解釋2,3點,咱們經過C++的代碼來看一下Block在截獲變量的時候都發生了什麼: C代碼:
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "var = %d\n";
void (^blk)(void) = ^{
printf(fmt,val);
};
val = 2;
fmt = "These values were changed. var = %d\n";
blk();
return 0;
}
複製代碼
C++代碼:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt; //被添加
int val; //被添加
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);
}
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 dmy = 256;
int val = 10;
const char *fmt = "var = %d\n";
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
val = 2;
fmt = "These values were changed. var = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
複製代碼
單獨抽取__main_block_impl_0來看一下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt; //截獲的自動變量
int val; //截獲的自動變量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
再來看一下函數體的代碼:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);
}
複製代碼
從這裏看就更明顯了:fmt,var都是從__cself裏面獲取的,更說明了兩者是屬於block的。並且從註釋來看(註釋是由clang自動生成的),這兩個變量是值傳遞,而不是指針傳遞,也就是說Block僅僅截獲自動變量的值,因此這就解釋了即便改變了外部的自動變量的值,也不會影響Block內部的值。
那爲何在默認狀況下改變Block內部到變量會致使編譯不經過呢? 個人思考是:既然咱們沒法在Block中改變外部變量的值,因此也就沒有必要在Block內部改變變量的值了,由於Block內部和外部的變量其實是兩種不一樣的存在:前者是Block內部結構體的一個成員變量,後者是在棧區裏的臨時變量。
如今咱們知道:被截獲的自動變量的值是沒法直接修改的,可是有兩個方法能夠解決這個問題:
咱們仍是用OC和C++代碼的對比看一下具體的實現:
OC代碼:
int global_val = 1;//全局變量
static int static_global_val = 2;//全局靜態變量
int main()
{
static int static_val = 3;//靜態變量
void (^blk)(void) = ^{
global_val *=1;
static_global_val *=2;
static_val *=3;
};
return 0;
}
複製代碼
C++代碼:
int global_val = 1;
static int static_global_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *=1;
static_global_val *=2;
(*static_val) *=3;
}
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()
{
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
return 0;
}
複製代碼
咱們能夠看到,
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *=1;
static_global_val *=2;
(*static_val) *=3;
}
複製代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;//是指針,不是值
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
那麼有什麼方法能夠在Block內部給變量賦值呢?-- 經過__block關鍵字。在講解__block關鍵字以前,講解一下Block截獲對象:
咱們看一下在block裏截獲了array對象的代碼,array超過了其做用域存在:
blk_t blk;
{
id array = [NSMutableArray new];
blk = [^(id object){
[array addObject:object];
NSLog(@"array count = %ld",[array count]);
} copy];
}
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
複製代碼
輸出:
block_demo[28963:1629127] array count = 1
block_demo[28963:1629127] array count = 2
block_demo[28963:1629127] array count = 3
複製代碼
看一下C++代碼:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;//截獲的對象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
值得注意的是,在OC中,C結構體裏不能含有被__strong修飾的變量,由於編譯器不知道應該什麼時候初始化和廢棄C結構體。可是OC的運行時庫可以準確把握Block從棧複製到堆,以及堆上的block被廢棄的時機,在實現上是經過__main_block_copy_0函數和__main_block_dispose_0函數進行的:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
複製代碼
其中,_Block_object_assign至關於retain操做,將對象賦值在對象類型的結構體成員變量中。 _Block_object_dispose至關於release操做。
這兩個函數調用的時機是在何時呢?
函數 | 被調用時機 |
---|---|
__main_block_copy_0 | 從棧複製到堆時 |
__main_block_dispose_0 | 堆上的Block被廢棄時 |
何時棧上的Block會被複制到堆呢?
何時Block被廢棄呢?
堆上的Block被釋放後,誰都再也不持有Block時調用dispose函數。
__weak關鍵字:
{
id array = [NSMutableArray new];
id __weak array2 = array;
blk = ^(id object){
[array2 addObject:object];
NSLog(@"array count = %ld",[array2 count]);
};
}
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
複製代碼
輸出:
block_demo[32084:1704240] array count = 0
block_demo[32084:1704240] array count = 0
block_demo[32084:1704240] array count = 0
複製代碼
由於array在變量做用域結束時被釋放,nil被賦值給了array2中。
先經過OC代碼來看一下給局部變量添加__block關鍵字後的效果:
__block int a = 10;
int b = 20;
PrintTwoIntBlock block = ^(){
a -= 10;
printf("%d, %d\n",a,b);
};
block();//0 20
a += 20;
b += 30;
printf("%d, %d\n",a,b);//20 50
block();/10 20
複製代碼
咱們能夠看到,__block變量在block內部就能夠被修改了。
加上__block以後的變量稱之爲__block變量,
先簡單說一下__block的做用: __block說明符用於指定將變量值設置到哪一個存儲區域中,也就是說,當自動變量加上__block說明符以後,會改變這個自動變量的存儲區域。
接下來咱們仍是用clang工具看一下C++的代碼:
OC代碼
int main()
{
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
return 0;
}
複製代碼
C++代碼
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
複製代碼
在__main_block_impl_0裏面發生了什麼呢?
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
>__main_block_impl_0裏面增長了一個成員變量,它是一個結構體指針,指向了 __Block_byref_val_0結構體的一個實例。那麼這個結構體是什麼呢?
這個結構體是變量val在被__block修飾後生成的!!
該結構體聲明以下:
```objc
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
複製代碼
咱們能夠看到,這個結構體最後的成員變量就至關於原來自動變量。 這裏有兩個成員變量須要特別注意:
用一張圖來直觀看一下:
咱們能夠看到,這裏面增長了指向__Block_byref_val_0結構體實例的指針。這裏//by ref這個由clang生成的註釋,說明它是經過指針來引用__Block_byref_val_0結構體實例val的。
所以__Block_byref_val_0結構體並不在__main_block_impl_0結構體中,目的是爲了使得多個Block中使用__block變量。
舉個例子:
int main()
{
__block int val = 10;
void (^blk0)(void) = ^{
val = 12;
};
void (^blk1)(void) = ^{
val = 13;
};
return 0;
}
複製代碼
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
void (*blk1)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
複製代碼
咱們能夠看到,在main函數裏,兩個block都引用了__Block_byref_val_0結構體的實例val。
那麼__block修飾對象的時候是怎麼樣的呢?
__block能夠指定任何類型的自動變量。下面來指定id類型的對象:
看一下__block變量的結構體:
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id obj;
};
複製代碼
被__strong修飾的id類型或對象類型自動變量的copy和dispose方法:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
複製代碼
一樣,當Block持有被__strong修飾的id類型或對象類型自動變量時:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
能夠看到,obj被添加到了__main_block_impl_0結構體中,它是__Block_byref_obj_0類型。
細心的同窗會發現,在上面Block的構造函數__main_block_impl_0中的isa指針指向的是&_NSConcreteStackBlock,它表示當前的Block位於棧區中。實際上,一共有三種類型的Block:
Block的類 | 存儲域 | 拷貝效果 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧拷貝到堆 |
_NSConcreteGlobalBlock | 程序的數據區域 | 什麼也不作 |
_NSConcreteMallocBlock | 堆 | 引用計數增長 |
由於全局Block的結構體實例設置在程序的數據存儲區,因此能夠在程序的任意位置經過指針來訪問,它的產生條件:
以上兩個條件只要知足一個就能夠產生全局Block,下面分別用C++來展現一下第一種條件下的全局Block:
c代碼:
void (^blk)(void) = ^{printf("Global Block\n");};
int main()
{
blk();
}
複製代碼
C++代碼:
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;//全局
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
printf("Global Block\n");}
static struct __blk_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blk_block_desc_0_DATA = { 0, sizeof(struct __blk_block_impl_0)};
static __blk_block_impl_0 __global_blk_block_impl_0((void *)__blk_block_func_0, &__blk_block_desc_0_DATA);
void (*blk)(void) = ((void (*)())&__global_blk_block_impl_0);
int main()
{
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
複製代碼
咱們能夠看到Block結構體構造函數裏面isa指針被賦予的是&_NSConcreteGlobalBlock,說明它是一個全局Block。
在生成Block之後,若是這個Block不是全局Block,那麼它就是爲_NSConcreteStackBlock對象,可是若是其所屬的變量做用域名結束,該block就被廢棄。在棧上的__block變量也是如此。
可是,若是Block變量和__block變量複製到了堆上之後,則再也不會受到變量做用域結束的影響了,由於它變成了堆Block:
將棧block複製到堆之後,block結構體的isa成員變量變成了_NSConcreteMallocBlock。
其餘兩個類型的Block在被複制後會發生什麼呢?
Block類型 | 存儲位置 | copy操做的影響 |
---|---|---|
_NSConcreteGlobalBlock | 程序的數據區域 | 什麼也不作 |
_NSConcreteStackBlock | 棧 | 從棧拷貝到堆 |
_NSConcreteMallocBlock | 堆 | 引用計數增長 |
而大多數狀況下,編譯器會進行判斷,自動將block從棧上覆制到堆:
除了這兩種狀況,基本都須要咱們手動複製block。
那麼__block變量在Block執行copy操做後會發生什麼呢?
若是在Block內部使用__strong修飾符的對象類型的自動變量,那麼當Block從棧複製到堆的時候,該對象就會被Block所持有。
因此若是這個對象還同時持有Block的話,就容易發生循環引用。
typedef void(^blk_t)(void);
@interface Person : NSObject
{
blk_t blk_;
}
@implementation Person
- (instancetype)init
{
self = [super init];
blk_ = ^{
NSLog(@"self = %@",self);
};
return self;
}
@end
複製代碼
Block blk_t持有self,而self也同時持有做爲成員變量的blk_t
- (instancetype)init
{
self = [super init];
id __weak weakSelf = self;
blk_ = ^{
NSLog(@"self = %@",weakSelf);
};
return self;
}
複製代碼
typedef void(^blk_t)(void);
@interface Person : NSObject
{
blk_t blk_;
id obj_;
}
@implementation Person
- (instancetype)init
{
self = [super init];
blk_ = ^{
NSLog(@"obj_ = %@",obj_);//循環引用警告
};
return self;
}
複製代碼
Block語法內的obj_截獲了self,由於ojb_是self的成員變量,所以,block若是想持有obj_,就必須引用先引用self,因此一樣會形成循環引用。就比如你若是想去某個商場裏的咖啡廳,就須要先知道商場在哪裏同樣。
若是某個屬性用的是weak關鍵字呢?
@interface Person()
@property (nonatomic, weak) NSArray *array;
@end
@implementation Person
- (instancetype)init
{
self = [super init];
blk_ = ^{
NSLog(@"array = %@",_array);//循環引用警告
};
return self;
}
複製代碼
仍是會有循環引用的警告提示,由於循環引用的是self和block之間的事情,這個被Block持有的成員變量是strong仍是weak都沒有關係,並且即便是基本類型(assign)也是同樣。
@interface Person()
@property (nonatomic, assign) NSInteger index;
@end
@implementation Person
- (instancetype)init
{
self = [super init];
blk_ = ^{
NSLog(@"index = %ld",_index);//循環引用警告
};
return self;
}
複製代碼
- (instancetype)init
{
self = [super init];
__block id temp = self;//temp持有self
//self持有blk_
blk_ = ^{
NSLog(@"self = %@",temp);//blk_持有temp
temp = nil;
};
return self;
}
- (void)execBlc
{
blk_();
}
複製代碼
因此若是不執行blk_(將temp設爲nil),則沒法打破這個循環。
一旦執行了blk_,就只有
使用__block 避免循環比較有什麼特色呢?
因此咱們應該根據實際狀況,根據當前Block的用途來決定到底用__block,仍是__weak或__unsafe_unretained。
本文已經同步到我的博客:傳送門
---------------------------- 2018年7月17日更新 ----------------------------
注意注意!!!
筆者在近期開通了我的公衆號,主要分享編程,讀書筆記,思考類的文章。
由於公衆號天天發佈的消息數有限制,因此到目前爲止尚未將全部過去的精選文章都發布在公衆號上,後續會逐步發佈的。
並且由於各大博客平臺的各類限制,後面還會在公衆號上發佈一些短小精幹,以小見大的乾貨文章哦~
掃下方的公衆號二維碼並點擊關注,期待與您的共同成長~