首先我们来看这么一段代码案例
*********************CLPerson.h*********************
#import <Foundation/Foundation.h>
@interface CLPerson : NSObject
@property (nonatomic,assign) int age;
@end
*********************CLPerson.m*********************
#import "CLPerson.h"
@implementation CLPerson
-(void)dealloc {
NSLog(@"%s",__func__);
}
@end
*********************main.m*********************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
{//临时作用域开始
CLPerson *person = [[CLPerson alloc] init];
person.age = 10;
}//临时作用域结束
NSLog(@"-----------flag1");
}
return 0;
}
通过在打印标记
flag1
处断点调试可看出,在临时作用域里面的person
对象只要出了作用域就会被释放,这一点是很好理解的。
上面的代码加入block
,调整如下
*********************CLPerson.h*********************
#import <Foundation/Foundation.h>
@interface CLPerson : NSObject
@property (nonatomic,assign) int age;
@end
*********************CLPerson.m*********************
#import "CLPerson.h"
@implementation CLPerson
-(void)dealloc {
NSLog(@"%s",__func__);
}
@end
*********************main.m*********************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
typedef void(^CLBlock)(void);//➕➕➕
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLBlock myBlock;//➕➕➕
{//临时作用域开始
CLPerson *person = [[CLPerson alloc] init];
person.age = 10;
myBlock = ^{//➕➕➕
NSLog(@"---------%d",person.age);
};
}//临时作用域结束
NSLog(@"-----------flag1");
}
return 0;
}
再次在打印标记flag1
处断点调试运行一下
结果告诉我们,出了临时作用域,
person
对象没有被释放。这里有两个注意点:
- 由于现在是ARC环境,
myBlock
属于强指针,因此在将block对象赋值给myBlock
指针的时候,编译器会自动对block对象执行copy
操作,因此赋值完成后,myBlock
指向的是一个堆空间上的block对象副本。
顺便,通过终端命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
,拿到编译后的.cpp文件,我们先查看一下当前代码编译底层样式,经整理简化后如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
CLPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CLPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
CLPerson *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_main_2cca58_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
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*/);
}
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;
CLBlock myBlock;
{
CLPerson * person = objc_msgSend(objc_msgSend(objc_getClass("CLPerson"),
sel_registerName("alloc")
),
sel_registerName("init")
);
objc_msgSend(person,
sel_registerName("setAge:"),
30
);
myBlock = objc_msgSend(&__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
person,
570425344),
sel_registerName("copy")
);
}
}
return 0;
}
以上是 【ARC环境-->堆上的block
-->强指针CLPerson *person
】所对应的运行结果以及底层实现。我们发现于之前捕获一个基本类型的auto
变量所不同的是,当block捕捉对象类型的auto
变量的时候,__main_block_desc_0
结构体里面多了两个彩蛋
- 函数指针
copy
,也就是__main_block_copy_0()
,内部调用了_Block_object_assign()
- 函数指针
dispose
,也就是__main_block_dispose_0()
,内部调用了_Block_object_dispose()
这里还需要注意的是,ARC 下CLPerson *person
被认为是强指针,等价于_strong CLPerson *person
,而弱指针需要显式地表示为__weak CLPerson *person
。通过终端命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m -o main.cpp
,可以看到block
的内捕获到的person
指针如下
为了对比,我们再分别看一下下面三种 场景分别是什么情况的:
- ARC环境-->堆上的
block
-->弱指针__weak CLPerson *person
- ARC环境-->栈上的
block
-->强指针CLPerson *person
- ARC环境-->栈上的
block
-->弱指针__weak CLPerson *person
【ARC环境-->堆上的block
-->弱指针__weak CLPerson *person
】案例如下
***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
typedef void(^CLBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLBlock myBlock;
{//临时作用域开始
__weak CLPerson * person = [[CLPerson alloc] init];
person.age = 30;
myBlock = ^{
NSLog(@"---------%d",person.age);
} ;
}//临时作用域结束
NSLog(@"-------------");
}
NSLog(@"------main autoreleasepool end-------");
return 0;
}
block的底层结构如下
运行结果显示堆上的block使用弱指针
__weak CLPerson *person
,没有影响person
所指向对象的生命周期,出了临时作用域的之后就被释放了。
【ARC环境-->栈上的block
-->强指针CLPerson *person
】
***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
typedef void(^CLBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLBlock myBlock;
{//临时作用域开始
CLPerson * person = [[CLPerson alloc] init];
person.age = 30;
^{
NSLog(@"---------%d",person.age);
} ;
}//临时作用域结束
NSLog(@"-------------");
}
NSLog(@"------main autoreleasepool end-------");
return 0;
}
block底层结构如下
运行结果显示栈上的block使用强指针CLPerson *person
,没有影响person
所指向对象的生命周期,出了临时作用域的之后就被释放了。
【ARC环境-->栈上的block
-->弱指针__weak CLPerson *person
】
***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
typedef void(^CLBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLBlock myBlock;
{//临时作用域开始
__weak CLPerson * person = [[CLPerson alloc] init];
person.age = 30;
^{
NSLog(@"---------%d",person.age);
} ;
}//临时作用域结束
NSLog(@"-------------");
}
NSLog(@"------main autoreleasepool end-------");
return 0;
}
block底层结构为
运行结果显示栈上的block使用弱指针__weak CLPerson *person
,没有影响person
所指向对象的生命周期,出了临时作用域的之后就被释放了。
从上面的四种情况的对比看出,只有堆上的block
使用强指针CLPerson *person
的时候,才会影响该指针所指对象的生命周期,这是怎么一回事呢?
【这个时候就要回到我们上面发现的那两个彩蛋了】也就是__main_block_desc_0
中的函数指针copy
和dispose
。实际上这两个函数指针分别是在block从栈空间拷贝到堆空间的时候,以及堆空间的block被释放的时候使用的。
当block
从栈拷贝到堆时,系统会通过block
中的函数指针copy
调用函数__xxx_block_copy_x
__xxx_block_copy_x(struct __main_block_impl_0*dst,
struct __main_block_impl_0*src
);
注意这个函数的两个参数,
- 第一个
dst
代表拷贝之前栈空间的block, - 第二个
src
代表拷贝之后堆空间上的block。
这个函数里面调用的是_Block_object_assign
函数
_Block_object_assign((void*)&dst->person,
(void*)src->person,
3/*BLOCK_FIELD_IS_OBJECT*/
);
这个函数的作用是把dst
(栈block)内部所捕获的那个person
对象(指针)的值赋值(assign)给src
(堆block)内的person
指针。
如果是ARC环境,会根据person
的强弱性(也就是之前的修饰词 __weak
和 __strong
)来决定src
(堆block)是否持有对象。如果person
是被__strong
修饰,就会通过 [person retain]
,使得person
所指向的对象的引用计数+1,这样,src
(堆block)就持有了(也就是强引用)person
所指向的对象。
当堆上的block
即将被释放的时候,系统会通过block
中的函数指针dispose
调用函数__xxx_block_dispose_x
__main_block_dispose_0(struct __main_block_impl_0*src);
很明显其中的参数src
就是堆上的block
,该函数内部调用了_Block_object_dispose
函数
_Block_object_dispose((void*)src->person,
3/*BLOCK_FIELD_IS_OBJECT*/
);
ARC下,该函数的作用是,根绝src
(堆block)内部的person
指针的强弱性(__weak/__strong
),决定如何处理person
所指向的对象,如果是__strong
,说明src
持有了person
所指向的对象(也就是强引用),因为此时src即将被释放,所以需要放开持有,也即是调用一下[person release]
,使得person
所指对象的引用计数-1。
为什么上面我都强调了ARC环境下 呢,因为MRC里面是没有__weak
和__strong
这两个东西的。ARC就是根据我们代码中的__weak/__strong
标记,来自动进行一些内存管理相关的处理。相信上面的分析应该能让你有所领悟。
如果回到MRC,其实内存管理本质原则没有变化,无非就是retain
和release
操作,我个人认为过程会相对ARC来说更简单一些。
首先,内部使用了auto变量(基础类型/对象类型)的block
是在栈空间上的,它不会对对象类型的auto变量
进行retain
操作,也就是不会持有该对象。
当我们对一个栈block调用copy
方法进行拷贝操作的时候,会跟ARC一样以相同的方式最终调用_Block_object_assign
函数,只不过此时会直接对堆block上 所捕获的对象进行调用retain
方法,进行持有,不会进行__weak/__strong
的判断,因为MRC里面根本没有这两个小兄弟。
当一个堆上的block即将释放的时候,也会最终调用_Block_object_dispose
函数,对该block
所捕获的对象调用release
方法,这样堆上的block就放开了对该对象的持有(强引用)。
最后,在通过几张图示,来说明一下【block对于对象类型的auto变量的捕获原理】
以上就是block对于对象类型的auto变量
的捕获过程。