Block原理Block變量捕獲Block類型copy操作和Block內(nèi)部訪問對(duì)象類型的變量__block修改變量及其本質(zhì)__block內(nèi)存管理Block循環(huán)引用問題
Block是一種可以在C、C++以及Objective-C代碼中使用,類似于“閉包(closure)”的代碼塊,借助Block機(jī)制,開發(fā)者可以將代碼像對(duì)象一樣在不同的上下文環(huán)境中進(jìn)行傳遞。
(這里說的不同上下文環(huán)境,我舉個(gè)例子:比如在A函數(shù)中定義了一個(gè)變量,它是一個(gè)局部變量,那么我要在B函數(shù)中去訪問,這里就屬于兩個(gè)不同的上下文環(huán)境)
一、Block原理
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
void (^block)(int,int) = ^(int a, int b){
NSLog(@"a = %d, b = %d, age = %d",a,b,age);
};
block(3,5);
}
return 0;
}
將上面main.m編譯生成C++代碼:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
main()函數(shù)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 20;
void (*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);
}
return 0;
}
__main_block_impl_0結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__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;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __maib_block_desc_0 {
size_t reserved;
size_t Block_size;
};
我們定義block變量,其實(shí)下面這句代碼:
void (*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
就是調(diào)用結(jié)構(gòu)體__main_block_impl_0內(nèi)部的構(gòu)造函數(shù)初始化一個(gè)結(jié)構(gòu)體出來,然后取結(jié)構(gòu)體地址&__main_block_impl_0賦給block指針,所以block底層是下面結(jié)構(gòu)體;調(diào)用構(gòu)造函數(shù)傳了三個(gè)參數(shù):
(void *)__main_block_func_0、&__main_block_desc_0_DATA、age
其中(void *)__main_block_func_0是下面函數(shù)的地址,這個(gè)函數(shù)就是封裝了block執(zhí)行邏輯的函數(shù),通過上面的構(gòu)造函數(shù)傳給__block_impl結(jié)構(gòu)體的FuncPtr:
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders__h_yv1h9mrx1155q6tq7brn8b9m0000gp_T_main_b54551_mi_0,a,b,age);
}
同樣,第二個(gè)參數(shù)類型&__main_block_desc_0_DATA是下面結(jié)構(gòu)體地址,最終通過構(gòu)造函數(shù)賦給了Desc,其中Block_size表示block結(jié)構(gòu)體的大小;
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()函數(shù)中調(diào)用block(3, 5)最終轉(zhuǎn)化為下面代碼,通過將block強(qiáng)制轉(zhuǎn)換為__block_impl(這里__block_impl類型是__main_block_impl_0結(jié)構(gòu)體第一個(gè)成員,所以可以轉(zhuǎn)) ,最終直接找到impl中的FuncPtr進(jìn)行調(diào)用
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);
二、Block變量捕獲
Block變量捕獲是指在Block內(nèi)部訪問外部變量時(shí),如果外部變量是局部變量,則Block內(nèi)部會(huì)將其捕獲,具體捕獲形式看外部的這個(gè)局部變量是auto類型還是static類型:
如果是auto類型,直接將變量的值傳遞給Block內(nèi)部,Block結(jié)構(gòu)體內(nèi)部會(huì)生成一個(gè)變量來存儲(chǔ)傳進(jìn)來的值,所以在Block外邊改變age=20,調(diào)用block()時(shí)內(nèi)部打印的結(jié)果依然是age=10,因?yàn)榇藭r(shí)進(jìn)行的是值傳遞;
如果是static類型,會(huì)將變量的地址傳遞給Block內(nèi)部,block結(jié)構(gòu)體內(nèi)部會(huì)生成一個(gè)指針變量來存儲(chǔ)傳進(jìn)來的地址值,所以在block外邊改變height=20,調(diào)用block()時(shí)內(nèi)部打印的結(jié)果是height=20,因?yàn)榇藭r(shí)進(jìn)行的是指針傳遞;
下面進(jìn)行驗(yàn)證:
- 局部變量?jī)煞N情況:
// 局部變量?jī)煞N情況
int main(int argc, const char * argv[]) {
@autoreleasepool {
// case-1: auto變量,離開作用域就銷毀
auto int age = 10; //等價(jià)于 int age = 10;
// case-2: static變量
static int height = 10;
void (^block)(void) = ^{
NSLog(@"age is %d, height is %d",age, height);
};
age = 20;
height = 20;
block();
}
return 0;
}
打印結(jié)果:
age is 10, height is 20
編譯成C++:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
從編譯生成的結(jié)構(gòu)體看出,age是值傳遞,height是指針傳遞;定義完block就將10和&height捕獲到block內(nèi)部,后邊調(diào)用block時(shí)訪問的結(jié)構(gòu)體內(nèi)部age是捕獲到的值10,height是捕獲到的地址&height;
- 全局變量:因?yàn)槭窃谌謪^(qū),所以任何函數(shù)內(nèi)部可以直接訪問
總結(jié)一下:

