block
一、block的本质
- block本质上也是一个OC对象, 它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block的底层结构如下图所示
二、block的变量捕获(capture)
- 为了保证block内部能够正常访问外部的变量, block有个变量捕获机制
- 总结
- auto局部变量: 值捕获, 自动变量, 离开作用域就销毁
int age = 10; // == auto int age = 10;
- static静态变量: 地址捕获
- 全局变量: 不捕获, 直接访问
- 参数: 属于auto局部变量, 值捕获
2.1 auto变量的捕获
三、block的类型
-
block有3种类型, 可以通过调用class方法或者isa指针查看具体类型, 最终都是继承自NSBlock类型
- 全局block:
__NSGlobalBlock__
( _NSConcreteGlobalBlock ) - 栈block:
__NSStackBlock__
( _NSConcreteStackBlock ) - 堆block:
__NSMallocBlock__
( _NSConcreteMallocBlock )
- 全局block:
-
堆内存的特点
- 动态分配内存, 需要程序员自己申请, 也需要程序员自己管理内存
-
block在内存中的分布
-
具体代码展示
// 1. 全局block: 没有访问auto变量
^{
NSLog(@"Hello");
};
// 2. 栈block: 访问了auto变量
int age = 10;
^{
NSLog(@"Hello - %d", age);
}
// 3. 堆block: 栈block调用一次copy操作
[^{
NSLog(@"%d", age);
} copy];
- 每一种类型的block调用copy后的结果如下所示
四、block的copy
- 在
ARC环境
下, 编译器会根据情况自动将栈上的block复制到堆上
, 比如以下情况- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
typedef void (^MJBlock)(void);
MJBlock myblock() {
int age1 = 10;
return ^{
NSLog(@"age1 = %d", age1);
};
}
// 1. block作为函数返回值时
MJBlock block1 = myblock();
myblock();
// 2. 将block赋值给__strong指针时
int age2 = 10;
MJBlock block2 = ^{
NSLog(@"age2 = %d", age2);
};
block2();
// 3. block作为Cocoa API中方法名含有usingBlock的方法参数时
NSArray *arr;
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
// 4. block作为GCD API的方法参数时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
- MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
- ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
五、对象类型的auto变量
当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)
六、__weak问题解决
- 在使用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.0 main.m
七、__block修饰符
7.1 __block修饰符
__block
可以用于解决block内部无法修改auto
变量值的问题__block
不能修饰全局变量、静态变量(static
)- 编译器会将
__block
变量包装成一个对象
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
};
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*);
};
7.2 __block的内存管理
当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)
7.3 __block的__forwarding指针
7.4 总结: 对象类型的auto变量、__block变量
当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_object_dispose((void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
7.5 被__block修饰的对象类型
当__block变量在栈上时, 不会对指向的对象产生强引用
当__block变量被copy到堆时
- 会调用__block变量内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作, 形成强引用(retain)或者弱引用(
注意:这里仅限于ARC时会retain, MRC时不会retain
)
如果__block变量从堆上移除
- 会调用__block变量内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放指向的对象(release)
八、循环引用问题
// 循环引用1
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"age is %d", person.age);
};
// 循环引用2
MJPerson *person = [[MJPerson alloc] init];
[person test1];
// MJPerson.m
- (void)test1 {
// 循环引用2
self.block = ^{
// NSLog(@"age is %d", _age);
// NSLog(@"age is %d", self.age);
NSLog(@"age is %d", self->_age);
};
}
8.1 解决循环引用问题 - ARC
用__weak、__unsafe_unretained
解决
- __weak和__unsafe_unretained的区别:
__weak
:不会产生强引用, 指向的对象销毁时, 会自动让指针置为nil__unsafe_unretained
:不会产生强引用, 不安全, 指向的对象销毁时, 指针存储的地址值不变, 再次访问该变量就会造成野指针错误
用__block解决(必须要调用block)
8.2 解决循环引用问题 - MRC
用__unsafe_unretained解决
用__block解决