- block本质上也是一个OC对象,它内部也有一个isa指针。
- block是封装了函数调用以及函数调用环境的OC对象。
Block底层结构.png - block底层结构就是
__main_block_impl_0
结构体,内部包含了impl
结构体和Desc
结构体以及外部需要访问的变量,block将需要执行的代码放到一个函数
里,impl内部的FuncPtr
指向这个函数的地址,通过地址调用这个函数,就可以执行block里面的代码了。Desc用来描述block,内部的reserved作保留,Block_size描述block占用内存。
block的变量捕获(capture)
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
- 由于
作用域
的问题,全局变量不用捕获到block内部,block执行代码的函数
可直接访问 -
局部变量的定义和需要执行的代码在不同的函数里,如果想跨函数访问局部变量,需要把局部变量捕获到block中存起来,再在执行代码的函数从block中取出捕获的局部变量进行访问。
block的变量捕获.png
auto int age = 10;
static int height = 10;
void (^block)(void) = ^{
NSLog(@"age is %d,height is %d",age,height);
};
age = 20;
height = 20;
block();
-------------------------------------------------
output: age is 10,height is 20
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; // 值传递
int *height; // 指针传递
}
auto变量和static变量访问方式的不同,是由于auto变量随时可能自动销毁,通过值传递访问;而static变量会一直在内存中,可通过指针(地址)传递访问。
- 同样的,self也会被block捕获,是因为所有的OC方法转化成C语言函数,底层会默认传递两个参数,
self
和_cmd
,也是局部变量。
- (void)test
{
void(^block)(void) = ^{
NSLog(@"----%p",self);
};
block();
}
-------------------------------------------------
static void _I_YCPerson_test(YCPerson * self, SEL _cmd) {
void(*block)(void) = ((void (*)())&__YCPerson__test_block_impl_0((void *)__YCPerson__test_block_func_0, &__YCPerson__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
struct __YCPerson__test_block_impl_0 {
struct __block_impl impl;
struct __YCPerson__test_block_desc_0* Desc;
YCPerson *self;
};
block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都继承自NSBlock类型。
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
block类型 | 环境 |
---|---|
NSGlobalBlock | 没有访问auto变量 |
NSStackBlock | 访问了auto变量 |
NSMallocBlock | NSStackBlock调用了copy |
每一种类型的block调用copy后的结果如下所示
Block的类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteGlogalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteMallocBlock | 堆 | 引用计数器增加 |
- 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
对象类型的auto变量
- 在使用clang转换OC代码为C++代码时,可能会遇到以下问题:
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc
-fobjc-arc -fobjc-runtime=ios-8.0.0main.m
YCPerson *person = [[YCPerson alloc] init];
person.age = 10;
__weak YCPerson *weakPerson = person;
MyBlock block = ^{
NSLog(@"-----%d",weakPerson.age);
};
-------------------------------------------------
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
YCPerson *__weak weakPerson;
}
YCPerson *person = [[YCPerson alloc] init];
person.age = 10;
// __weak YCPerson *weakPerson = person;
MyBlock block = ^{
NSLog(@"-----%d",person.age);
};
-------------------------------------------------
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
YCPerson *__strong person;
}
当block内部访问了对象类型的auto
变量时
- 如果block是在栈上,不论是ARC还是MRC环境,或者对外部的auto变量是强引用还是弱引用,都不会对
auto
变量产生强引用。 - 如果block被拷贝到堆上
1、会调用block内部的copy函数
2、copy函数内部会调用_Block_object_assign函数
3、_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak)产生强引用还是弱引用。 - 如果block从堆上移除
1、会调用block内部的dispose函数
2、dispose函数内部会调用_Block_object_dispose函数
3、_Block_object_dispose函数会自动释放引用的auto变量,类似于release
函数 | 调用时机 |
---|---|
copy函数 | 栈上的Block复制到堆上 |
dispose函数 | 堆上的Block被废弃时 |
__block修饰符
- 一般情况下,对被截获对象进行
赋值
操作需要添加__block修饰符(赋值≠使用) - 对变量进行赋值时
- 对局部变量(
基本数据类型
、对象类型
),需要__block修饰符 - 对
静态局部变量
、全局变量
、静态全局变量
,不需要__block修饰符
- 对局部变量(
-
__block
可以用来解决block内部无法修改auto变量的问题 - 编译器会将
__block
变量包装成一个对象
(__Block_byref_age_0结构体),结构体内部__forwarding是指向自身的指针,内部还存储着外部auto变量的值
__block修饰基本数据类型
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 三种auto变量
int no = 20; // 基本数据类型的auto变量,不需要内存管理,不会生成copy函数和dispose函数
__block int age = 10; // __block修饰基本数据类型变量,需要内存管理,会生成copy函数和dispose函数
NSObject *object = [[NSObject alloc] init]; // 对象类型的auto变量,需要内存管理,不会生成copy函数和dispose函数
__weak NSObject *weakObject = object;
// 刚开始block内存在栈上,在ARC环境下,一旦block被强引用着,会对栈上的block进行copy操作,会拷贝到堆上
// block如果是在栈上,对象类型的auto变量object和__block变量age产生的都是弱引用,不是强引用;如果block被copy到堆时,都会通过copy函数来处理它们
MyBlock block = ^{
age = 20; // 修改age变量
NSLog(@"%d",no);
NSLog(@"%d",age);
NSLog(@"%p", weakObject);
};
block();
}
return 0;
}
-------------------------------------------------
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age;
};
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age; // 结构体内部会存储着age值
};
// 此处block会捕获三个auto变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int no; // 将no值直接存储
NSObject *__weak weakObjc; // 访问对象类型的auto变量,会在内部存储该类型的指针变量,__weak or __strong取决于外部如何访问
__Block_byref_age_0 *age; // 访问__Block变量,会将age包装到__Block_byref_age_0结构体中,block内部保留一个引用这个结构体的指针,指针会指向__Block_byref_age_0结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObjc, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObjc(_weakObjc), age(_age->__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_age_0 *age = __cself->age; // 通过block中age指针拿到指向结构体的指针
int no = __cself->no; // bound by copy
NSObject *__weak weakObjc = __cself->weakObjc; // bound by copy
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_0,no);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_1,(age->__forwarding->age));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_2,weakObjc);
}
// block从栈拷贝到堆时调用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
// __block,第三个参数传递8(BLOCK_FIELD_IS_BYREF),__block变量不存在强弱引用之分,就是强引用
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
// 对象类型的auto变量object,第三个参数传递3(BLOCK_FIELD_IS_OBJECT),如果外部通过弱(强)引用访问OC对象,那_Block_object_assign对OC对象产生的就是弱(强)引用
_Block_object_assign((void*)&dst->weakObjc, (void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
// block从堆中移除调用
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->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
// 访问对象类型的auto变量,会生成以下两个函数,对内部访问的对象进行内存管理操作,访问基本数据类型不会生成这两个函数
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的forwarding指针
__block的forwarding指针(age->__forwarding->age) = 20;
-
栈
上,__block结构体中的__forwarding指针指向自己,一旦复制到堆
上,栈上的__block结构体中的__forwarding指针会指向堆上的__block结构体,堆上__block结构体中的__forwarding还是指向自己。假设age是栈上的变量,age->__forwarding会拿到堆上的__block结构体,age->__forwarding->age会把20赋值到堆上,不论是栈上还是堆上的__block结构体,都能保证20赋值到堆的结构体里。
block循环引用问题
- 用__weak、__unsafe_unretained解决
// __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil,若再次访问此变量,不会产生错误。
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%d",weakSelf.age);
};
// __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,若再次访问此变量,容易产生野指针错误。
__unsafe_unretained typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%d",weakSelf.age);
};
__weak、__unsafe_unretained解决循环引用.png
- 用__block解决
__block YCPerson *person = [[YCPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"%d",person.age);
person = nil;
};
person.block(); // 必须要调用block,执行block内代码,将对象置为nil
用__block解决循环引用.png
网友评论