block本質(zhì)
- block 本質(zhì)上是一個(gè) oc對象,它內(nèi)部也有 isa 指針
- block 是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的 OC 對象
- block 的底層結(jié)構(gòu):
int age = 20;
void (^block)(void) = ^{
NSLog(@"age is %d", age);
}
// 對于上面的代碼轉(zhuǎn)換成 c++后結(jié)構(gòu)如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 * Desc; // block 的描述信息
int age; // 保存捕獲的外部變量 age 的值
}
struct __block_impl {
void *isa; // block 的 isa桐筏,所以說 block 本質(zhì)是一個(gè) oc 對象
int Flags秃流; // c++中構(gòu)造方法需要的字段苟跪,沒什么用
int Reserved;
void *FuncPtr; // block 內(nèi)部的代碼是封裝到了一個(gè)函數(shù)中,通過調(diào)用這個(gè)函數(shù)來執(zhí)行 block 內(nèi)部的代碼塊,F(xiàn)uncPtr中保存的就是這個(gè)函數(shù)的地址
}
struct __main_block_desc_0 {
size_t reserved; //保留字段
size_t Block_size箍邮; //block的大小
}
block 的變量捕獲
- 為了保證 block 內(nèi)部能夠正常訪問外部的變量棒动,block 有個(gè)變量捕獲機(jī)制
-
如果是全局變量糙申,直接捕獲指針,如果是局部變量則捕獲變量值船惨,如果是 static 局部變量則捕獲指針
- auto: 自動(dòng)變量,就是離開作用域就會(huì)銷毀的變量,局部變量 默認(rèn) auto 關(guān)鍵字可以不寫,auto 不可以修飾全局變量
- block 對變量的捕獲為什么會(huì)這樣分呢? 這是因?yàn)?對局部變量,如果捕獲的是地址的話,在 block 執(zhí)行的時(shí)候局部變量可能已經(jīng)被釋放了,這個(gè)時(shí)候訪問局部變量就會(huì)發(fā)生錯(cuò)誤.對于全部變量,因?yàn)槿肿兞坎粫?huì)釋放,所以捕獲的是內(nèi)存地址.static 修飾的局部變量也是常駐內(nèi)存不會(huì)釋放的,所以也是地址.
-(void)test {
int age = 10柜裸;
static int height = 10;
void (^block)(void) = ^{
NSLog(@"age is %d, height is %d",age,height); //age is 10 height is 20,
/*這是因?yàn)?age 變量有可能在 block 調(diào)用的時(shí)候被釋放掉缕陕,*/
/*如果在 block 調(diào)用的時(shí)候 age 被釋放掉了就會(huì)發(fā)生錯(cuò)誤,*/
/*所以不能捕獲 age 變量的地址疙挺,而對于 height 變量扛邑,*/
/*因?yàn)槭?static 變量所以一直在內(nèi)存中不會(huì)釋放,所以捕獲的是內(nèi)存地址*/
}
height = 20;
block();
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson * p = [[MJPerson alloc] init];
void (^block)(void) = ^{
NSLog(@"Person ==== %@", p);
};
block();
}
return 0;
}
上面的block 轉(zhuǎn)換成 c++后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MJPerson *p; // 捕獲的外部 p 對象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *_p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock; // block 類型
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
轉(zhuǎn)換成 c++的 main 函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
MJPerson *p = __cself->p; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8b_t3lg0zjs5zvdrnhdn4_55zqw0000gn_T_main_f28bc7_mi_0, p);
}
block 類型
- block 有 3 中類型铐然,可以通過 class 方法或 isa 指針查看具體類型蔬崩,最終都是繼承自 NSBlock,NSBlock 繼承自 NSObject
- 全局 block
_NSGlobalBlock(_NSConcreteGlobalBlock )
,沒有訪問 auto 變量,存放在程序的數(shù)據(jù)區(qū) - 棧 block
_NSStackBlock(_NSConcreteStackBlock )
搀暑,訪問了 auto 變量 - 堆 block
_NSMallocBlock(_NSConcreteMallocBlock )
舱殿,_NSStackBlock調(diào)用了 copy
三種 block調(diào)用 copy 函數(shù)的結(jié)果
-
_NSGlobalBlock
: 什么也不做,依舊存放在程序的數(shù)據(jù)區(qū) -
_NSStackBlock
:從棧復(fù)制到堆 -
_NSMallocBlock
:引用計(jì)數(shù)+1
block的 copy
- 在 ARC 環(huán)境下险掀,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的 block copy到沪袭,比如以下情況:
1、block作為函數(shù)返回值時(shí)
2樟氢、將block賦值給__strong指針時(shí)
3冈绊、block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
4、block作為GCD API的方法參數(shù)時(shí)
// ARC 環(huán)境下
typedef void(^Block)(void)
int age = 10;
Block block = ^{
NSLog(@"age is %d",age);
}
NSLog(@"block is type %@",[block class]); // block is type _NSMallocBlock
- MRC 下 block 屬性使用 copy
- ARC 下 block 屬性使用 strong 和 copy 都可以
對象類型的 auto 變量
- 當(dāng) block 內(nèi)部訪問了對象類型的 auto 變量時(shí)埠啃,如果 block 是在棧上死宣,將不會(huì)對 auto 變量產(chǎn)生強(qiáng)引用。如果 block 被 copy 到堆上碴开,則會(huì)調(diào)用 block 內(nèi)部的 __main__block__copy函數(shù)毅该,copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù),這個(gè)函數(shù)會(huì)根據(jù) auto 變量的修飾符(__strong潦牛、__weak眶掌、__unsafe_unretained)做出相應(yīng)的操作形成強(qiáng)引用或者弱引用。如果block 從堆上移除時(shí)巴碗,會(huì)調(diào)用 block 內(nèi)部的 dispose 函數(shù)朴爬。這個(gè)函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù),這個(gè)函數(shù)會(huì)自動(dòng)釋放引用的 auto 變量
__block
- __block可以用于解決block內(nèi)部無法修改auto變量值的問題
- __block不能修飾全局變量橡淆、靜態(tài)變量(static)
- 編譯器會(huì)將__block變量包裝成一個(gè)對象
@interface Person: NSObject
@end
int num = 20;
__block int age = 10;
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
__weak NSObject *weakObj = obj2;
Person *person = [[Person alloc] init];
__block __weak Person *weakPerson = person; // 這里的 __weak 決定著__Block_byref_weakPerson_0結(jié)構(gòu)體中指向 weakPerson 的指針是強(qiáng)引用還是弱引用
^{
NSLog(@"%d",age);
NSLog(@"%@",obj1);
NSLog(@"%@",weakObj);
NSLog(@"%@",weakPerson);
}
//以上代碼轉(zhuǎn)換成 c++后如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
int num;
__Block_byref_age_0 *age;
NSObject *__strong obj1;
NSObject *__weak weakObject;
__Block_byref_weakPerson_0 *weakPerson;
}
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; // 指向自己
int __flags;
int __size;
int age;
}
struct __Block_byref_weakPerson_0 {
void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
int __flags;
int __size;
void (* __Block_byref_id_object_copy)(void*,void*);
void(* __Block_byref_id_object_dispose)(void*);
Person *__weak weakPerson;
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
// 如果 block 外部的變量沒有用__block修飾的話召噩,則不會(huì)有下面兩個(gè)方法,下面兩個(gè)方法對__block修飾的變量進(jìn)行內(nèi)存管理
void (*copy)(struct __main_block_impl_0*,struct __main_block_impl_0*);
void(*dispose)(struct __main_block_impl_0*);
}
__block的內(nèi)存管理
-
如果block 引用了外部變量逸爵,當(dāng) block從棧內(nèi)存 copy 到堆內(nèi)存時(shí)具滴,會(huì)將外部引用的__block變量也 copy 到堆內(nèi)存,并持有該變量师倔;如果多個(gè) block 引用同一__block變量构韵,當(dāng)block從棧 copy 到堆時(shí),__block變量的引用計(jì)數(shù)會(huì)相應(yīng)的增加。
__block的__forwarding指針
- 當(dāng) block 在棧上時(shí)__forwarding指針指向自己
-
當(dāng) block 從棧復(fù)制到堆之后贞绳,棧上block 的__forwarding指針指向堆內(nèi)存上的__block變量谷醉,而堆內(nèi)存上的 block 的__forwarding 指針指向自己,這樣在訪問 age 時(shí)不論是訪問棧上的變量冈闭,還是訪問堆上的變量都可以通過age->__forwarding->age 訪問到堆內(nèi)存的 age
block 問題
- 在使用 block 時(shí)俱尼,當(dāng) block 內(nèi)部引用了 self,我們通常在 block 外部聲明一個(gè) weakSelf萎攒,然后在 block 內(nèi)再聲明一個(gè) strongSelf遇八,這是為什么?
1耍休、當(dāng)我們在 block內(nèi)部使用 weakSelf 的時(shí)候刃永,編譯器會(huì)報(bào)錯(cuò)Dereferencing a __weak pointer is not allowed due to possible null value caused by rece condition, assign to strong variable first
2、防止當(dāng)我們執(zhí)行 block 代碼塊的時(shí)候外部 self 已經(jīng)被釋放掉