Block的本質(zhì):Block本質(zhì)上也是一個(gè)OC對(duì)象,它內(nèi)部也有個(gè)isa指針,但它是一個(gè)封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象;
三、Block類型
Block有三種類型,可以通過調(diào)用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"Hello World!");
};
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
}
return 0;
}
打印結(jié)果:
05-block類型[46881:69217078] __NSGlobalBlock__
05-block類型[46881:69217078] __NSGlobalBlock
05-block類型[46881:69217078] NSBlock
05-block類型[46881:69217078] NSObject
三種類型:
-
__NSGlobalBlock__(_NSConcreteGlobalBlock) -
__NSStackBlock__(_NSConcreteStackBlock) -
__NSMallocBlock__(_NSConcreteMallocBlock)

int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block1)(void) = ^{
NSLog(@"Hello");
};
int age = 10;
void (^block2)(void) = ^{
NSLog(@"Hello - %d", age);
};
NSLog(@"%@ %@ %@",[block1 class], [block2 class], [^{
NSLog(@"%d",age);
} class]);
}
return 0;
}
打印結(jié)果:
05-block類型[47475:69339707] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
那不同類型Block分別對(duì)應(yīng)什么情況呢?
static int height = 30;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 沒有訪問外部變量
void (^block1)(void) = ^{
NSLog(@"--------------");
};
// 訪問 auto 變量
int age = 10;
void (^block2)(void) = ^{
NSLog(@"--------------%d", age);
};
// 訪問 static 變量
void (^block3)(void) = ^{
NSLog(@"=-------------%d", height);
};
NSLog(@"%@ %@ %@",[block1 class], [block2 class], [block3 class]);
}
return 0;
}
打印結(jié)果:
05-block類型[48630:69576321] __NSGlobalBlock__ __NSMallocBlock__ __NSGlobalBlock__
可以看出,在沒有訪問外部變量的情況下,block1是一個(gè)__NSGlobalBlock__類型,存放在數(shù)據(jù)區(qū),此時(shí)的block1就相當(dāng)于我們定一個(gè)了一個(gè)函數(shù),函數(shù)中的代碼沒有訪問另外一個(gè)函數(shù)(此處為main())中的變量;同理,block3雖然訪問外部變量,但static變量是全局的,同樣相當(dāng)于單獨(dú)拿出去定義一個(gè)和main()函數(shù)上下文無關(guān)的函數(shù);
由于block2訪問了auto變量,相當(dāng)于在block2封裝的函數(shù)中訪問了另外一個(gè)函數(shù)內(nèi)部的變量(main()函數(shù)中的局部變量age),此時(shí)block2變?yōu)?code>__NSStackBlock__,因?yàn)樗枰4孢@個(gè)局部變量,由于是在ARC環(huán)境,會(huì)自動(dòng)對(duì)__NSStackBlock__類型進(jìn)行copy操作,所以 block2打印類型是一個(gè) __NSMallocBlock__類型;
關(guān)閉ARC在MRC環(huán)境下打印:

