Block的使用规范
什么是Blocks?
- 一句话简单概括一下:带有自动变量(局部变量)的匿名函数
顾名思义:Block没有函数名,另外Block带有^标记,插入记号便于查找到Block - Block也被称作闭包、代码块。也就是说我们可以把我们想要执行的代码封装在这个代码块中,我们需要的时候直接进行调用
- Block共享局部作用域的数据。如果实现一个方法,并且该方法定义一个块,则该块可以访问该方法的局部变量和参数(包括堆栈变量),以及函数和全局变量(包括实例变量)。这种访问是只读的,如果使用__block修饰符来声明变量,我们就可以在Block内修改其值。这些我们后面再做详解
Block语法
格式
标准格式
例子:
^int (int count){return count + 1;}
省略格式
返回值类型默认void
如果是void 我们也可以默认省略(void)
另外如果没有参数的话也就直接相当于省略了参数列表部分
^{return count + 1;}
Block变量
Block变量类似于函数指针
声明Block类型变量仅仅是将声明函数指针类型变量的"*“变为”^"(不考虑参数的情况下)
例如:
函数指针的声明:int result =(*funcptr)(10);
或int result =(*funcptr)(a)
;
Block变量的声明:int (^blk)(int);或int (^blk)(int a)
;
block变量与c语言变量完全相同,可以作为以下用途:
- 自动变量(局部变量)
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
完整格式
完整的格式就是:变量声明+定义
int (^sumOfNumbers)(int a, int b) = ^int (int a, int b) {
return a + b;
};
int sum = sumOfNumbers(1,5);
NSLog(@"sum = %d", sum);
打印结果为:
Block-Test[63351:3799010] sum = 6
截获自动变量
定义时的:带有自动变量值在Block中表现为“截获自动变量值”。
int val = 10;
void (^blk)(int a) = ^(int a){
printf("a = %d beforeVal = %d\n", a, val);
};
val = 2;
blk(val);
运行结果是:a = 2 beforeVal = 10
beforeVal
并不是咱们所修改后的2
也就是说变量在代码运行到定义那一块时就被截获了,执行的时候已经不是原变量了,但是传进block的参数的值还是以调用block时的为准。
值得注意的点: 在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,例子如下
const char text[] = "hello";
void (^blkTest)(void) = ^ {
NSLog(@"%c\n", text[2]);
};
我们可以看到报错信息:无法引用块内具有数组类型的声明。
然而使用指针表示这个数组就没有问题了:
可以注意到现在已经没有报错了,而且运行的结果是:Block-Test[65785:3914212] l
__block说明符
block可以截获变量,但是在块里不能修改变量的值
修改会报错
此时我们使用__block修饰符修饰变量,对需要在block内进行赋值的变量,使用修饰符修饰,保证可以对变量进行赋值
例如:
__block int val = 10;
void (^blk)(void) = ^{
//val = 5;
printf("val = %d\n",val);
};
val = 2;
blk();
return 0;
现在我们的运行结果就是:val = 2
如果我们将里面注释的://val = 5
;改为不注释,如下:
__block int val = 10;
void (^blk)(void) = ^{
val = 5;
printf("val = %d\n",val);
};
val = 2;
blk();
return 0;
那么运行结果就会变为:val = 5
Block的循环引用
- (id)init {
self = [super init];
blk_ = ^{NSLog(@"self = %@", self);};
return self;
}
可以看到这是一个类的初始化方法,我们在初始化这个类的实例时就会造成循环引用,因为Block语法赋值在了成员变量blk中,因此通过Block语法生成在栈上的Block此时由栈复制到了堆,并持有这个self。
self持有Block,Block持有self,形成了循环引用。
解决Block循环引用的方法
__weak__strong协作(强弱共舞)
- (id)init {
self = [super init];
__weak typeof(self) weakSelf = self;
blk_ = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"self = %@", strongSelf);
};
return self;
}
block对象,并没有引用self,在执行block的时候strongSelf的生命周期只有在block内部,在block内部,self的引用计数+1,当执行完block,引用计数-1。既没有引起循环引用,又适当延长了self的生命周期,一举双得。
__block
- (id)init {
self = [super init];
__block Person *blockSelf = self;
blk_ = ^{
NSLog(@"self = %@", blockSelf);
blockSelf = nil;
};
return self;
}
使用这种方式,同样也可以解决循环引用,但是要注意,block执行完一次,下一次执行之前记得要给blockSelf重新赋值,不然会出问题,显然维护这个是非常麻烦的,所以不推荐。
参数传递
- (id)init {
self = [super init];
__block Person *blockSelf = self;
blk_ = ^(Person * _Nonnull selfTest) {
NSLog(@"self = %@", selfTest);
};
blk_(self);
return self;
}
通过block的参数进行传递,同样可以解决循环引用(由于函数参数作用域的特点),但是这样做的意义不大,因为block在执行的地方,一定是需要获取到self的,如果已经获取到self了,就可以直接对self操作了,再使用block有点多余。应用并不多,只做了解。
Block的实现
BLock的实现是基于指针和函数指针,Block属性是指向结构体的指针。
我们可以将block代码通过clang重写为C++代码,看其底层实现:
//这是正常的block流程
void(^myBlock)(void) = ^{
printf("myBlock");
};
myBlock();
重写命令的方法(先在终端cd进对应项目文件夹):
//x86架构写法
xcrun -sdk iphoneos clang -rewrite-objc <OriginFile> -o <CppFile>
//arm64架构写法
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc < OC FileName> -o <Cpp FileName>
//注意实际写文件名的时候不要带<>
得到Block的结构的源码定义(C++):
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
我们来分析一下:
初始化定义部分block语法变成了:&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
调用block的过程变成了:(__block_impl *)myBlock)->FuncPtr
接着我们来看一下其中涉及到的对应的部分:
初始化Block部分
//Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
所以我们可以看出,__main_block_impl_0结构体也就是Block结构体包含了三个部分:
- 成员变量impl
- 成员变量Desc指针
- __main_block_impl_0构造函数
struct __block_impl结构
包含Block实际函数指针的结构体:
struct __block_impl {
void *isa;//用于保存Block结构体的实例指针
int Flags;//标志位
int Reserved;//今后版本升级所需的区域大小
void *FuncPtr;//函数指针
};
__block_impl
包含了Block实际函数指针FuncPtr
,FuncPtr
指针指向Block的主体部分,也就是Block对应OC代码中的^{...}
的部分- 还包含了标志位
Flags
,在实现block
的内部操作时可能会用到 - 今后版本升级所需的区域大小
Reserved
__block_impl
结构体的实例指针isa
struct __main_block_desc_0
Block附加信息结构体:包含今后版本升级所需区域的大小,Block的大小
static struct __main_block_desc_0 {
size_t reserved;//今后版本升级所需区域大小
size_t Block_size;//Block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
关于构造函数对各个成员变量的赋值我们回到上面刚刚转成C++的地方(将其转换为可读性更高的形式):
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &temp;
该代码通过__main_block_impl_0
构造函数,生成的__main_block_impl_0
结构体(Block结构体)的实例的指针,赋值给__main_block_impl_0
结构体(Block结构体)类型的指针变量myBlock
可以看到,调用Block结构体构造函数的时候,传入了两个参数:
第一个参数:__main_block_func_0
其就是Block对应的Block语法的主体部分,看一下__main_block_func_0
结构体在底层C++部分的定义
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("myBlock");
}
这是一个C语言函数,函数里面的内容和我们OC block中编写的操作是一样的。
这里传入的参数__cself是指向Block的值的指针变量,相当于OC的self
构造函数传入的第二个参数是:__main_block_desc_0_DATA
包含该Block的相关信息
Block调用部分
Block结构体和成员变量已经分析完了,我们来看一下是如何调用Block的:
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
myBlock
结构体的第一个成员变量为__block_impl
,所以myBlock的首地址,就是__block_impl impl
的首地址,也就意味着我们第一个强制类型转换可以直接转换成__block_impl
类型((void (*)(__block_impl *))
是__block_impl
中Func
的类型((__block_impl *)myBlock)->FuncPtr)
调用__main_block_func_0
函数((__block_impl *)myBlock)
;是__main_block_func_0
函数的参数- 对于这个调用参数我的理解是传递其Block本身到
__main_block_func_0
函数的参数__cself
中去,这也就可以看出Block
自身正是作为参数进行的传递
Block的实质总结
用一句话来说,Block是个对象(其内部第一个成员为isa
指针)
在构造函数中,我们可以看到impl.isa = &_NSConcreteStackBlock
;
我们对isa
指针进行了赋值,_NSConcreteStackBlock
相当于该block
实例的父类。在将Block
作为OC对象调用时,关于该类的信息放置于_NSConcretestackBlock
中,这也证明了 block出生就是在栈上(isa指针指向_NSConcreteStackBlock)
Block拥有捕获变量的能力:
关于捕获变量这一点,我们举一个例子:
//相比前面的例子就是在定义Block之前多定义了一个NSObject *obj对象,以供Block去捕获
NSObject *obj = [[NSObject alloc] init];
//这是正常的block流程
void(^myBlock)(void) = ^{
printf("myBlock");
};
myBlock();
然后我们用clang
编译为C++的形式:
//Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
//可以看到下方相比之前的例子多了一个NSObject *obj;对象,这个就是Block所捕获的变量,也证实了block有捕获变量的能力
NSObject *obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看到编译后在Block结构体里面多出了一个NSObject *obj
;对象,这个就是Block所捕获的变量,也证实了block有捕获变量的能力。
Block的类型
Block根据其类型可以分为三类:
- 全局Block(数据区) NSGlobalBlock(_NSConcreteGlobalBlock)
- 栈Block(栈区) NSStackBlock(_NSConcreteStackBlock)
- 堆Block(堆区) NSMallocBlock(_NSConcteteMallocBlock)
而其区分的规则为:
如果没有引用局部变量,或者只引用了静态变量和全局变量,则为全局Block,如果内部有使用局部变量,如果有被强指针引用过,就是堆Block,如果没有则为栈Block。
代码例子如下:
- (void)func2 {
//静态变量
static int blockInt = 2;
/**
— 全局block,没有使用局部变量,或者只使用了静态变量或者只使用了全局变量
*/
// 没有使用局部变量
NSLog(@"block0 - %@",^{});
// 使用了静态变量
void(^block1)(void) = ^{
blockInt = 3;
};
NSLog(@"block1 - %@",block1);
/**
- 堆Block 使用局部变量 并且用强指针引用过
*/
// 即使用局部变量又使用静态变量
NSInteger i = 1;
void(^block2)(void) = ^{
NSLog(@"block %ld", i);
NSLog(@"block static %d", blockInt);
};
NSLog(@"block2 - %@",block2);
// 只使用局部变量
void(^block3)(void) = ^{
NSLog(@"block %ld", i);
};
NSLog(@"block3 - %@",block3);
// 使用强指针引用过,再使用弱指针引用
void(^ __weak block4)(void) = block3;
NSLog(@"block4 - %@",block4);
/**
- 栈Block没有被强引用过的
*/
// 没有使用强指针引用过
void (^__weak block5)(void) = ^{
NSLog(@"block %ld", i);
};
NSLog(@"block5 - %@",block5);
}
分析:
- block0 : 没有使用任何变量,属于全局block。
- block1 : 只使用了静态变量blockInt,属于全局block。
- block2 : 使用了局部变量和静态变量,并且有被strong引用过,属于堆block
- block3 : 使用了局部变量i,并且有被strong引用过,属于堆block
- block4 : 虽然被weak指针引用的,但其已经被strong引用过,属于堆block
- block5 : 没有被strong指针引用过。即使使用了局部变量,属于栈block
下面我们总结一下这三种类型的Block:
NSGlobalBlock
没有截获自动变量或者没有被强引用过的话就是NSGlobalBlock,上面我们已经举过一个例子了,现在我们再举一个更常见的例子:
记述全局变量的地方定义Block就会默认为Global
//在记述全局变量的地方定义Block
void (^myGlobalBlock)(void) = ^{
NSLog(@"this is GlobalBlock");
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
//打印类型
NSLog(@"myGlobalBlock- %@", myGlobalBlock);
}
return 0;
}
NSStackBlock
除了上述的情况,其他情况下创建的Block都是NSConcreteStackBlock对象(只要没有被强引用)
其存储在栈区。如果其所属的变量作用域结束,则该Block就会被废弃,如果Block使用了__block变量,则__block变量同样被废弃
NSMallocBlock
arc下没有栈block了。因为block会自动copy变成堆block
为了解决栈上的Block在变量作用域结束被废弃这一问题,Block提供了复制的功能,可以将Block对象和__block变量从栈区复制到堆区上。
所以后面即使栈区的作用域结束时,堆区上的Block和__block变量仍然可以继续存在,也可以继续使用
block作为属性,用什么关键字修饰呢?
MRC下: 使用copy修饰。因为block申明在栈区,使用copy修饰可以将block从栈区copy到堆区。(现已优化,使用copy或strong修饰符都可以copy到堆区)
ARC下: 使用copy与strong修饰都可以。因为就算用strong,程序会自动copy将block从栈区copy到堆区。
实际测试中,无论是ARC还是MRC,strong和copy都可以使对象复制到堆上。 测试例子如下:
@interface block : NSObject {
void (^blk_)(void);
void (^blk2_)(void);
};
@property (nonatomic, strong) void (^block)(void);
@property (nonatomic, copy) void (^block2)(void);
- (void) test2;
@end
#import "block.h"
@implementation block
- (void) test2 {
int a = 2;
_block = ^ {
NSLog(@"%d", a);
};
_block2 = ^ {
NSLog(@"%d", a);
};
blk_ = [_block copy];
blk2_ = [_block2 copy];
NSLog(@"_block = %@, _block2 = %@, blk_ = %@, blk2_ = %@", _block, _block2, blk_, blk2_);
}
@end
即使是MRC,strong和copy都可以使对象复制到堆上。
拷贝情况:
在ARC环境下编译器会进行判断,三种情况会自动复制。
- Block作为函数返回值返回
- 向方法或函数中传递Block,使用以下两种方法的情况下
Cocoa框架的方法且方法名中含有usingBlock等时
GCD的API
- 将Block赋值给类的附有__strong修饰符的id类型 或 Block类型的成员变量时
- (void) test2 {
int a = 2;
void (^blkTest)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"blkTest = %@", blkTest);
}
当然有自动拷贝我们也可以手动拷贝
我们手动拷贝对不同的Block类有不同的效果:
- 栈区 -> 从栈拷贝到堆
- 数据区 -> 不改变
- 堆区 -> 引用计数增加
__block变量的拷贝:
在使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响
按照oc的内存管理机制来管理,此时两者的关系就从block使用__block变成了block持有__block
__block变量的配置存储区域 | Block从栈复制到堆时的影响 |
---|---|
堆区 | 被Block所持有,引用计数加1 |
栈区 | 从栈复制到堆,并被Block所持有 |
当然,如果不再有Block引用该__block变量,那么该__block变量也会被废除
Block捕获变量
ARC的时候我们就了解过,变量的几种修饰符,我们来看一下Block如何捕获不同修饰符的类型变量
先把几个结论扔出来:
- 全局变量:不捕获
- 局部变量:捕获值
- 静态全局变量:不捕获
- 静态局部变量:捕获指针
先一个一个看:
捕获局部变量的例子
int c = 30;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int a = 10, b = 20;
void(^myLocalBlock)(void) = ^{
NSLog(@"%d %d %d",a,b,c);
};
c = 50;
void(^Block)(int, int, int) = ^(int a, int b, int c) {
NSLog(@"%d %d %d",a,b,c);
};
a = 40;
b = 50;
myLocalBlock();
Block(a,b,c);
}
return 0;
}
打印结果为:
Block-Test[70423:4137195] 10 20 50
Block-Test[70423:4137195] 40 50 50
可以看到,直接访问会被捕获
间接不会被捕获
Block表达式截获所使用的局部变量的值,保存了该变量的瞬时值。
Block的自动变量的截获只针对Block中使用到的自动变量,没有使用到的自动变量不捕获
直接访问局部变量
为什么仅仅截获瞬时值,而不是局部变量的当前值?看一下对应的c++源码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
int b;
//下面的构造函数比以往多出了 : a(_a), b(_b) ,这个:后面的操作意识就是给Block自身的成员变量_a,_b赋值
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_3bac69_mi_1,a,b,c);
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10, b = 20;
void(*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
c = 50;
a = 40;
b = 50;
((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
}
return 0;
}
可以看到__main_block_impl_0
结构体里多出了两个成员变量a,b,这两个的值来自__main_block_impl_0
构造函数中传递的值
构造函数加冒号就是相当于一个赋值的过程(这点很重要)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
- 在
__main_block_func_0
结构体中,变量a,b的值使用的是__cself
获取的值,即a = __cself->a; b = __cself->b
;,这也说明了a,b只是Block内部的变量,改变Block外部的局部变量值,并不能改变Block内部的变量值 - 我们也可以发现全局变量并没有存储在Block的结构体中,而是在调用的时候通过直接访问的方式来调用
浅浅地总结一下这个流程:
Block语法部分的源码就是
调用__main_block_impl_0
的构造函数,构造函数正常有两个参数,func_0
和desc_0
,在截获自动变量时,会把需要截获的自动变量也放入参数列表中,同时__main_block_impl_0
中也会增加两个成员变量a,b,构造函数带参数就是自动给这两个成员变量赋值。在调用func_0
时,直接通过__cself
(相当于self
,其传递的参数也是其Block本身)的a、b
(此时的a,b就是Block成员变量中的,因为上面的构造函数已经赋值了)
通过传值间接访问局部变量
源码如下:
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself, int a, int b, int c) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_3bac69_mi_2,a,b,c);
}
static struct __main_block_desc_1 {
size_t reserved;
size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10, b = 20;
c = 50;
void(*Block)(int, int, int) = ((void (*)(int, int, int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA));
a = 40;
b = 50;
((void (*)(__block_impl *, int, int, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, a, b, c);
}
return 0;
}
__main_block_impl_1
结构体并没有变量a、b
,说明通过直接传值的方式,变量并没有存进Block
的结构体中。func_0
函数中,直接通过参数列表把a,b
传进去了,并且在调用时直接传入a,b,c
的值这是和直接调用局部变量不同的,并没有直接调用局部变量的那个用:赋值的操作。
static修饰变量的捕获
首先了解一下静态变量的概念:
- 静态变量一经创建,只要进程还在它就一直在内存的静态存储区当中存在,也可以认为是其内存地址不变,直到整个程序运行结束
- 静态变量虽在程序的整个执行过程中始终存在,但是在它作用域之外不能使用。
- 所有的全局变量都是静态变量,而局部变量只有定义时加上类型修饰符static,才为局部静态变量。
- 静态变量可以在任何可以申请的地方申请,一旦申请成功后,它将不再接受其他的同样申请。
- 静态变量并不是说其就不能改变值,不能改变值的量叫常量。 其拥有的值是可变的 ,而且它会保持最新的值。
- 所以静态变量的概念和全局变量是有些相似的。
下面我们看Block中对于静态变量的捕获情况:
static int c = 30;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
static const int a = 10;
static int b = 20;
void (^Block)(void) = ^{
printf("a = %d, b = %d, c = %d\n",a, b, c);
};
b = 100;
c = 100;
Block();
}
return 0;
}
输出结果为:
a = 10, b = 100, c = 100
从上面的例子中我们可以看出,Block对象好像没有捕获静态全局变量和静态局部变量,那么具体有没有捕获还得看源码分析:
static int c = 30;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const int *a;
int *b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int *_a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const int *a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static const int a = 10;
static int b = 20;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a, &b));
b = 100;
c = 100;
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
我们发现:__main_block_impl_0
结构体中,静态局部变量static int b
以指针int *b
;的形式添加为成员变量,而静态局部常量static const int a
以const int *
指针的形式添加为成员变量。而全局静态变量并没有添加为成员变量
我们接着看下方的func_0
函数中,静态全局变量直接访问,静态局部变量和静态局部常量都是通过指针传递:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const int *a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}
但是const修饰的无法进行赋值操作(毕竟是常量):
我们为什么能获取static变量最新的值?
这个问题又扯到了静态变量的概念了
static修饰的,均存储在全局存储区,该地址在程序运行过程中一直不会改变,所以能访问最新值。
static在修饰后,全局变量直接访问,局部变量指针访问(只要含static变量)。
const修饰变量的捕获
代码例子如下:
const int c = 30;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
const int a = 10;
int b = 20;
void (^Block)(void) = ^{
printf("a = %d, b = %d, c = %d\n",a, b, c);
};
Block();
}
return 0;
}
输出结果:
a = 10, b = 20, c = 30
源码如下
const int c = 30;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const int a;
int b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int _a, int _b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const int a = __cself->a; // bound by copy
int b = __cself->b; // bound by copy
printf("a = %d, b = %d, c = %d\n",a, b, c);
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
const int a = 10;
int b = 20;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
可以看到:
- const全局变量仍然是直接访问的
- const局部变量和正常的自动变量一样,依然是值传递
总结
- 全局变量: 不捕获
- 局部变量: 捕获值
- 静态全局变量: 不捕获
- 静态局部变量: 捕获指针
- const修饰的局部常量:捕获值
- const修饰的静态局部常量:捕获指针
Block捕获对象
代码例子如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
id obj = [NSMutableArray array];
void (^Block)(void) = ^{
NSLog(@"%@",obj);
};
[obj addObject:@1];
Block();
}
return 0;
}
输出结果为:
Block-Test[71641:4198978] (
1
)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
id obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_4a265c_mi_1,obj);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
id obj = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)obj, sel_registerName("addObject:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
- 我们看到
__main_block_impl_0
结构体中多了一个成员变量__strong id obj
;因为obj
是自动变量,所以这里捕获了自动变量obj
作为_main_block_impl_0
结构体的成员变量。 fun0
也是指针传递(结构体外面自动变量是什么类型,结构体里面成员变量也是什么类型,id是指针类型,所以结构体里面也是指针类型),func_0
也有__strong id obj = __cself->obj
;这样的赋值操作- 但是这里的源码多出了两个函数指针:
//其中调用的_Block_object_assign相当于retain,将对象赋值在对象类型的结构体变量__main_block_impl_0中。在栈上的Block复制到堆时会进行调用。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
//其中调用的_Block_object_dispose相当于release,释放赋值在对象类型的结构体变量中的对象。在堆上Block被废弃时会被调用
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
针对这两个函数指针:
__main_block_copy_0
作用就是调用_Block_object_assign
,相当于retain
,将对象赋值在对象类型的结构体变量__main_block_impl_0
中。在栈上的Block
复制到堆时会进行调用。__main_block_dispose_0
调用_Block_object_dispose
,相当于release,释放赋值在对象类型的结构体变量中的对象。在堆上的Block被废弃时会被调用。
__block修饰符
__block修饰局部变量
代码例子如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
__block int a = 10, b = 20;
void (^Block)(void) = ^{
NSLog(@"%d %d", a, b);
};
a = 100;
b = 100;
Block();
}
return 0;
}
输出结果:
100 100
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __Block_byref_b_1 {
void *__isa;
__Block_byref_b_1 *__forwarding;
int __flags;
int __size;
int b;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Block_byref_b_1 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
__Block_byref_b_1 *b = __cself->b; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_4a8210_mi_1, (a->__forwarding->a), (b->__forwarding->b));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
__Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 20};
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_b_1 *)&b, 570425344));
(a.__forwarding->a) = 100;
(b.__forwarding->b) = 100;
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
我们可以发现,就仅仅添加了一个__block修饰符,就增加了好多内容,接下来我们一步一步来分析:
首先从__main_block_impl_0
看起:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Block_byref_b_1 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们发现__block int a __block int b
分别变成了__Block_byref_a_0 *a
;和__Block_byref_b_1 *b
;,这是结构体类型的指针,对于用__block修饰的变量,不管使用了没,都会相应的生成一个结构体,我们拿其中任何一个结构体举个例子:
struct __Block_byref_a_0 {
void *__isa;//标识对象类的isa实例变量
__Block_byref_a_0 *__forwarding;//传入变量的地址(局部变量a本身的地址)
int __flags;//标志位
int __size;//结构体大小
int a;//存放a实际的值,和之前加__block修饰符时一致,其实就相当于原自动变量
};
接着我们来看一下这个结构体的赋值情况(main函数中):
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
10
};
将这个赋值情况一一对应写成可读性更高的形式是:
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
void *__isa = (void*)0,
__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a,
int __flags = 0,
int __size = sizeof(__Block_byref_a_0),
int a = 10
};
我们可以看到__isa
指针值传空,__forwarding
指向了局部变量a本身的地址,__flags
分配了0,__size
为结构体大小,a赋值为10。
__forwarding
的指向如下图所示:
从main
函数赋值操作__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a
就可以看出__forwarding
是一个指针,它的值是最初的我们在main函数中声明的局部变量a的地址值,所以__forwarding
其实就是局部变量a本身的地址。
现在访问成员变量a的完整流程解释如下:
我们可以看到main函数中(a.__forwarding->a) = 100;
,说明现在想要访问自动变量a,就需要__main_block_impl_0
结构体中的__Block_byref_a_0 *a
;指针先访问__Block_byref_a_0
结构体,接着通过__Block_byref_a_0
结构体中的成员变量__forwarding
访问成员变量a。到这里,整个流程就完成了。
一个问题,那么如果堆上和栈上都有我们的__block,我们如何找到我们需要的那个?
这也就用到了__forwarding
指针,它在没有复制的时候就是简单的指向自己,而当进行复制以后,就会指向堆上的那个__block
变量
栈上不持有仅仅只是使用,复制到堆上才持有:
这里还有一个疑问?为什么我们的__block
变量的__Block_byref_a_0
结构体不直接存放在Block
的__main_block_impl_0
结构体中,而是要将其指针存放在__main_block_impl_0
结构体中,其实原因是这样子的话,我们就可以在多个Block
中使用同一个__block
变量了
__block修饰对象
代码例子如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
__block Test *testFirst = [[Test alloc] init];
NSLog(@"%@",testFirst);
void (^Block)(void) = ^{
NSLog(@"%@",testFirst);
testFirst = [[Test alloc]init];
NSLog(@"%@",testFirst);
};
Block();
}
return 0;
}
打印结果为:
Block-Test[72427:4254748] <Test: 0x10d830bf0>
Block-Test[72427:4254748] <Test: 0x10d830bf0>
Block-Test[72427:4254748] <Test: 0x10d904180>
可以看到未再次在Block中进行初始化之前两次打印的testFirst对象的地址是相同的
源码如下:
struct __Block_byref_testFirst_0 {
void *__isa;
__Block_byref_testFirst_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Test *testFirst;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_testFirst_0 *testFirst; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_testFirst_0 *_testFirst, int flags=0) : testFirst(_testFirst->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_testFirst_0 *testFirst = __cself->testFirst; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_ab9f4e_mi_2,(testFirst->__forwarding->testFirst));
(testFirst->__forwarding->testFirst) = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_ab9f4e_mi_3,(testFirst->__forwarding->testFirst));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->testFirst, (void*)src->testFirst, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->testFirst, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_testFirst_0 testFirst = {(void*)0,(__Block_byref_testFirst_0 *)&testFirst, 33554432, sizeof(__Block_byref_testFirst_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc")), sel_registerName("init"))};
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_ab9f4e_mi_1,(testFirst.__forwarding->testFirst));
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_testFirst_0 *)&testFirst, 570425344));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
继续是先从__main_block_impl_0
结构体开始看:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_testFirst_0 *testFirst; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_testFirst_0 *_testFirst, int flags=0) : testFirst(_testFirst->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
和__block
变量的情况相比并没有什么大的区别。
我们接着来看main函数中的对于__Block_byref_testFirst_0
结构体的初始化。
__attribute__((__blocks__(byref))) __Block_byref_testFirst_0 testFirst = {
(void*)0,
(__Block_byref_testFirst_0 *)&testFirst,
33554432,
sizeof(__Block_byref_testFirst_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"),sel_registerName("alloc")), sel_registerName("init"))
};
转换为可读性更高的形式就是:
__attribute__((__blocks__(byref))) __Block_byref_testFirst_0 testFirst = {
void *__isa = (void*)0;
__Block_byref_testFirst_0 *__forwarding = (__Block_byref_testFirst_0 *)&testFirst;
int __flags = 33554432;
int __size = sizeof(__Block_byref_testFirst_0);
void (*__Block_byref_id_object_copy)(void*, void*) = __Block_byref_id_object_copy_131;
void (*__Block_byref_id_object_dispose)(void*) = __Block_byref_id_object_dispose_131;
Test *testFirst = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"),sel_registerName("alloc")), sel_registerName("init"));
};
flags = 33554432 即二进制的 1 << 25
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler 译:compiler 含有copy_dispose助手[即拥有copy和dispose函数]
这里有两个函数:__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131
:
//其中也是调用了_Block_object_assign方法,在Block拷贝在堆上时对对象进行持有
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
//其中也是调用了_Block_object_dispose方法,在Block从堆上废弃时对__block对象的持有进行释放
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
这两个函数的作用看函数名就知道:__Block_byref_id_object_copy_131
是当__block
对象随Block
从栈拷贝到堆上时调用的,调用后Block
就持有了该对象,在栈上的时候只是使用并没有持有。而__Block_byref_id_object_dispose_131
就是Block
从堆上释放时调用该方法,从而释放掉对__block
对象的持有。
但是我们发现这里调用_Block_object_assign
和 _Block_object_release
的时候还加了40,究竟是为什么呢,我们接着看下面
**struct __Block_byref_testFirst_0 {
void *__isa;//8字节
__Block_byref_testFirst_0 *__forwarding;//8字节
int __flags;//4字节
int __size;//4字节
void (*__Block_byref_id_object_copy)(void*, void*);//8字节
void (*__Block_byref_id_object_dispose)(void*);//8字节
Test *testFirst;
};
**
我们发现__Block_byref_testFirst_0
结构体的起始地址和testFirst
的地址之间相差40字节,所以+40也是为了可以找到testFirst
指针,通过testFirst
指针进行调用。
__block修饰局部变量,这个变量在block内外属于同一个地址上的变量,可以被block内部的代码修改
对于__block类型的对象的捕获中的内存管理逻辑就是:
(以上方代码例子为例:)struct __main_block_impl_0
中的对象指针的成员变量__Block_byref_testFirst_0 *testFirst
;指向__Block_byref_testFirst_0
结构体,再由__Block_byref_testFirst_0
结构体中的__forwarding
指针指向我们所需要的__block
类型的对象。
(其中__forwarding
指针的值就是原本我们所需要的__block
类型的对象的地址值)
Block的内存管理
干预:程序员手动管理,本质还是要系统管理内存的分配和释放
- 自动局部基本类型变量,因为是值传递,内存是跟随Block,不用干预
- static局部基本类型变量,指针传递,由于分配在静态区,故不用干预
- 全局变量,存储在数据去,不用干预
- 局部对象变量,如果在栈上,只是使用,不用干预。但Block在拷贝到堆时,对其retain,在Block对象销毁时,对其release
引用计数相关内容:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
__block Test *testFirst = [[Test alloc] init];
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)testFirst));
NSLog(@"%@",testFirst);
void (^Block)(void) = ^{
NSLog(@"%@",testFirst);
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)testFirst));
Test *testSecond = testFirst;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)testFirst));
};
Block();
NSLog(@"%@", Block);
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)testFirst));
}
return 0;
}
打印结果如下:
Block-Test[73044:4299100] 1
Block-Test[73044:4299100] <Test: 0x10e060070>
Block-Test[73044:4299100] <Test: 0x10e060070>
Block-Test[73044:4299100] 1
Block-Test[73044:4299100] 2
Block-Test[73044:4299100] <__NSMallocBlock__: 0x108e26060>
Block-Test[73044:4299100] 1
我们看到这个例子的引用计数只有在Block中将testFirst
强持有了一次之后才变为2,其他情况全都是1,而且Block的类型结果打印显示Block就是在堆上,让人有些疑惑是不是Block从栈拷贝到堆上的时候没有调用_Block_object_assign
方法。
其实其原因如下:
从栈上拷贝Block到堆上的时候会调用这么一个函数:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
// 拷贝 block,
// 如果原来就在堆上,就将引用计数加 1;
// 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1,并且会调用 copy helper 方法(如果存在的话);
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
// 参数 arg 就是 Block_layout 对象,
// 返回值是拷贝后的 block 的地址
// 运行?stack -》malloc
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
// 如果 arg 为 NULL,直接返回 NULL
if (!arg) return NULL;
// The following would be better done as a switch statement
// 强转为 Block_layout 类型
aBlock = (struct Block_layout *)arg;
const char *signature = _Block_descriptor_3(aBlock)->signature;
// 如果现在已经在堆上
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
// 就只将引用计数加 1
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
// block 现在在栈上,现在需要将其拷贝到堆上
// 在堆上重新开辟一块和 aBlock 相同大小的内存
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
// 开辟失败,返回 NULL
if (!result) return NULL;
// 将 aBlock 内存上的数据全部复制新开辟的 result 上
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
// 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// copy 方法中会调用做拷贝成员变量的工作
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
// isa 指向 _NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;
return result;
}
}
可以看到该函数作用是:
如果原来就在堆上,就将引用计数加 1;
如果原来在栈上,会拷贝到堆上,引用计数初始化为 1
所以这就是新拷贝到堆上的Block的引用计数为什么是1的原因,因为被重新初始化为1啦。
另一个引用计数情况:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Test *testFirst = [[Test alloc] init];
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)testFirst));
NSLog(@"%@",testFirst);
void (^Block)(void) = ^{
NSLog(@"%@",testFirst);
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)testFirst));
Test *testSecond = testFirst;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)testFirst));
};
Block();
NSLog(@"%@", Block);
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)testFirst));
}
return 0;
}
打印结果如下:
Block-Test[73128:4302875] 1
Block-Test[73128:4302875] <Test: 0x1098a0930>
Block-Test[73128:4302875] <Test: 0x1098a0930>
Block-Test[73128:4302875] 3
Block-Test[73128:4302875] 4
Block-Test[73128:4302875] <__NSMallocBlock__: 0x109905460>
Block-Test[73128:4302875] 3
可以看到从进入了Block
之后,__block
对象的引用计数居然从1突增到了3,然后在Block
中经过一次强引用之后引用计数又达到了4,最后出Block
之后那一次强引用释放,引用计数又到了3
其实这个3也好理解:
- 创建好对象之后对象本身持有一次
- 栈上的block没有持有,但是这个第四行的成员变量这个指针持有一次
- copy到堆上之后在堆上的第四行的成员变量这个指针又持有一次
- 所以一共是3次引用计数。
Block循环引用的问题
-
什么时候会发生循环引用,如何解决?
一个对象中强引用了block,在block中又使用了该对象,就会发生循环引用。 -
解决是:将该对象使用__weak或者__block修饰符修饰之后再在block中使用。
变量前加block和weak和strong的区别 -
__strong是为了防止block持有的对象提前释放,__weak和__block是解决循环引用
-
__block不管是ARC还是MRC都可以使用,可以修饰对象和基本数据类型
-
__weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型
-
__block对象可以在Block中被重新赋值,__weak不可以
对于__block变量MRC如何解决循环引用?ARC如何解决? -
MRC解决循环引用用__block,禁止Block对所引用的对象进行retain操作
-
ARC时期__block并不能禁止Block对所引用的对象进行强引用。解决办法可以是在Block中将变量置空,因为需要在Block中对Block进行操作,所以还是需要__block修饰符
__block在MRC下有两个作用
- 允许在Block中访问和修改局部变量
- 禁止Block对所引用的对象进行retain操作
ARC下仅仅只有一个作用
-
允许在Block中访问和修改局部变量
-
解决Block循环引用的方法上文已经介绍了三种,其中最常用的就是:“强弱共舞”
-
__weak是为了解决循环引用
-
__strong是为了防止block持有的对象提前释放