block是C語言的擴充功能薛训。帶有自動變量的匿名函數(shù)
截獲自動變量值
保存該自動變量的瞬間值
int main() {
int dmy = 256;
int val = 10;
const char *fmt = "f";
void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
return 0;
}
block語法表達式中使用的自動變量被作為成員變量追加到了__main_block_impl_0結(jié)構(gòu)體中
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
const char *fmt;
int val;
}
使用執(zhí)行block語法時的自動變量fmt和val來初始化__main_block_impl_0結(jié)構(gòu)體實例。
impl.isa = &_NSConcreteStackBlock;
fmt = "f";
val = 10;
通過block使用的匿名函數(shù),實際上被作為簡單的C語音函數(shù)來處理典阵。即自動變量瞬間值被截獲
// id 為objc_object結(jié)構(gòu)體的指針類型
typedef struct objc_object {
Class isa;
} *id;
// Class 為objc_class結(jié)構(gòu)體的指針類型
typedef struct objc_class *Class;
struct objc_class {
Class isa;
};
總的來說,所謂‘截獲自動變量的值’意味著在執(zhí)行block語法時,block語法表達式所使用的自動變量值被保存到block的結(jié)構(gòu)體實例中
// OC 類聲明
@interface MyObject : NSObject {
int val0;
int val1;
}
@end
// 該類的對象的結(jié)構(gòu)體
struct MyObject {
Class isa;
int val0;
int val1;
};
MyObject類的實例變量val0和val1被直接聲明為對象的結(jié)構(gòu)體成員。
各類的結(jié)構(gòu)體就是基于objc_class結(jié)構(gòu)體的class_t結(jié)構(gòu)體
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
}
在OC中惧财,比如NSObject的class_t結(jié)構(gòu)體實例,以及NSMutableArray的class_t結(jié)構(gòu)體實例等扭仁,均生成并保持各個類的class_t結(jié)構(gòu)體實例垮衷。該實例持有聲明的成員變量、方法的名稱乖坠、方法的實現(xiàn)(即函數(shù)指針)搀突、屬性以及父類的指針。
// block結(jié)構(gòu)體
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0 *Desc;
}
此__main_block_impl_0結(jié)構(gòu)體相當于基于objc_objct結(jié)構(gòu)體的OC類對象的結(jié)構(gòu)體熊泵。
對其中的成員變量 isa 進行初始化
isa = &_NSConcreteStackBlock;
即_NSConcreteStackBlock相當于class_t結(jié)構(gòu)體實例仰迁,在將block作為OC的對象處理時,關(guān)于該類的信息放置于_NSConcreteStackBlock中顽分。所以block的實質(zhì)就是OC的對象
__block
__block同block一樣變成__Block_byref_val_o結(jié)構(gòu)體類型的自動變量轩勘,即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實例。該變量初始化為10怯邪,且這個值也出現(xiàn)在結(jié)構(gòu)體的初始化中,這意味著該結(jié)構(gòu)體持有相當于原自動變量的成員變量花墩。
__block int val = 10;
void (^blk)(void) = ^{val = 1;};
// 編譯器代碼
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 * __forwarding;
int val;
}
struct __main_block_impl_0 {
__Block_byref_val_0 *val;
...
}
block的__main_block_impl_0結(jié)構(gòu)體實例持有指向__block變量的__Block_byref_val_0結(jié)構(gòu)體實例的指針悬秉。
__Block_byref_val_0結(jié)構(gòu)體實例的成員變量__forwarding持有指向該實例自身的指針
另外澄步,__block變量的__Block_byref_val_0結(jié)構(gòu)體并不在Block的__main_block_impl_0結(jié)構(gòu)體中。這樣做是為了在多個Block中使用同一__block變量和泌。
Block存儲
名稱 | 實質(zhì) |
---|---|
Block | 棧上Block的結(jié)構(gòu)體實例 |
__block變量 | 棧上__block變量的結(jié)構(gòu)體實例 |
類 | 存儲域 |
---|---|
NSConcreteStackBlock | 棧 |
NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū) |
NSConcreteMallocBlock | 堆 |
typedef int (^blk_t)(int);
blk_t blk = ^(int count){return count};
不截獲自動變量的值村缸,放置在程序的數(shù)據(jù)區(qū),其他的block放置在棧上
block超出變量作用域可存在的原因 武氓?
__block變量用結(jié)構(gòu)體成員變量__forwarding存在的原因 梯皿?
配置在全局變量上的block,從變量作用域外也可以通過指針安全的使用县恕,但設(shè)置在棧上的block东羹,__block變量,如果其所屬的變量作用域結(jié)束忠烛,該block属提,__block變量就被廢棄。
將配置在棧上的block復(fù)制到堆上美尸,這樣即使block變量的作用域結(jié)束冤议,堆上的block還可以繼續(xù)存在。
復(fù)制到堆上的block將_NSConcreteMallocBlock類對象寫入block的結(jié)構(gòu)體實例的成員變量isa
impl.isa = &_NSConcreteMallocBlock;
__block變量用結(jié)構(gòu)體成員變量__forwarding可以實現(xiàn)無論__block變量配置在棧上還是堆上师坎,都能正確的訪問__block變量恕酸。在__block變量配置在堆上時,只要棧上的結(jié)構(gòu)體實例成員變量__forwarding指向堆上的結(jié)構(gòu)體實例胯陋。
一般編譯器自行判斷是否將block從棧上copy到堆上
編譯器不能進行判斷的情況:向方法或函數(shù)的參數(shù)中傳遞block時
下面的方法或函數(shù)不用手動復(fù)制
1.Cocoa框架的方法且方法名中含有usingBlock
NSArray enumerateObjectsUsingBlock
2.CGD 的API
dispatch_async函數(shù)
- (id)getBlockArray{
int val = 10;
return [[NSArray alloc] initWithObjects: ^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);}, nil];
}
- (void)testBlock{
id obj = getBlockArray();
typedef void (^blk_t) (void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk(); // 異常
}
// getBlockArray 函數(shù)執(zhí)行結(jié)束時蕊温,棧上的Block已被廢棄
// 修正 如果block不截取自動變量則可正確運行
- (id)getBlockArray{
int val = 10;
return [[NSArray alloc] initWithObjects: [^{NSLog(@"blk0:%d",val);} copy],
[^{NSLog(@"blk1:%d",val);} copy], nil];
}
block類 | 副本源的配置存儲域 | 復(fù)制效果
------- | -------
_NSConcreteStackBlock | 棧 | 從棧復(fù)制到堆
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū) | 什么也不做
_NSConcreteMallocBlock | 堆 | 引用計數(shù)增加
不管block配置在何處,用copy方法復(fù)制都不會引起任何問題惶岭,在不確定時調(diào)用copy即可
__block變量存儲域
__block變量的配置存儲 | block從棧上復(fù)制到堆時的影響 |
---|---|
棧 | 從棧復(fù)制到堆寿弱,并被block持有 |
堆 | 被block持有 |
將棧上的__block變量結(jié)構(gòu)體實例復(fù)制到堆上時,會將成員變量_forwarding的值替換為復(fù)制目標堆上的__block變量結(jié)構(gòu)體實例的地址