打印結(jié)果:
05-block類型[49786:69814242] __NSGlobalBlock__ __NSStackBlock__ __NSGlobalBlock__
可以看出block2確實(shí)是一個(gè)__NSStackBlock__類型;
四、copy操作和Block內(nèi)部訪問對(duì)象類型的變量
copy操作分MRC和ARC兩種情況:
-
MRC環(huán)境:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
// 情況一:沒有訪問外部 auto 變量
// 它是一個(gè) NSGlobalBlock
// 內(nèi)存是放在數(shù)據(jù)段
void (^block)(void) = ^{
NSLog(@"---------");
};
// 情況二:訪問外部 auto 變量 age
// 它是一個(gè) NSStackBlock 隨時(shí)會(huì)被回收
// 內(nèi)存是在棧上
// 通過 copy 操作轉(zhuǎn)變?yōu)?NSMallocBlock 把它放到堆上保活
void (^block2)(void) = [^{
NSLog(@"---------%d",age);
} copy];
// 因?yàn)樵?MRC 環(huán)境 不用時(shí)要進(jìn)行 release 操作
[block2 release];
}
return 0;
}
-
ARC環(huán)境:
在ARC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的Block拷貝到堆上,即自動(dòng)進(jìn)行一次copy操作,比如以下情況:
- 情況一:
Block作為函數(shù)返回值
// 定義一個(gè)block類型
typedef void (^DJTBlock)(void);
// block作為函數(shù)返回值
DJTBlock myblock()
{
// case1: 這里沒有訪問auto變量 是一個(gè)NSGlobalBlock
return ^{
NSLog(@"------------");
};
// 相當(dāng)于下面這樣寫
// DJTBlock block = ^{
// NSLog(@"------------");
// };
// return block;
//-----------------------------------------------------------------
// case2: 這里訪問了auto 是一個(gè)NSSackBlock 作為函數(shù)返回值A(chǔ)RC下自動(dòng)copy成NSMallocBlock
// int age = 10;
// return ^{
// NSLog(@"------------%d",age);
// };
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// ARC環(huán)境下 調(diào)用myblock()函數(shù)
// DJTBlock作為myblock函數(shù)的返回值 編譯器自動(dòng)進(jìn)行一次 copy 操作
// 所以 block變量指向的 DTJBlock 此時(shí)已經(jīng)在堆上
DJTBlock block = myblock();
block();
// 打印 block 類型
NSLog(@"%@",[block class]);
}
return 0;
}
打印結(jié)果:
05-block--copy[64907:9167520] ------------
05-block--copy[64907:9167520] __NSGlobalBlock__
打印結(jié)果是一個(gè)NSGlobalBlock類型,這是因?yàn)樵诤瘮?shù)my block()內(nèi)部沒有訪問auto變量(上面block類型有闡述),而對(duì)NSGlobalBlock類型的Block執(zhí)行copy操作生成的Block還是NSGlobalBlock,所以如果將返回改為myblock()函數(shù)內(nèi)注釋部分,就會(huì)打印__NSMallocBlock__。
- 情況二:將
Block賦值給__strong強(qiáng)指針時(shí)
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
// Block被強(qiáng)指針指著
DJTBlock block = ^{
NSLog(@"------------%d",age);
};
block();
// 打印 block 類型
NSLog(@"%@",[block class]);
}
return 0;
}
打印結(jié)果:
05-block--copy[69520:9293376] ------------10
05-block--copy[69520:9293376] __NSMallocBlock__
- 情況三:
Block作為Cocoa API中方法各含有usingBlock的方法參數(shù)時(shí):
NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
這個(gè)參數(shù)Block也是一個(gè)堆上的block;
- 情況四:
Block作為GCD API的方法參數(shù)時(shí):
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
Block內(nèi)部訪問對(duì)象類型的變量
先看一個(gè)有趣的現(xiàn)象:
// DJTPerson.h
@interface DJTPerson : NSObject
@property(nonatomic, assign) int age;
@end
// DJTPerson.m
@implementation DJTPerson
- (void)dealloc
{
NSLog(@"DJTPerson----dealloc");
}
@end
// main.m
#import "DJTPerson.h"
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
DJTPerson *person = [[DJTPerson alloc] init];
person.age = 10;
}
NSLog(@"-----------------");// 打斷點(diǎn)
}
return 0;
}
在上面NSLog(@"-----------------");處打斷點(diǎn),運(yùn)行程序發(fā)現(xiàn)控制臺(tái)打印:
05-block訪問對(duì)象類型的auto變量[77563:9561984] DJTPerson----dealloc
(lldb)
說明在斷點(diǎn)前的中括號(hào)結(jié)束,person變量就已經(jīng)釋放,接著我們定義一個(gè)block,在內(nèi)部訪問person的age屬性:
int main(int argc, const char * argv[]) {
@autoreleasepool {
DJTBlock block;
{
DJTPerson *person = [[DJTPerson alloc] init];
person.age = 10;
block = ^{
NSLog(@"-----------%d",person.age);
};
}
NSLog(@"-----------------");// 打斷點(diǎn)
}
return 0;
}
通用在NSLog(@"-----------------");處打斷點(diǎn),運(yùn)行程序發(fā)現(xiàn)控制臺(tái)無打印,說明person沒被回收。
為什么被第二種情況下person沒有被回收呢?為了驗(yàn)證我們將代碼簡(jiǎn)化并編譯成C++來進(jìn)行底層原理分析:
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
DJTPerson *person = [[DJTPerson alloc] init];
person.age = 10;
DJTBlock block = ^{
NSLog(@"-----------%d",person.age);
};
}
return 0;
}
日常操作命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
在編譯生成的C++文件中查看生成的block結(jié)構(gòu)體:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
DJTPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, DJTPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
由于person是DJTPerson *類型,所以捕獲到block內(nèi)部也是DJTPerson *類型,即struct __main_block_impl_0結(jié)構(gòu)體內(nèi)部可以看到有一個(gè)DJTPerson *類型變量person;下面先從一個(gè)角度理解為什么person沒有被釋放:
在上面代碼中,我們定義的Block是被一個(gè)DJTBlock類型的變量block強(qiáng)引用的,即這句代碼:
DJTBlock block = ^{
NSLog(@"-----------%d",person.age);
};
在ARC環(huán)境下,被強(qiáng)引用的這個(gè)Block(訪問了auto變量)會(huì)自動(dòng)拷貝到堆上,而這個(gè)Block內(nèi)部(編譯成C++即為struct __main_block_impl_0結(jié)構(gòu)體)又有一個(gè)DJTPerson*類型的指針指向外面這個(gè)person對(duì)象,所以只要這個(gè)Block在,那么這個(gè)強(qiáng)指針就在,所以外邊的person對(duì)象不會(huì)被釋放;
換成MRC環(huán)境:
// DJTPerson.h
@interface DJTPerson : NSObject
@property(nonatomic, assign) int age;
@end
// DJTPerson.m
@implementation DJTPerson
- (void)dealloc
{
[super dealloc];
NSLog(@"DJTPerson----dealloc");
}
@end
// main.m
#import "DJTPerson.h"
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
DJTBlock block;
{
DJTPerson *person = [[DJTPerson alloc] init];
person.age = 10;
block = ^{
NSLog(@"-----------%d",person.age);
};
[person release];
}
NSLog(@"---------------");// 打斷點(diǎn)
}
return 0;
}
依然在NSLog(@"---------------");打斷點(diǎn),發(fā)現(xiàn)控制臺(tái)打印結(jié)果:
05-block訪問對(duì)象類型的auto變量[83896:9803518] DJTPerson----dealloc
發(fā)現(xiàn)person被釋放,這是因?yàn)榧词?code>block內(nèi)部訪問了person對(duì)象,MRC環(huán)境下,block內(nèi)部訪問了auto變量,它是一個(gè)棧上block,但并不會(huì)自動(dòng)拷貝到堆上,由于它是一個(gè)NSStackBlock,內(nèi)部并不會(huì)對(duì)外部person強(qiáng)引用(這里說強(qiáng)引用并不準(zhǔn)確,在MRC環(huán)境沒有強(qiáng)引用說法,應(yīng)該描述為沒有對(duì)外邊person進(jìn)行retain操作,但為了好理解 so...),所以在執(zhí)行完[person release]以后,雖然Block還沒有離開其作用域(Block作用域到return 0;前到大括號(hào)),但person就被釋放;可以通過[block copy]將其復(fù)制到堆上,這樣內(nèi)部就會(huì)對(duì)外邊的person強(qiáng)引用(其實(shí)是retain操作)從而?;?code>person,當(dāng)然在Block銷毀的時(shí)候,內(nèi)部對(duì)person還會(huì)進(jìn)行一次release操作,這樣一加一減,就保持了平衡;
要點(diǎn):??臻g的Block(NSStackBlock)是不會(huì)對(duì)外邊auto對(duì)象進(jìn)行保活(ARC環(huán)境表現(xiàn)為不會(huì)強(qiáng)引用,MRC下表現(xiàn)為不會(huì)進(jìn)行retain操作),只有拷貝到堆上(NSMallocBlock)才會(huì)對(duì)其自動(dòng)?;睢?/p>
回到ARC環(huán)境:
看一下__weak作用:
int main(int argc, const char * argv[]) {
@autoreleasepool {
DJTBlock block;
{
DJTPerson *person = [[DJTPerson alloc] init];
person.age = 10;
// 這里使用 __weak 修飾
__weak DJTPerson *weakPerson = person;
block = ^{
NSLog(@"-----------%d",weakPerson.age);
};
}
NSLog(@"---------------"); // 打斷點(diǎn)
}
return 0;
}
依然在NSLog(@"---------------");處打斷點(diǎn),打印結(jié)果為:
05-block訪問對(duì)象類型的auto變量[87323:9930285] DJTPerson----dealloc
這說明,即使在ARC環(huán)境,Block被拷貝到堆上,由于我們用__weak類型的__weakPerson訪問了外部auto變量,它也不會(huì)對(duì)外部person進(jìn)行強(qiáng)引用。
同樣我們把上述代碼編譯成C++,由于弱引用需要運(yùn)行時(shí)機(jī)制來支持,所以我們不能進(jìn)行靜態(tài)編譯,還需要運(yùn)行時(shí)調(diào)用,指定運(yùn)行時(shí)系統(tǒng)版本,所以編譯命令如下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
在生成的C++代碼中找到__main_block_impl_0結(jié)構(gòu)體,發(fā)現(xiàn)是一個(gè)弱引用:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
DJTPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, DJTPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
總結(jié)一下:
-
Block在棧上,無論ARC、MRC環(huán)境,block內(nèi)部都不會(huì)對(duì)外部對(duì)象類型的auto變量產(chǎn)生強(qiáng)引用,就算Block內(nèi)部生成強(qiáng)指針,也不會(huì)對(duì)外部person產(chǎn)生強(qiáng)引用,因?yàn)?code>Block自己就在棧上,隨時(shí)可能被銷毀; -
Block在堆上:
在ARC環(huán)境下,訪問外部對(duì)象類型的auto變量,編譯后:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
DJTPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, DJTPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在 __main_block_desc_0結(jié)構(gòu)體中,多了兩個(gè)函數(shù):__main_block_copy_0和__main_block_dispose_0
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};
分別看它們實(shí)現(xiàn):
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*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
當(dāng)對(duì)Block進(jìn)行copy操作時(shí),會(huì)調(diào)用這個(gè)__main_block_copy_0函數(shù),在它內(nèi)部調(diào)用_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/),會(huì)對(duì)外部person對(duì)象產(chǎn)生強(qiáng)引用或者弱引用,這取決于block內(nèi)部使用__strong指針還是__weak指針訪問。
當(dāng)Block從堆上移除,會(huì)調(diào)用__main_block_dispose_0函數(shù),它內(nèi)部調(diào)用_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);,會(huì)對(duì)外部person對(duì)象進(jìn)行一次release操作。
在MRC環(huán)境下,也是由這兩個(gè)函數(shù)決定是否進(jìn)行retain和release操作。
五、__block修改變量及其本質(zhì)
我們先看下面一段代碼:
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
DJTBlock block = ^{
age = 20; // 報(bào)錯(cuò):Variable is not assignable (missing __block type specifier)
NSLog(@"-----------%d",age);
};
}
return 0;
}
ARC下直接在Block內(nèi)部修改age會(huì)報(bào)錯(cuò),這就相當(dāng)于在block生成的結(jié)構(gòu)體中FuncPtr指向的函數(shù)中去修改main函數(shù)中的局部變量(如果這里age是static或者全局變量,可以修改,因?yàn)檫@兩種變量一直在內(nèi)存中),上下文環(huán)境發(fā)生了改變,所以不能直接訪問age;我們使用__block修飾age變量,然后編譯成C++:
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
DJTBlock block = ^{
age = 20;
NSLog(@"-----------%d",age);
};
}
return 0;
}
會(huì)生成下面結(jié)構(gòu)體:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到在Block結(jié)構(gòu)體中多了__Block_byref_age_0 *age;,看一下 __Block_byref_age_0發(fā)現(xiàn)它也是一個(gè)結(jié)構(gòu)體:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
這里看出編譯器將 __block修飾的變量(這里是age)包裝成一個(gè)對(duì)象__Block_byref_age_0(因?yàn)樗鼉?nèi)部有isa指針,所以可以認(rèn)為它是個(gè)對(duì)象),Block內(nèi)部(__main_block_impl_0結(jié)構(gòu)體中)并不會(huì)直接擁有這個(gè)變量age,而是擁有__Block_byref_age_0這個(gè)結(jié)構(gòu)體,然后__Block_byref_age_0結(jié)構(gòu)體中有一個(gè)int age變量,我們?cè)贐lock內(nèi)部改變age = 20,實(shí)際上就是賦值給__Block_byref_age_0結(jié)構(gòu)體中的age變量。
我們對(duì)__block int age = 10轉(zhuǎn)化成的C++代碼進(jìn)行簡(jiǎn)化:
// __block int age = 10;對(duì)應(yīng)下面c++代碼:
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
// 進(jìn)行簡(jiǎn)化
__Block_byref_age_0 age = {
0,
&age,
0,
sizeof(__Block_byref_age_0),
10
};
對(duì)應(yīng)到下面結(jié)構(gòu)體初始化:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
__forwarding指針傳入&age指向__Block_byref_age_0 age結(jié)構(gòu)體自己(這里&age是結(jié)構(gòu)體地址,不要混淆),10賦值給了__Block_byref_age_0結(jié)構(gòu)體內(nèi)部的age變量;我們?cè)倏聪滦薷?code>age為20的代碼:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
// 這里是 age = 20;
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders__h_yv1h9mrx1155q6tq7brn8b9m0000gp_T_main_05a705_mi_0,(age->__forwarding->age));
}
可以發(fā)現(xiàn)先通過 __cself->age找到__Block_byref_age_0結(jié)構(gòu)體,然后(age->__forwarding->age) = 20;通過__forwarding指針修改結(jié)構(gòu)體內(nèi)部的age變量,__forwarding指向結(jié)構(gòu)體自己,那為什么要多此一舉通過__forwarding指針去修改內(nèi)部age,而不通過結(jié)構(gòu)體指針直接去修改呢?這是為了保證Block被copy到堆上時(shí),不管訪問棧上還是堆上Block,通過forwarding指針都是找到堆上。
這里如果__block修飾的是一個(gè)對(duì)象類型,比如下面代碼:
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
__block NSObject *obj = [[NSObject alloc] init];
DJTBlock block = ^{
obj = nil;
age = 20;
};
}
return 0;
}
轉(zhuǎn)換為C++同樣會(huì)多生成一個(gè)對(duì)應(yīng)的結(jié)構(gòu)體,只不過內(nèi)部會(huì)多出兩個(gè)方法copy``和dispose方法來負(fù)責(zé)相應(yīng)的內(nèi)存管理:
// __block age 對(duì)應(yīng)的結(jié)構(gòu)體
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
// __block NSObject 對(duì)應(yīng)的結(jié)構(gòu)體
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;
};
下面看一個(gè)例子:
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *mutarray = [NSMutableArray array];
DJTBlock block = ^{
[mutarray addObject:@(12)];
[mutarray addObject:@(13)];
};
}
return 0;
}
這里不會(huì)報(bào)錯(cuò),是因?yàn)槲覀儾]有修改mutarray指針,而是在使用mutarray指針,除非我們修改mutarray指針的值,比如 mutarray = nil;才需要__block來修飾;
六、__block的內(nèi)存管理
我們知道,當(dāng)Block內(nèi)部訪問外部對(duì)象類型的變量時(shí),如下面簡(jiǎn)單代碼:
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object = [[NSObject alloc] init];
DJTBlock block = ^{
NSLog(@"%p", object);
};
block();
}
return 0;
}
block編譯成C++后的結(jié)構(gòu):
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__strong object; //內(nèi)部強(qiáng)引用外部變量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _object, int flags=0) : object(_object) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到在block結(jié)構(gòu)體內(nèi)部會(huì)生成一個(gè)強(qiáng)指針指向外邊的object對(duì)象,并且在block被拷貝到堆上時(shí),調(diào)用__main_block_desc_0中的copy函數(shù),對(duì)這個(gè)指針指向的對(duì)象進(jìn)行一次retain操作,即引用計(jì)數(shù)+1,當(dāng)然如果用__weak修飾object會(huì)生NSObject *__weak object;此時(shí)不會(huì)強(qiáng)引用;
那當(dāng)我們用__block修飾變量時(shí),比如分別修飾基礎(chǔ)數(shù)據(jù)類型age,和對(duì)象類型obj1,如下代碼:
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
__block NSObject *obj1 = [[NSObject alloc] init];
NSObject *object = [[NSObject alloc] init];
DJTBlock block = ^{
NSLog(@"%d %p %p", age,obj1, object);
};
block();
}
return 0;
}
編譯成C++:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __Block_byref_obj1_1 {
void *__isa;
__Block_byref_obj1_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__strong obj1;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__strong object;
__Block_byref_age_0 *age; // by ref
__Block_byref_obj1_1 *obj1; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _object, __Block_byref_age_0 *_age, __Block_byref_obj1_1 *_obj1, int flags=0) : object(_object), age(_age->__forwarding), obj1(_obj1->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
經(jīng)__block修飾的變量age和obj1分別生成構(gòu)體__Block_byref_age_0和 __Block_byref_obj1_1,它們的本質(zhì)就是OC對(duì)象,所以在block對(duì)應(yīng)的結(jié)構(gòu)體內(nèi)部生成兩個(gè)結(jié)構(gòu)體指針指向這兩個(gè)對(duì)象,即
__Block_byref_age_0 *age; // by ref
__Block_byref_obj1_1 *obj1; // by ref
它們其實(shí)和object一樣,因?yàn)?code>__block修飾的變量也是轉(zhuǎn)換成結(jié)構(gòu)體,而且內(nèi)部有isa指針,其實(shí)就是OC對(duì)象,所以也會(huì)在__main_block_desc_0中生成兩個(gè)函數(shù):copy和dispose,來管理對(duì)象的內(nèi)存,可以看下結(jié)構(gòu):
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->obj1, (void*)src->obj1, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->obj1, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
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};
所以__block內(nèi)存管理可以總結(jié)一下:
當(dāng)
Block在棧上時(shí),內(nèi)部并不會(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ù),它內(nèi)部會(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ì)對(duì)引用的__block變量進(jìn)行引用計(jì)數(shù)-1(release)
下面我們對(duì)比下Block內(nèi)部訪問外部變量幾種情況:
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
//直接將20存放在Block生成的結(jié)構(gòu)體中
int num = 20;
//Block結(jié)構(gòu)體內(nèi)部生成一個(gè)強(qiáng)指針 強(qiáng)引用object對(duì)象
NSObject *object = [[NSObject alloc] init];
// Block內(nèi)部生成一個(gè)弱指針 弱引用object對(duì)象
__weak NSObject *weakObject = object;
// Block內(nèi)部生成一個(gè)結(jié)構(gòu)體指針,指針指向的結(jié)構(gòu)體內(nèi)部存儲(chǔ)著變量age
__block int age = 10;
//Block內(nèi)部生成一個(gè)結(jié)構(gòu)體指針,指針指向的結(jié)構(gòu)體內(nèi)部存儲(chǔ)著變量obj1
__block NSObject *obj1 = [[NSObject alloc] init];
DJTBlock block = ^{
NSLog(@"%d %d %p %p %p",num, age, obj1, object, weakObject);
};
block();
}
return 0;
}
編譯成C++看看block結(jié)構(gòu)體,和上邊注釋的一致:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num;
NSObject *__strong object;
NSObject *__weak weakObject;
__Block_byref_age_0 *age; // by ref
__Block_byref_obj1_1 *obj1; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, NSObject *__strong _object, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, __Block_byref_obj1_1 *_obj1, int flags=0) : num(_num), object(_object), weakObject(_weakObject), age(_age->__forwarding), obj1(_obj1->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這里主要對(duì)比一下 對(duì)象類型的auto變量和__block修飾的變量?jī)?nèi)存管理的區(qū)別:
- 相同點(diǎn):
- 當(dāng)
Block在棧上時(shí),對(duì)它們都不會(huì)產(chǎn)生強(qiáng)引用 - 當(dāng)
Block拷貝到堆上時(shí),都會(huì)通過copy函數(shù)來處理它們:
(1)__block變量(假設(shè)變量名叫做a)
(2)對(duì)象類型的_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);auto變量(假設(shè)變量名叫做p)_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/); - 當(dāng)
Block從堆上移除時(shí),都會(huì)通過dispose函數(shù)來釋放它們
(1)__block變量(假設(shè)變量名叫做a)
(2)對(duì)象類型的_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*auto變量(假設(shè)變量名叫做p)_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
- 當(dāng)
- 不同點(diǎn)(主要是在引用的問題上)
(1)對(duì)象類型的auto變量,根據(jù)傳進(jìn)來時(shí)是__strong還是__weak類型決定調(diào)用copy函數(shù)時(shí)Block內(nèi)部對(duì)傳進(jìn)來的變量進(jìn)行強(qiáng)還是弱引用。
(2)如果時(shí)__block類型的變量,比如__block int age = 20;,它被封裝成一個(gè)OC對(duì)象,調(diào)用copy函數(shù)時(shí)Block內(nèi)部直接對(duì)它產(chǎn)生強(qiáng)引用,對(duì)它的內(nèi)存進(jìn)行管理,不存在__weak修飾int age這種操作,所以沒有弱引用這一說。(這里強(qiáng)引用的是age轉(zhuǎn)換成的結(jié)構(gòu)體對(duì)象,真正的age變量的值存儲(chǔ)在結(jié)構(gòu)體里邊);
但是如果是下面代碼
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object = [[NSObject alloc] init];
__block __weak NSObject *weakObject = object;
DJTBlock block = ^{
NSLog(@"%p %p", object, weakObject);
};
block();
}
return 0;
}
編譯成C++:
struct __Block_byref_weakObject_0 {
void *__isa;
__Block_byref_weakObject_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__weak weakObject;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__strong object;
__Block_byref_weakObject_0 *weakObject; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _object, __Block_byref_weakObject_0 *_weakObject, int flags=0) : object(_object), weakObject(_weakObject->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到在__block修飾變量生成的結(jié)構(gòu)體 __Block_byref_weakObject_0內(nèi)部,通過__weak弱引用變量weakObject,即Block結(jié)構(gòu)體內(nèi)部是一個(gè)強(qiáng)指針指向__block生成的結(jié)構(gòu)體,即這句代碼
__Block_byref_weakObject_0 *weakObject;
(注意雖然名字中有`weak`但這是一個(gè)強(qiáng)指針)
而在結(jié)構(gòu)體__Block_byref_weakObject_0內(nèi)部:
NSObject *__weak weakObject;
這才是一個(gè)弱指針,指向外部傳入的弱引用對(duì)象weakObject,它表達(dá)了外部傳入變量的類型是__weak還是__strong

注意:這里在MRC下有個(gè)特殊情況,在__block生成的結(jié)構(gòu)體內(nèi)部,始終都是弱引用,不會(huì)對(duì)外邊對(duì)象進(jìn)行強(qiáng)引用。

在
MRC環(huán)境下驗(yàn)證, 下面代碼在block();調(diào)用前person就已經(jīng)掛了,說明確實(shí)內(nèi)部沒有強(qiáng)引用:
#import "DJTPerson.h"
typedef void (^DJTBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block DJTPerson *person = [[DJTPerson alloc] init];
DJTBlock block = [^{
NSLog(@" %p", person);
} copy];
[person release];
block();
}
return 0;
}
七、Block相關(guān)問題
-
Block的原理是怎樣的?本質(zhì)是什么? -
__block的作用是什么?有什么使用注意點(diǎn)? -
Block的屬性修飾詞為什么是copy?使用Block有哪些使用注意? -
Block在修改NSMutableArray,需不需要添加__block?
理解上邊原理再回答這些問題應(yīng)該不難吧。




