Block的类型
-
block有3种类型:
- NSGlobalBlock (NSConcreteGlobalBlock)
- NSStackBlock (NSConcreateStackBlock)
- NSMallocBlock (NSConcreateMallocBlock)
-
可以通过class或者isa指针查看具体类型,最终都是继承自NSBlock类型
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
}
return 0;
}
2020-06-03 21:42:23.579757+0800 Interview01-Block的本质[56222:9306988] __NSGlobalBlock__
2020-06-03 21:42:23.581321+0800 Interview01-Block的本质[56222:9306988] __NSGlobalBlock
2020-06-03 21:42:23.581831+0800 Interview01-Block的本质[56222:9306988] NSBlock
2020-06-03 21:42:23.585482+0800 Interview01-Block的本质[56222:9306988] NSObject
因为其继承自NSObject对象,所以block都isa指针,所以也可以把block看成一个对象
打印并且编译一下三种block
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;
}
}
打印结果
2020-06-03 21:48:28.625843+0800 Interview01-Block的本质[56274:9310988] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
编译结果($ xcrun -sdk iphoneos clang --arch arm64 rewrite-objc main.m)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int age;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __main_block_impl_2 {
struct __block_impl impl;
struct __main_block_desc_2* Desc;
int age;
__main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
发现这里都是_NSConcreteStackBlock。和运行时不一样,但是一切以运行时的结果为主,因为运行时可能进行了处理,而且编译完的代码未必是真正的oc代码
Block的内存分配
应用程序的内存地址分类是从低地址向高地址的(栈区最高)
- .text段: 代码
- .data段:全局变量
- 堆:动态分配内存,程序员申请内存,管理内存
- 栈:局部变量,离开大括号就自动销毁
int age_ = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
NSLog(@"数据段:age_ %p", &age_);
NSLog(@"栈:a %p", &a);
NSLog(@"堆:obj %p", [[NSObject alloc] init]);
NSLog(@"数据段:class %p", [RLPerson class]);
}
return 0;
}
打印结果:
2020-06-29 14:09:54.578086+0800 TestBlock[3428:10823423] 数据段:age_ 0x1000011d0
2020-06-29 14:09:54.578513+0800 TestBlock[3428:10823423] 栈:a 0x7ffeefbff51c
2020-06-29 14:09:54.578605+0800 TestBlock[3428:10823423] 堆:obj 0x100502f80
2020-06-29 14:09:54.578705+0800 TestBlock[3428:10823423] 数据段:class 0x1000011a8
可以知道RLPerson地址和age_地址相似,所以是类是存存放在数据段
MRC下的Block
首先关闭ARC: Target -> Build Setting -> Objctive-C Automatic Reference Counting 设置为NO
先观察访问是否auto变量的block类型
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Global:没有访问auto变量
void (^block1)(void) = ^{
NSLog(@"block1---------");
};
// Stack:访问了auto变量
int age = 10;
void (^block2)(void) = ^{
NSLog(@"block2---------%d", age);
};
NSLog(@"%@-%@", [block1 class], [block2 class]);
}
return 0;
}
打印结果:
2020-06-03 22:03:52.335076+0800 Interview01-Block的本质[56499:9322480] __NSGlobalBlock__-__NSStackBlock__
可以发现访问了auto变量的为__NSStackBlock__类型 __NSStackBlock__的存在
void (^block)(void);
void test2()
{
// NSStackBlock
int age = 10;
block = ^{
NSLog(@"block---------%d", age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test2();
block();
}
return 0;
}
打印结果:
2020-06-03 22:08:45.533995+0800 Interview01-Block的本质[56553:9325730] block----------272632888
发现age的值不对。这里是因为栈上的block在执行完test2()后,这里栈上的内存就可能是垃圾数据,导致打印的值有问题
如何处理这种情况呢?就是将block变成_NSMallocBlock,这里只要将__NSStackBlock__copy之后就会变成_NSMallocBlock_了。就可以在堆中控制block的生命周期。
void (^block)(void);
void test2()
{
// NSStackBlock
int age = 10;
block = [^{
NSLog(@"block---------%d", age);
} copy];
[block release];
}
打印结果
2020-06-03 22:13:27.853082+0800 Interview01-Block的本质[56639:9329121] block---------10
小结
ARC环境下的Block
在ARC环境下,编译器会根据自身情况自动将栈上的block复制到堆上,比如以下情况
- 作为函数返回值
- 将block赋值给强指针_strong的时候
- block作为Cocoa API中方法名含有usingBlock的方法参数时候
- block最为GCD API的方法参数
作为函数返回值
typedef void (^RLBlock)(void);
RLBlock myblock()
{
int a = 10;
return ^{
NSLog(@"---------%d", a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%@", [myblock() class]);
}
return 0;
}
2020-06-03 23:26:53.399930+0800 Interview01-block的copy[57002:9364496] __NSMallocBlock__
- 将block赋值给强指针_strong的时候
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
RLBlock block = ^{
NSLog(@"---------%d", age);
};
NSLog(@"有强指针:%@", [block class]);
NSLog(@"无强指针:%@", [^{
NSLog(@"---------%d", age);
} class]);
}
return 0;
}
有强指针:__NSMallocBlock__
2020-06-03 23:29:30.468301+0800 Interview01-block的copy[57030:9366298] 无强指针:__NSStackBlock__
- block作为Cocoa API中方法名含有usingBlock的方法参数时候,修改NSMutableArray也不用__block修饰
NSMutableArray *sorts = [NSMutableArray array];
[sorts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
- block最为GCD API的方法参数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
对象类型的auto变量
创建类
@interface RLPerson : NSObject
@property(nonatomic, assign) NSInteger age;
@end
@implementation RLPerson
- (void)dealloc
{
// [super dealloc];
NSLog(@"RLPerson - dealloc");
}
@end
typedef void (^RLBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
RLBlock block;
{
RLPerson *person = [[RLPerson alloc] init];
person.age = 10;
block = ^{
NSLog(@"---------%ld", (long)person.age);
};
NSLog(@"------%@", [block class]);
}
NSLog(@"-----");
}
return 0;
}
因为有强指针引用,所有block为__NSMallocBlock__ 类型
在ARC环境下打断点至NSLog(@"-----")处打印值为
2020-06-05 08:45:09.755390+0800 Interview01-block的copy[1125:71486] ------__NSMallocBlock__
在MRC环境下打断点至NSLog(@"-----")处打印值为
- (void)dealloc
{
[super dealloc];
NSLog(@"RLPerson - dealloc");
}
typedef void (^RLBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
RLBlock block;
{
RLPerson *person = [[RLPerson alloc] init];
person.age = 10;
block = ^{
NSLog(@"---------%ld", (long)person.age);
};
[person release];
NSLog(@"------%@", [block class]);
}
NSLog(@"-----");
}
return 0;
}
2020-06-07 12:15:50.813657+0800 Interview01-block的copy[1231:28255] RLPerson - dealloc
2020-06-07 12:16:10.955578+0800 Interview01-block的copy[1231:28255] ------__NSStackBlock__
对MRC环境下对block进行copy操作
block = [^{
NSLog(@"---------%d", person.age);
} copy];
打印结果
2020-06-07 12:18:58.412416+0800 Interview01-block的copy[1268:31279] ------__NSMallocBlock__
此时block不会销毁,block类型为__NSMallocBlock__,相当于[person retain],所以堆block可以强引用持有外界的对象,而栈block不能
ARC下继续进行研究 对person对象前加__weak修饰
int main(int argc, const char * argv[]) {
@autoreleasepool {
RLBlock block;
{
RLPerson *person = [[RLPerson alloc] init];
person.age = 10;
__weak RLPerson *weakPerson = person;
block = ^{
NSLog(@"---------%ld", (long)weakPerson.age);
};
NSLog(@"------%@", [block class]);
}
NSLog(@"-----");
}
return 0;
}
打断点至NSLog(@"-----");打印结果,发现person被销毁
2020-06-07 12:38:46.093930+0800 Interview01-block的copy[1540:52404] RLPerson - dealloc
2020-06-07 12:38:46.094325+0800 Interview01-block的copy[1540:52404] ------__NSMallocBlock__
查看此时的c++源码 执行命令
$ xcrun -sdk iphoneos clang -arch -rewrite-objc main.m
会发现报错
/var/folders/zz/v5rdxc250h53xq3b41vnsd3c0000gn/T/main-802857.mi:28844:28: error:
cannot create __weak reference because the current deployment target does
not support weak references
__attribute__((objc_ownership(weak))) RLPerson *weakPerson = person;
^
1 error generated.
这是因为此时要用到运行时编译,而不是静态编译,需要用以下命令编译
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13 main.m
找到源码,发现 RLPerson *__weak weakPerson
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
RLPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, RLPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
如果block改成打印person
block = ^{
NSLog(@"---------%d", person.age);
};
编译获取源码,发现RLPerson *__strong person;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
RLPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, RLPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
MRC下捕获block变量的copy操作
观察以下三个代码的c++源码block的结构变化,主要Desc的结构体__main_block_desc_0内部变化
1.捕获auto对象
int main(int argc, const char * argv[]) {
@autoreleasepool {
RLBlock block;
{
RLPerson *person = [[RLPerson alloc] init];
person.age = 10;
block = [^{
NSLog(@"---------%d", person.age);
} copy];
}
NSLog(@"------");
}
return 0;
}
c++ 源码
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};
2.auto对象的copy操作
int main(int argc, const char * argv[]) {
@autoreleasepool {
RLBlock block;
{
RLPerson *person = [[RLPerson alloc] init];
person.age = 10;
block = [^{
NSLog(@"---------%d", person.age);
} copy];
}
NSLog(@"------");
}
return 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*/);}
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};
3.捕获auto基本数据类型捕获copy操作
int main(int argc, const char * argv[]) {
@autoreleasepool {
RLBlock block;
{
int age = 10;
block = ^{
NSLog(@"---------%d", age);
};
}
NSLog(@"------");
}
return 0;
}
查看源码
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)};
可以看出,当block内部访问auto对象类型,Desc会多俩个方法执行copy和dispose操作,其中copy操作会调用 _Block_object_assign 函数,这里其实会根据变量前面的修饰词进行强引用你还是弱引用。dispose则会调用 _Block_object_dispose 函数进行释放操作
小结
- 当block内部访问的对象类型的auto变量时
- 如果block在栈上,就不会对auto变量产生强引用
- 如果block被拷贝到堆上: 会对用block的内部的copy函数,copy函数内部会调用 _Block_object_assign, _Block_object_assign 会根据auto变量的修饰符(_strong, _weak,_unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
- 如果block从堆上被移除: 会对用block的内部的dispose函数,dispose函数内部会调用 _Block_object_dispose 函数,_Block_object_dispose 函数会自动释放引用的auto变量(release)