Block结构
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block的底层结构如图所示
声明一个block转义之后是下面这种代码
((void (*)())&__test_block_impl_0((void *)__test_block_func_0,
&__test_block_desc_0_DATA,
));
主要的方法,就是__test_block_func_0结构体的的内容 和 __test_block_desc_0_DATA结构体的描述
block的结构
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int a;
int *b;
__test_block_impl_0(void *fp, struct __test_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;
}
};
传入的参数会在自己的构造函数 __test_block_impl_0 中用到,这个构造函数类似于 init 方法,返回的是一个结构体
block的ISA指向的是_NSConcreteStackBlock类型,相当于,对象指向的是NSObject类型一样
block大括号里面的实现代码:
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, a, (*b));
}
结构体的描述
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};
block的调用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
相当于调用了block结构体重的impl对象的FuncPtr方法,但是一般是impl. FuncPtr才可以,这个却能直接调用呢,是因为将__test_block_func_0的类型直接转换成了 void (*)(__block_impl * 类型,impl 是 __test_block_func_0 结构体的第一个成员变量,所以 impl 的地址是和 __test_block_func_0 的地址一样的,所以才能转换类型成功
Block捕获器
局部变量分为auto和static,auto是默认方法,比如 int a = 10,其实全称应该是 auto int a = 10
Block捕获.png当定义一个方法的时候,调用的时候相当于默认传递了两个参数,一个是当前对象,一个是方法名称,所以无论是在Block中使用self还是self的某个属性都会捕获当前对象
auto变量的捕获.png- (void)test
{
void (^block)(void) = ^{
NSLog(@"-------%d", [self name]);
};
block();
}
Block类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
Block的类型所在内存区域如下图:
Block的类型.png
block类型 | 环境 |
---|---|
NSGlobalBlock | 没有访问auto变量 |
NSStackBlock | 访问了auto变量 |
NSMallocBlock | NSStackBlock调用了copy |
block类型 | 副本源的配置存储域 | 复制效果 |
---|---|---|
NSGlobalBlock | 程序的数据区域 | 什么也不做 |
NSStackBlock | 栈 | 从栈复制到堆 |
NSMallocBlock | 堆 | 引用计数加一 |
Block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况,block作为函数返回值时,将block赋值给__strong指针时,block作为Cocoa API中方法名含有usingBlock的方法参数时,block作为GCD API的方法参数时
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
当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)
将Block从栈copy到堆中,desc中会多出两个结构体
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
};
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*/);
}
下面这种情况下,可以看到最里面是有用到强引用的p的,这个时候编译器会全部考虑进去,最里面有p,所以当1秒之后并不会释放p对象
MJPerson *p = [[MJPerson alloc] init];
__weak MJPerson *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", weakP);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-------%@",p);
});
});
在Block中修改变量
方法一
设置为全局变量,或者是使用static声明变量
static age = 10;
MJBlock block2 = ^{
age = 30;
NSLog(@"age is %d", age);
};
方法二
使用__block修饰,__block可以用于解决block内部无法修改auto变量值的问题,__block不能修饰全局变量、静态变量(static),编译器会将__block变量包装成一个对象
__block int age = 10;
MJBlock block1 = ^{
age = 20;
NSLog(@"age is %d", age);
};
通过__block修饰后,会将 int age 修改为__Block_byref_age_0 *age;结构体如下,证明也是一个对象
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;//指向自己,就是 &age
int __flags;
int __size;
int age;
};
__Block_byref_age_0.png
对象使用的时候并不需要加 __block,但是当修改的对象的值的时候是需要加的
NSMutableArray *array = [NSMutableArray array];
MJBlock block1 = ^{
array = nil; //报错
[array addObject:@"123"]; //不报错
};
当我们在外部定义一个变量的时候,在block中修改,这时候这个变量就是 __block生成的结构体里面的变量地址
__block int age = 10;
MJBlock block = ^{
age = 20;
NSLog(@"age is %d", age);
};
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%p", &age); //这个地址就是__Block_byref_age_0 里面的那个变量地址
当__block变量在栈上时,不会对指向的对象产生强引用
当__block变量被copy到堆时,会调用__block变量内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用<font color="#FF0033">(注意:这里仅限于ARC时会retain,MRC时不会retain)</font>
如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)
Block内存管理
Block内存管理.png当block在栈上时,并不会对__block变量产生强引用
当block被copy到堆时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)
当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)
block从堆中移除.png当block在栈上时,对它们都不会产生强引用
当block拷贝到堆上时,都会通过copy函数来处理它们
__block变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
当block从堆上移除时,都会通过dispose函数来释放它们
__block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
__block的__forwarding指针
__forwarding.png在block中修改值
__block int age = 10;
MJBlock block = ^{
age = 20;
NSLog(@"age is %d", age);
};
实现是这样的,先拿到 &age的地址,也就是 __Block_byref_age_0 结构体,然后拿到里面的 __forwarding指针,__forwarding指向自己,然后在拿到 __Block_byref_age_0 结构体里面的 age,为什么通过 __forwarding指针 找age是因为,使用的时候, block被强引用的时候会被copy到堆上,__forwarding的作用就是为了保证指向正确的那个地址,在堆上指向copy到堆上的地址,在栈上指向栈上的地址
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
(age->__forwarding->age) = 20;
Block的循环引用
导致循环引用的原因是相互持有对方,比如下面代码,person使用了强引用,然后又在block中使用了person,就导致了循环引用
Block的循环引用.png
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"age is %d", person.age);
};
解决方式(ARC)
用__weak、__unsafe_unretained、__block解决
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__weak typeof(person) weakPerson = person;
__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
__unsafe_unretained typeof(person) weakPerson = person;
__block的情况比较复杂,相当于是造成了一个三方的相互持有,对象->block->__block->__block内部的对象,但是我们需要手动调用下,block并且将对象置为Nil,这样就可以打破相互引用
__block MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"age is %d", person.age);
person = nil;
};
person.block();
解决方式(ARC).png
解决方式(MRC)
不支持__weak,只能使用__unsafe_unretained、__block解决
__unsafe_unretained typeof(person) weakPerson = person;
MRC的环境下__block 不会retain,所以使用方法和__unsafe_unretained一样
网友评论