继续上面的基础篇内容。
1 - block的强引用与弱引用
我们先看一下这样的代码
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
@property (nonatomic , assign) int age;
@end
@implementation TestObject
- (void)dealloc {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
void (^gloadBlockC)(void);
{
TestObject *testObj = [[TestObject alloc] init];
testObj.age = 3;
TestObject *weakTest = testObj;
gloadBlockC = ^void(){
NSLog(@"%d",weakTest.age);
};
testObj.age = 5;
}
gloadBlockC();
}
NSLog(@"执行了");
}
return 0;
}
在ARC环境下,输出结果是:
5
-[TestObject dealloc]
执行了
在MRC环境下,gloadBlockC();
还未执行时
-[TestObject dealloc]
执行gloadBlockC()
的时候,发生崩溃Thread 1: EXC_BAD_ACCESS (code=1, address=0x18)
。
结合上一篇的记录,在MRC环境下,捕获auto变量的block是stackBlock,存储在栈中。而在ARC环境下,当stackBlock赋值给__strong指针的时候,会自动copy,变成了MallocBlock
。所以,栈上的block,不会对捕获的auto变量强引用(计数+1)。
我们再来看一下,在ARC下的几种情况:
首先是捕获的变量是强引用。
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
void (^gloadBlockC)(void);
{
TestObject *testObj = [[TestObject alloc] init];
testObj.age = 3;
__strong TestObject *weakTest = testObj;
gloadBlockC = ^void(){
NSLog(@"%d",weakTest.age);
} ;
testObj.age = 5;
}
gloadBlockC();
}
NSLog(@"执行了");
}
return 0;
}
__strong是默认引用类型,不添加__strong也可以。 转换出来的c++代码如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
TestObject *__strong weakTest;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, TestObject *__strong _weakTest, int flags=0) : weakTest(_weakTest) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们可以看到,捕获到的auto变量类型也是__strong,经测试验证得出结论:在ARC环境下,block捕获的对象修饰符(__weak,__strong,__unsafe_unretained),与该对象本身的修饰符一致!
而
在block进行copy到堆上的时候,会调用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)
2 - __block修饰符
我们先看一下下面的代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
void (^gloadBlockC)(void);
{
TestObject *testObj = [[TestObject alloc] init];
testObj.age = 3;
TestObject *weakTest = testObj;
static int a = 1;
auto int b = 1;
gloadBlockC = ^void(){
a = 2;
b = 2;
weakTest = [TestObject new];
NSLog(@"%d",weakTest.age);
};
testObj.age = 5;
}
gloadBlockC();
}
NSLog(@"执行了");
}
return 0;
}
编译出现2处错误:Variable is not assignable (missing __block type specifier)
。这里有个知识点,auto修饰的变量,在block中不能修改。那么,要在block中修改局部变量怎么办呢,除了static,我们还可以用__block来修饰。
修改之后如下:
__block TestObject *weakTest = testObj;
static int a = 1;
auto __block int b = 1;
gloadBlockC = ^void(){
a = 2;
b = 2;
weakTest = [TestObject new];
NSLog(@"%d",weakTest.age);
};
那么,__block到底是什么呢?我们来看一下编译成c++的源码吧。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
{
void (*gloadBlockC)(void);
{
TestObject *testObj = ((TestObject *(*)(id, SEL))(void *)objc_msgSend)((id)((TestObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestObject"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)testObj, sel_registerName("setAge:"), 3);
__attribute__((__blocks__(byref))) __Block_byref_weakTest_0 weakTest = {(void*)0,(__Block_byref_weakTest_0 *)&weakTest, 33554432, sizeof(__Block_byref_weakTest_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, testObj};
static int a = 1;
auto __attribute__((__blocks__(byref))) __Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 1};
gloadBlockC = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a, (__Block_byref_b_1 *)&b, (__Block_byref_weakTest_0 *)&weakTest, 570425344));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)testObj, sel_registerName("setAge:"), 5);
}
((void (*)(__block_impl *))((__block_impl *)gloadBlockC)->FuncPtr)((__block_impl *)gloadBlockC);
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_swf2m0ds52l_g_ly5nz8vxmw0000gn_T_main_44cc4d_mi_2);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *a;
__Block_byref_b_1 *b; // by ref
__Block_byref_weakTest_0 *weakTest; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, __Block_byref_b_1 *_b, __Block_byref_weakTest_0 *_weakTest, int flags=0) : a(_a), b(_b->__forwarding), weakTest(_weakTest->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_weakTest_0 {
void *__isa;
__Block_byref_weakTest_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
TestObject *__strong weakTest;
};
struct __Block_byref_b_1 {
void *__isa;
__Block_byref_b_1 *__forwarding;
int __flags;
int __size;
int b;
};
可以看到,捕获到的变量b
和weakTest
是一个结构体。关于__block
有一下几个结论:
-
__block
可以用于解决block内部无法修改auto变量值的问题 -
__block
不能修饰全局变量、静态变量(static) - 当block在栈上时,并不会对
__block
变量产生强引用(同上文中描述的,栈上的block,不会对捕获的auto变量产生强引用
) ,同上述,block从栈上拷贝到堆上时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)。 -
__block
结构体中包含的变量,__weak,__strong,__unsafe_unretained
修饰符与捕获变量本身一致。
3 - block的循环引用
在ARC环境下:先看下下面的代码
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
@property (nonatomic , assign) int age;
@property (strong , nonatomic) void(^testBlock)(void);
@end
@implementation TestObject
- (void)test{
self.age = 5;
TestObject *weakSelf = self;
self.testBlock = ^{
NSLog(@"%d",weakSelf.age);
};
self.testBlock();
}
- (void)dealloc {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
[[[TestObject alloc] init] test];
}
NSLog(@"执行了");
}
return 0;
}
输出结果为:
5
执行了
TestObject对象没有被释放。
造成这个问题的原因是TestObject持有了一个testBlock属性,testBlock中又捕获了TestObject对象本身,都是强引用,导致循环引用,无法释放。
所以,解决循环引用的原理就是,打破互相强引用。简单的做法,我们将block引用的weakself改成弱引用。
__weak TestObject *weakSelf = self;
这样,块代码捕获到的对象就成了弱引用。如下:
struct __TestObject__test_block_impl_0 {
struct __block_impl impl;
struct __TestObject__test_block_desc_0* Desc;
TestObject *__weak weakSelf;
__TestObject__test_block_impl_0(void *fp, struct __TestObject__test_block_desc_0 *desc, TestObject *__weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
所以,当weakSelf被释放的时候,weakSelf中的testBlock就被释放了。
如图:
![](https://img.haomeiwen.com/i4322913/f0ee2e5c41ca6c3e.png)
还有一种是使用__block
来修饰weakSelf。将weakSelf包一层,要释放的时候,将block中捕获的强引用赋值为nil,或者将对象的block赋值为nil,打破循环的强引用就可以。
如图:
![](https://img.haomeiwen.com/i4322913/5ee05d288feafded.png)
还可以用 __unsafe_unretained
来修饰weakSelf。用__unsafe_unretained
来修饰的话,block内部copy的时候,不会对捕获的变量进行retain
操作(也就是计数器+1,也就是强引用)。
__unsafe_unretained TestObject *weakSelf = self;
在MRC环境下:
同理,我们可以用__unsafe_unretained
来修饰。
我们可以用
__unsafe_unretained TestObject *weakSelf = self;
另外,我们可以用__block
来修饰weakSelf。只不过这里跟ARC环境不一样的是,在MRC下,block的copy到堆的时候,_Block_object_assign 不会对捕获到的变量根据修饰类型形成强引用!!!
__block TestObject *weakSelf = self;
总结一下:
在block进行copy到堆上的时候,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
(仅限于ARC环境!MRC不会retain操作! 仅限于ARC环境!MRC不会retain操作! 仅限于ARC环境!MRC不会retain操作! 仅限于ARC环境!MRC不会retain操作! 仅限于ARC环境!MRC不会retain操作!
)
网友评论