Block的本質(zhì)
Block本質(zhì)上也是一個(gè)OC對(duì)象定页,它的內(nèi)部有一個(gè)isa指針剃法,block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的oc對(duì)象
首先巾兆,我們利用clang 命令查看一下聲明Block對(duì)應(yīng)的c++代碼(
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
)main.m 是文件名
轉(zhuǎn)換為C++
block 聲明刪減一部分轉(zhuǎn)化代碼嗓袱,如下
查找轉(zhuǎn)換后的C++代碼,其實(shí)block是有結(jié)構(gòu)體組成的,具體形式如下:
也可以理解為
因此赵讯,這個(gè)block結(jié)構(gòu)體 包含了isa 指針
再查找 __main_block_impl_0 函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
block實(shí)現(xiàn)的函數(shù) 的isa 指向_NSConcreteStackBlock 對(duì)象盈咳,因此說(shuō)明block 是oc對(duì)象
我們還可以自定義block結(jié)構(gòu)體耿眉,用于探測(cè)block 內(nèi)部實(shí)現(xiàn)過(guò)程
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
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 age;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
NSLog(@"this is a block!");
};
struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;
block(10, 10);
}
return 0;
}
為什么block內(nèi)部修改變量沒(méi)有效果
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)(void) = ^{
// age的值捕獲進(jìn)來(lái)(capture)
NSLog(@"age is %d", age);
};
age = 20;
block();
}
return 0;
}
為什么age的值被修改了,但是打印得到的是修改之前的值呢鱼响?
沒(méi)有其他參考途徑鸣剪,我們只有接著cpp文件
//相當(dāng)于OC對(duì)象
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
//相當(dāng)于OC 構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
// age(_age) 相當(dāng)于 把_age的值賦給age 此時(shí)所生成的block對(duì)象中的age的值就是_age,也就是傳遞過(guò)來(lái)的10
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
//相當(dāng)于調(diào)用了__main_block_impl_0函數(shù),并把a(bǔ)ge這個(gè)變量的值傳遞了過(guò)去丈积,
// void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age);
age = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
//因此在blcok 外部改變變量的值 block內(nèi)部并沒(méi)有發(fā)生改變
為什么 static 修飾的變量可以更改值
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
static int height = 10;
void (^block)(void) = ^{
// age的值捕獲進(jìn)來(lái)(capture)
NSLog(@"age is %d, height is %d", age, height);
};
age = 20;
height = 20;
block();
}
return 0;
}
//c++
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
static int height = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
age = 20;
height = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_4z_4p5xc0z55l38rrjdfyrzdxbw0000gn_T_main_2d6064_mi_1, age, (*height));
}
通過(guò)以上代碼筐骇,我們發(fā)現(xiàn)static 修飾的變量傳遞的是地址,在block內(nèi)部函數(shù)也訪問(wèn)的是(*height)而沒(méi)static修飾的age則直接訪問(wèn)的是變量
為什么static 修飾就傳遞地址呢江滨? 因?yàn)閟tatic修飾的變量 屬于靜態(tài)變量铛纬,首次被加載時(shí)static定義的變量被分配空間,程序結(jié)束后由系統(tǒng)釋放.所以它在內(nèi)存中是一直存在的,而被修飾的變量唬滑,在一定條件下是會(huì)被釋放的告唆,這是系統(tǒng)為了保持正確性,就把它的值傳遞過(guò)去晶密。
放上一個(gè)別人總結(jié)的圖擒悬,我覺(jué)得他能把上面的意思完全概括:
block的類型
前面說(shuō)過(guò)block是oc對(duì)象,所以我們可以調(diào)用oc方法來(lái)查看它的父類稻艰,以及父類的父類
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]);//__NSGlobalBlock__
NSLog(@"%@", [[block class] superclass]);//__NSGlobalBlock
NSLog(@"%@", [[[block class] superclass] superclass]);//NSBlock
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);//NSObject
所以很好的證明了block是OC對(duì)象懂牧。
但是在不同環(huán)境下,block的類型是不同的
* block有3種類型尊勿,可以通過(guò)調(diào)用class方法或者isa指針查看具體類型僧凤,最終都是繼承自NSBlock類型
* __NSGlobalBlock__( _NSConcreteGlobalBlock)
* __NSStackBlock__( _NSConcreteStackBlock )
* __NSMallocBlock__( _NSConcreteMallocBlock )
void (^block1)(void) = ^{
NSLog(@"Hello");
};
int age = 10;
void (^block2)(void) = ^{
NSLog(@"Hello - %d", age);
};
NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
NSLog(@"%d", age); //__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
} class]);
何為auto變量?
其實(shí)我們?cè)诙x局部變量時(shí)元扔,系統(tǒng)會(huì)默認(rèn)的添加 auto 關(guān)鍵字
// 特別注意是在MRC環(huán)境下躯保,因?yàn)锳RC環(huán)境,運(yùn)行時(shí)會(huì)做一系列的操作
// Global:沒(méi)有訪問(wèn)auto變量
void (^block1)(void) = ^{
NSLog(@"block1---------");
};
// Stack:訪問(wèn)了auto變量
int age = 10; // 相當(dāng)于 auto int age = 10;
void (^block2)(void) = ^{
NSLog(@"block2---------%d", age);
};
NSLog(@"%@ %@", [block1 class], [block2 class]);//__NSGlobalBlock__ __NSStackBlock__
Block內(nèi)部實(shí)現(xiàn)中(static struct __main_block_desc_0)的兩種形態(tài)
第1種
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
**第二種**
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};
相比較兩種 __main_block_desc_0 會(huì)發(fā)現(xiàn) 第二種(__main_block_desc_0)多出來(lái)兩個(gè)函數(shù)(copy摇展,dispose)吻氧,這是因?yàn)樵赽lock引用對(duì)象變量才會(huì)出現(xiàn)第二種情況(因?yàn)橐脤?duì)象變量時(shí),要涉及內(nèi)存管理咏连,要確保使用的對(duì)象不會(huì)出現(xiàn)內(nèi)存問(wèn)題盯孙,因此block內(nèi)部也會(huì)對(duì)其使用的對(duì)象做【return/release】)
block的copy
在ARC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上祟滴,比如以下情況
block作為函數(shù)返回值時(shí)
將block賦值給__strong指針時(shí)
block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
block作為GCD API的方法參數(shù)時(shí)
如果block被拷貝到堆上
1振惰、會(huì)調(diào)用block內(nèi)部的copy函數(shù)
2、copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
3垄懂、_Block_object_assign函數(shù)會(huì)根據(jù)變量的修飾符(__strong骑晶、__weak痛垛、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(return)或弱引用
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
//__main_block_desc_0 相比較之前沒(méi)有強(qiáng)弱引用變量時(shí)桶蛔,多出來(lái)copy dispose 函數(shù)
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};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
如果block從堆上移除
1匙头、會(huì)調(diào)用block內(nèi)部的dispose函數(shù)
2、dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
3仔雷、_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的變量(release)
__block修飾符
有時(shí)候我們要在block 中修改block之外的變量值蹂析,可以用static修改此變量,也可以設(shè)置為全局變量碟婆,但是我們有時(shí)只是臨時(shí)用下此變量电抚,沒(méi)有必要做如此復(fù)雜的操作
__block可以用于解決block內(nèi)部無(wú)法修改auto變量值的問(wèn)題
__block不能修飾全局變量、靜態(tài)變量(static)
編譯器會(huì)將__block變量包裝成一個(gè)對(duì)象
(分別討論一下(基本數(shù)據(jù)類型) int 和 對(duì)象的區(qū)別)
我們都知道__block修飾的變量 可以再block內(nèi)部對(duì)他進(jìn)行修改竖共,但這是為什么呢蝙叛?
__block修飾的變量會(huì)在運(yùn)行時(shí),由系統(tǒng)封裝成為 (Block_byref變量名序號(hào))結(jié)構(gòu)體類型的對(duì)象公给,我們?cè)赽lock內(nèi)部和之后訪問(wèn)的變量 都是轉(zhuǎn)換過(guò)后的類型
__block int age = 10;
// __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
__block NSObject *obj = [NSObject new];
// __attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
HFBlock block = ^{
age = 20;
obj = nil;
// __Block_byref_age_0 *age = __cself->age; // bound by ref
// __Block_byref_obj_1 *obj = __cself->obj; // bound by ref
//
// (age->__forwarding->age) = 20;
// (obj->__forwarding->obj) = __null;
NSLog(@"age is %d", age);
};
NSLog(@"%p", &age);//對(duì)應(yīng)轉(zhuǎn)換后
NSLog((NSString *)&__NSConstantStringImpl__var_folders_4z_4p5xc0z55l38rrjdfyrzdxbw0000gn_T_main_218c5b_mi_1, &(age.__forwarding->age)); 我們確定他訪問(wèn)的是對(duì)象中的age
轉(zhuǎn)換后
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__strong obj;
};
通過(guò)上述代碼我們得出結(jié)論借帘,變量在聲明__block后 就會(huì)轉(zhuǎn)為其他對(duì)象形式,并且在聲明以后用到的次變量都是__Block_byref _ 變量名 _序號(hào) 類型的
相比較基本數(shù)據(jù)類型轉(zhuǎn)換的類型妓布,對(duì)象類型的屬性轉(zhuǎn)換后姻蚓,會(huì)多出來(lái)(__Block_byref_id_object_copy,__Block_byref_id_object_dispose)兩個(gè)函數(shù)匣沼,這是因?yàn)檎玻瑢?duì)象類型要涉及到內(nèi)存管理,這兩個(gè)函數(shù)是內(nèi)存管理所用到的
為了從內(nèi)存地址角度證明 我們?cè)赽lock外訪問(wèn)的變量也是轉(zhuǎn)換過(guò)的類型释涛,
typedef void (^HFBlock) (void);
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
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;
struct __Block_byref_age_0 *age;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
__block NSObject *obj = [NSObject new];
HFBlock block = ^{
age = 20;
obj = nil;
NSLog(@"age is %d", age);
};
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%p", &age);//0x100425288
}
return 0;
}
我們需按照cpp代碼的思路自定義幾個(gè)機(jī)構(gòu)體加叁,來(lái)強(qiáng)制轉(zhuǎn)換以此來(lái)窺看系統(tǒng)內(nèi)部的操作,
(lldb) p/x blockImpl->age
(__Block_byref_age_0 *) $4 = 0x0000000100425270
(lldb) p/x &(blockImpl->age->age)
(int *) $5 = 0x0000000100425288
由此我們看到唇撬,block中age對(duì)象 距離訪問(wèn)的age的值 相差24個(gè)字節(jié)它匕,我們?cè)倏碼ge結(jié)構(gòu)體
struct __Block_byref_age_0 {//假設(shè)這個(gè)對(duì)象的內(nèi)存地址是0x0000000100425270
void *__isa;//指針8個(gè)字節(jié)
struct __Block_byref_age_0 *__forwarding;//指針8個(gè)字節(jié)
int __flags;//4個(gè)字節(jié)
int __size;//4個(gè)字節(jié)
//age 的地址是 0x0000000100425270 + 8 + 8 + 4 + 4 = 0x0000000100425288
int age;
};
這和我們的打印結(jié)果完全吻合,由此再次得到證明
__block的內(nèi)存管理
當(dāng)block在棧上時(shí)窖认,并不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用
當(dāng)block被copy到堆時(shí)
會(huì)調(diào)用block內(nèi)部的copy函數(shù)
copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
_Block_object_assign函數(shù)會(huì)對(duì)__block變量形成強(qiáng)引用(retain)
當(dāng)block從堆中移除時(shí)
會(huì)調(diào)用block內(nèi)部的dispose函數(shù)
dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的__block變量(release)
對(duì)象類型的auto變量豫柬、__block變量
當(dāng)block在棧上時(shí),對(duì)它們都不會(huì)產(chǎn)生強(qiáng)引用
當(dāng)block拷貝到堆上時(shí)扑浸,都會(huì)通過(guò)copy函數(shù)來(lái)處理它們
__block變量(假設(shè)變量名叫做a)
_Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
對(duì)象類型的auto變量(假設(shè)變量名叫做p)
_Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
當(dāng)block從堆上移除時(shí)烧给,都會(huì)通過(guò)dispose函數(shù)來(lái)釋放它們
__block變量(假設(shè)變量名叫做a)
_Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);
對(duì)象類型的auto變量(假設(shè)變量名叫做p)
_Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);
被__block修飾的對(duì)象類型
當(dāng)__block變量在棧上時(shí),不會(huì)對(duì)指向的對(duì)象產(chǎn)生強(qiáng)引用
當(dāng)__block變量被copy到堆時(shí)
會(huì)調(diào)用__block變量?jī)?nèi)部的copy函數(shù)
copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
_Block_object_assign函數(shù)會(huì)根據(jù)所指向?qū)ο蟮男揎椃╛_strong喝噪、__weak础嫡、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于ARC時(shí)會(huì)retain酝惧,MRC時(shí)不會(huì)retain)
如果__block變量從堆上移除
會(huì)調(diào)用__block變量?jī)?nèi)部的dispose函數(shù)
dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放指向的對(duì)象(release)