如下環境都在ARC環境下,常規設置,使用XCode10測試。html
Block做爲屬性聲明時爲何都聲明爲Copy?git
Block爲何能保存外部變量?程序員
Block中__block
關鍵字爲什麼能同步Block外部和內部的值?github
Block有幾種類型?objective-c
何時棧上的Block會複製到堆?編程
Block的循環引用應該如何處理?api
Block外部__weak typeof(self) weakSelf = self;
Block 內部 typeof(weakSelf) strongSelf = weakSelf;
,爲何須要這樣操做?數組
如下Block在ARC環境下能正常運行嗎?若能分別打印什麼值?閉包
void exampleA_addBlockToArray(NSMutableArray*array) {
char b = 'A';
[array addObject:^{
printf("%c\n", b);
}];
}
void exampleA() {
NSLog(@"---------- exampleA ---------- \n");
NSMutableArray *array = [NSMutableArray array];
exampleA_addBlockToArray(array);
void(^block)(void) = [array objectAtIndex:0];
block();
}
複製代碼
void exampleB_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("B\n");
}];
}
void exampleB() {
NSLog(@"---------- exampleB ---------- \n");
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void(^block)(void) = [array objectAtIndex:0];
block();
}
複製代碼
typedef void(^cBlock)(void);
cBlock exampleC_getBlock() {
char d = 'C';
return^{
printf("%c\n", d);
};
}
void exampleC() {
NSLog(@"---------- exampleC ---------- \n");
cBlock blk_c = exampleC_getBlock();
blk_c();
}
複製代碼
NSArray* exampleD_getBlockArray() {
int val = 10;
return [[NSArray alloc] initWithObjects:^{NSLog(@"blk1:%d",val);}, ^{NSLog(@"blk0:%d",val);}, ^{NSLog(@"blk0:%d",val);}, nil];
}
void exampleD() {
NSLog(@"---------- exampleD ---------- \n");
typedef void (^blk_t)(void);
NSArray *array = exampleD_getBlockArray();
NSLog(@"array count = %ld", [array count]);
blk_t blk = (blk_t)[array objectAtIndex:1];
blk();
}
複製代碼
NSArray* exampleE_getBlockArray() {
int val = 10;
NSMutableArray *mutableArray = [NSMutableArray new];
[mutableArray addObject:^{NSLog(@"blk0:%d",val);}];
[mutableArray addObject:^{NSLog(@"blk1:%d",val);}];
[mutableArray addObject:^{NSLog(@"blk2:%d",val);}];
return mutableArray;
}
void exampleE() {
NSLog(@"---------- exampleE ---------- \n");
typedef void (^blk_t)(void);
NSArray *array = exampleE_getBlockArray();
NSLog(@"array count = %ld", [array count]);
blk_t blk = (blk_t)[array objectAtIndex:1];
blk();
}
複製代碼
void exampleF() {
NSLog(@"---------- exampleF ---------- \n");
typedef void (^blk_f)(id obj);
__unsafe_unretained blk_f blk;
{
id array = [[NSMutableArray alloc] init];
blk = ^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
};
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
複製代碼
void exampleG() {
NSLog(@"---------- exampleG ---------- \n");
typedef void (^blk_f)(id obj);
blk_f blk;
{
id array = [[NSMutableArray alloc] init];
blk = ^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld", [array count]);
};
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
複製代碼
void exampleH() {
NSLog(@"---------- exampleH ---------- \n");
typedef void (^blk_f)(id obj);
blk_f blk;
{
id array = [[NSMutableArray alloc] init];
id __weak weakArray = array;
blk = ^(id obj) {
[weakArray addObject:obj];
NSLog(@"array count = %ld", [weakArray count]);
};
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
複製代碼
void exampleI() {
NSLog(@"---------- exampleI ---------- \n");
typedef void (^blk_g)(id obj);
blk_g blk;
{
id array = [[NSMutableArray alloc] init];
__block id __weak blockWeakArray = array;
blk = [^(id obj) {
[blockWeakArray addObject:obj];
NSLog(@"array count = %ld", [blockWeakArray count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
複製代碼
Objective-C中的Block中文名閉包,是C語言的擴充功能,是一個匿名函數而且能夠截獲(保存)局部變量。經過三個小節來解釋這個概念。app
程序語言 | Block的名稱 |
---|---|
Swift | Closures |
Smalltalk | Block |
Ruby | Block |
LISP | Lambda |
Python | Lambda |
Javascript | Anonymous function |
由於Block是在模仿C語言函數指針的寫法:
int func(int count) {
return count + 1;
}
// int (^tmpBlock)(int i) = ...
int (*funcptr)(int) = &func;
複製代碼
可是Block的寫法依舊很是難記,國外的朋友更是專門寫了一個叫fuckingblock網頁提供Block的各類寫法。
// 演示截取局部變量
int tmpVal = 10;
void (^blk)(void) = ^{
printf("val = %d", tmpVal); // val = 10
};
tmpVal = 2;
blk();
複製代碼
這裏依舊顯示val = 10
,Block會截取當前狀態下val
的值。至於爲何能截獲局部變量的值,咱們下一節中討論。
經過clang -rewrite-objc main.m
將上面的示例代碼翻譯成C,關鍵代碼以下:
// Block基礎結構
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
複製代碼
// 根據示例中blk的實現,生成不一樣的 __main_block_impl_0 結構體。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int tmpVal;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tmpVal, int flags=0) : tmpVal(_tmpVal) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
根據上面的代碼能解決咱們3個疑惑:
__block_impl
中有isa
指針,那麼Block
也是一個對象。__main_block_impl_0
,這裏結構裏面包含int tmpVal
就是咱們局部變量,而__main_block_impl_0
的構造函數中是值傳遞。因此block內部截獲的變量不受外部影響。__main_block_impl_0
構造函數中有個void *fp
函數指針指向的就是block實現。咱們向上面示例代碼再添加多一些變量類型:
static int outTmpVal = 30; // 靜態全局變量
int main(int argc, char * argv[]) {
int tmpVal = 10; // 局部變量
static int localTmpVal = 20; // 局部靜態變量
NSMutableArray *localMutArray = [NSMutableArray new]; // 局部OC對象
void (^blk)(void) = ^{
printf("val = %d\n", tmpVal); // val = 10
printf("localTmpVal = %d\n", localTmpVal); // localTmpVal = 21
printf("outTmpVal = %d\n", outTmpVal); // outTmpVal = 31
[localMutArray addObject:@"newObj"];
printf("localMutArray.count = %d\n", (int)localMutArray.count); // localMutArray.count = 2
};
tmpVal = 2;
localTmpVal = 21;
outTmpVal = 31;
[localMutArray addObject:@"startObj"];
blk();
}
複製代碼
對應輸出結果爲:
val = 10
localTmpVal = 21
outTmpVal = 31
localMutArray.count = 2
clang -rewrite-objc main.m
後關鍵代碼以下:
static int outTmpVal = 30;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int tmpVal;
int *localTmpVal;
NSMutableArray *localMutArray;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tmpVal, int *_localTmpVal, NSMutableArray *_localMutArray, int flags=0) : tmpVal(_tmpVal), localTmpVal(_localTmpVal), localMutArray(_localMutArray) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
由於涉及到OC對象,這裏還會有2個新的方法,這2個方法會放到後面講:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->localMutArray, (void*)src->localMutArray, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src){
_Block_object_dispose((void*)src->localMutArray, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
複製代碼
static int outTmpVal = 30;
儲存在內存中的.data
段,static
限制了做用域,該文件做用域內可修改。static int localTmpVal = 20;
在int main(int argc, char * argv[]) { }
做用域可修改,注意__main_block_impl_0
構造函數中是傳遞的*_localTmpVal
指針,因此外部修改Block內部一樣有效,由於是static
因此,Block內部也能夠修改localTmpVal
的值。NSMutableArray *localMutArray
向__main_block_impl_0
傳遞的是指向的地址,因此localMutArray
內部操做對於block內一樣有效。
- 靜態變量的這種方式一樣也能夠做用到局部變量上,傳遞一個指針到block內,經過指針來讀取指向的值,通知也能夠修改。可是這種方式在block離開局部變量所在做用域後再調用就會出現問題,由於局部變量已經被釋放。
static int localTmpVal = 20;
能經過指針的方式修改值,NSMutableArray *localMutArray
修改指向的值爲何不能夠? 這是clang對於Block內修改指針的一個保護措施。
總結下:
靜態變量
、 靜態全局變量
、全局變量
均可以訪問,修改,保持同一份值。一樣的方式,咱們先看__block
用C是怎麼實現的,下面是一段使用__block
的代碼:
int main(int argc, char * argv[]) {
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
printf("val = %d", val);
};
blk();
}
複製代碼
翻譯成C,只保留關鍵代碼:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
複製代碼
這就是__block
對應C中的新結構體:
*__forwarding
是一個與本身同類型的指針。int val;
這個變量就是爲了保存本來__block int val = 10;
的值。__block int val = 10;
對應的結構體__Block_byref_val_0
也是和以前同樣建立在棧上的。接下來繼續看,blk
的結構:
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.... // 和以前的__block_impl構造方式一致
};
複製代碼
blk
結構內部新增了__Block_byref_val_0 *val
做爲成員變量,和以前原理一致。
blk
的實現val = 1;
:
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;
printf("val = %d", (val->__forwarding->val));
}
複製代碼
(val->__forwarding->val) = 1;
這句很是重要,不是直接經過val->val
進行賦值操做,而是通過__forwarding
指針進行賦值,這帶來很是大的靈活性,如今是blk
和__block int val
都是在棧上,__forwarding
也都指向了棧上的__Block_byref_val_0
。以上代碼解決了在Block內修改外部局部變量的值。
__block
新增了2個方法:__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(&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*/);
}
複製代碼
經過方法命名和參數,能夠大體猜出是對Block
的拷貝和釋放。
經過以上clange
的編譯,Block和__block都是有isa指針的,二者都應該是Objective-C的對象。isa指向的就是它的類對象。在ARC下大體有如下幾種,根據名字能夠知道對應儲存空間:
clang轉出的結果和運行代碼時 Block 實際顯示的isa類型是不同的,在實際的編譯過程當中已經不會通過clang翻譯成C再編譯。
_NSConcreteGloalBlock
有兩種狀況下能夠生成:
_NSConcreteStackBlock
由於在棧上,在函數做用域內聲明的Block。
_NSConcreteMallocBlock
正由於_NSConcreteStackBlock
的做用域在棧上,超出做用域後想要繼續使用Block,這就得複製到堆上。那些狀況會觸發這種複製:
block
賦值給Strong
修飾的屬性時。Block
做爲一個返回值時(超出做用域還能使用,autorelease處理對象生命週期)。copy
。usingBlock
等時,不用外部copy
。內部已經進行copy。GCD
的Api,也不用外部copy
。這裏有個比較經典的例子(摘自《Objective-C高級編程》):
- (id)getBlockArray {
int val = 10;
return [[NSArray alloc] initWithObjects:^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);}, nil];
}
{
id obj = [self getBlockArray];
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
}
// crash
複製代碼
在ARC狀況下,NSArray 數組類會有2個元素,第一個在堆上,第二個棧上。在超出getBlockArray做用域後,第二棧上的block會變成野指針。在全部做用域結束時,Array會釋放數組內全部元素。野指針對象執行銷燬時會觸發崩潰。 正常狀況下
NSArray
應該持有數組內全部元素。但使用initWithObjects:
方法時,發現只有第一個元素進行了持有操做,第二個Block
依舊在棧上。當我使用NSMutableArray
的addObject:
方法時,每一個Block都會進行持有賦值到堆上。我懷疑應該是initWithObjects:
方法中多參形式比較特殊。
反覆提到Block就是OC的對象,對於對象Copy會帶來哪些變化:
Block類 | 原來儲存域 | 複製產生的影響 |
---|---|---|
_NSConcreteStackBlock | 棧 | 從棧複製到堆 |
_NSConcreteGlobalBlock | .data | 無變化 |
_NSConcreteMallocBlock | 堆 | 引用計數增長 |
Block是一個OC對象,因此涉及到從棧到堆,引用計數的變動等,常見OC對象內存管理的問題。同時Block在堆上時又會對__block
進行持有,那麼對於 __block
一樣也是OC對象,內存管理有什麼區別呢?
Block從棧複製到堆時對__block變量產生的影響:
__block 存儲域 | Block 從棧複製到堆時對__block的影響 |
---|---|
棧 | 從棧複製到堆並被Block持有 |
堆 | 被Block持有 |
__block
從棧上覆制到堆上後,本來棧上的__block
依舊會存在,被複制到堆上的__block
會被Block持有__block
的引用計數會增長,棧上的__block
會由於做用域結束而釋放,堆上的__block
會在引用計數歸零後釋放。__block
的內存管理就是OC對象的引用計數管理方式,沒有被其餘Block持有時引用計數歸0後釋放。上面提到當__block
從棧上覆制到堆上,會有兩個__block
產生,一個棧上的一個堆上的。這兩個不一樣儲存區域的__block
是如何實現數據同步的?
這就利用__block關鍵字如何實現?中提到的指向本身的*__forwarding
,當持有__block
的Block沒有從棧上拷貝到堆上時:*__forwarding
指向棧上的__block
, 當持有__block
的Block拷貝到堆上時後,棧上的__block
->__forwarding
->堆上的__block
,堆上的__block
->__forwarding
->堆上的__block
。讀起來有點繞,借用《Objective-C高級編程》中的插圖:
上面講了Block
和__block
在從棧上覆制到堆上時的一些變化。爲了解決__block
和OC對象
在Block結構體
內的生命週期問題,新增了一下幾個方法:
__main_block_desc_0
中新加2個成員方法:copy
和dispose
,這是兩個函數指針,指向的分別就是__main_block_copy_0
和__main_block_dispose_0
。Block
中使用OC對象
和__block
關鍵字時新增的2個方法:__main_block_copy_0
和 __main_block_dispose_0
,這兩個方法用於在Block
被 copy
到堆上時,管理__block
和OC對象
的生命週期。Block:
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*);
}
複製代碼
OC對象:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->localMutArray, (void*)src->localMutArray, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->localMutArray, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
複製代碼
__block:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign(&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*/);
}
複製代碼
捕獲OC對象
和使用__block
變量時在參數上會不一樣:
OC對象 | BLOCK_FIELD_IS_OBJECT |
---|---|
__block | BLOCK_FIELD_IS_BYREF |
_Block_object_assign
就至關於retain
方法,_Block_object_dispose
就至關於release
方法,可是咱們在clang翻譯的C語言中並無發現 __main_block_copy_0
和 __main_block_dispose_0
的調用。只有在如下時機copy
和dispose
方法纔會調用:
copy函數 | 棧上的Block複製到堆時 |
---|---|
dispose函數 | 堆上的Block被廢棄時(引用計數爲0) |
何時棧上的Block會複製到堆?
Block
的copy
實例方法。Block
做爲函數返回值返回時。(autorelease
對象延長生命週期)Block
賦值給附有__strong
修飾符的id類型的類或Block
類型成員變量(賦值給Strong
修飾的Block
類型屬性時,編譯器會幫忙複製到堆)。usingBlock
的Cocoa框架方法
或GCD
的api中傳遞Block
時。使用self.blockxxx()
時,使用clang
轉換成C時,能夠看到Bblock的調用實際是調用
Block`內的函數指針與OC對象調用發消息的形式不同。
其餘業務場景,好比使用self
的成員變量作NSAarry
或 NSDictionary
作增長操做時。
不要無腦使用,更加清晰的理解Weak-Strong-Dance
,Block
內部strong
self
後Block
會繼續持有self
,有些場景並不須要。
Strong
與Copy
效果都同樣。在ARC環境下編譯會自動將做爲屬性的Block
從棧Copy
到堆,這裏Apple建議繼續使用Copy
防止程序員忘記編譯器有Copy
動做。Block
結構體中會有建立一個成員變量與截獲的變量類型一直,這個值與截獲時的值一致,這是一個值傳遞,保存的是一個瞬時值。__block
關鍵字的實現是一個結構體,結構體中有個本身同類型的*_farwarding
指針,當Block在棧上,__block
也是在棧上時:*_farwarding
指向棧上的本身。當Block拷貝到堆,堆中建立的__block
的*_farwarding
指向本身,同時將棧上的*_farwarding
指向堆中__block
。copy
。2 做爲返回值返回。3 將Block
賦值給__strong
修飾的id類型
或Block
類型成員變量。4 方面名中含有usingBlock
的cocoa框架方法
或GCD
。__weak
弱引用,或者手動斷開強引用。Block
內的weakSelf
可能會出現nil
的狀況,nil
可能會形成奔潰或是其餘意外結果。因此在Block
內做用域內聲明一個Strong
類型的局部變量,在做用域結束後會自動釋放不會形成循環引用。編程題目答案,請參考Github上的repo:TestBlock。