也谈block 编程那些事

转:http://sealedace.com/blog/2014/01/23/block-retain-cycle/

我们知道在使用block时,必须避免出现retain cycle。如果写代码不仔细造成了retain cycle,就会出现内存泄露。即使Xcode有静态代码分析工具,但很多时候Xcode也不太靠谱,根本什么提示都没有,所以还是自己写代码多注意比较好。

(下文译自The Correct Way to Avoid Capturing Self in Blocks With ARC,有小改动)

以前避免retain cycle的方法大概像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation Foo
@property (strong) Bar *bar;
@property (strong) Baz *baz;

-(void)aMethod
{
    __block Foo *blockSelf = self;
    bar.block = ^
    {
        [blockSelf.baz doSomething];
    };
}
@end

所有在block中被访问的变量都会被block保留,而上面这段代码的避免了对self的直接引用。如果self持有这个block——直接或者间接,都会导致retain cycle。因此,我们使用了__block关键字来创建一个对self的临时引用(非保留),我们在block会使用这个引用代替self来操纵对象。但是很不幸,ARC出现后废弃了这种方法并且将__block作用改成了跟strong关键字一样。所以,我们需要一个新的方法来解决这个问题。

一个常见的(naive)方法就是像下面的代码一样去创建一个weak引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation Foo
@property (strong) Bar *bar;
@property (strong) Baz *baz;

-(void)aMethod
{
    __weak Foo *blockSelf = self;
    bar.block = ^
    {
        [blockSelf.baz doSomething];
    };
}

@end

其实,这个跟之前的那个方法差不多是一样的,只是关键字变成了__weak。用这个引用放在block里面运行,没有retain cycle。但是不管怎样,这样做仍然有一些问题。

问题是,当self在block执行期间或block执行之前被销毁了,会怎样呢?我们乍一看应该是没什么问题的:因为blockSelf是一个weak引用,它在销毁后会自动置空,而且对nil发送消息是不会发生任何情况的。对吧?

嗯,如果block中的代码就这样简单,没有复杂的消息传递就结束了,也许倒也没什么问题。但是,假如self在这个block执行的期间,在其他线程里面被销毁了呢?假如在你block里面工作完成到一半的时候self就释放置nil了呢?难道你就这样放弃block中要做的事情,就这样丢着不管了吗?是啊!你这样做也许没有什么损失。呵呵,但是这里还真有一种更加糟糕情况潜伏着…

请看下面这一段看起来感觉跟之前差不多的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation Foo
@property (strong) Bar *bar;
@property (strong) Baz *baz;

-(void)aMethod
{
    __weak Foo *blockSelf = self;
    bar.block = ^
    {
        [baz doSomething];
    };
}

@end

可以看到,这里我们是直接访问成员变量(很糟糕的方式),而没有通过Objective-C里面的推荐的成员访问器。这样写仍然会先访问到self,然后才能访问它的成员变量。如果你这样做的话,runtime没有使用成员访问器,而是使用了C级别的空指针引用。换种方式来看,上面的代码会变成大概下面这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation Foo
@property (strong) Bar *bar;
@property (strong) Baz *baz;

-(void)aMethod
{
    __weak Foo *blockSelf = self;
    bar.block = ^
    {
        [self->baz doSomething];
    };
}

@end

所以,如果self在block执行的途中被销毁了,呵呵,你就会遇到一个因为使用了空指针来访问成员变量而导致的crash。这种情况,跟对nil发送消息可不一样,因为你根本就是在对一个不存在的东西发送消息。

鉴于这些问题,最安全的做法是在block外部对self先获取一个weak引用,然在block内部执行代码的开始,创建一个对selfstrong引用——这样在block执行期间,你不用担心self被销毁。然后等你不需要用到selfstrong引用时候,可以将strongSelf置nil,或者干脆不管,等待代码执行结束退出(ARC,你懂的)。如果你按照这个方法做了,应该不会有什么问题了。所以,代码结构大致应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation Foo
@property (strong) Bar *bar;
@property (strong) Baz *baz;

-(void)aMethod
{
    __weak Foo *blockSelf = self;
    bar.block = ^
    {
        Foo *strongSelf = blockSelf
        [strongSelf.baz doSomething];
    };
}

@end

另外,澄清一点,这段代码并不能保证线程安全,你仍然需要添加@synchronize域或者线程锁来保证线程安全。不过可以肯定的是,这样写一定比文章中提到的存在问题的代码要安全的多!

参考:http://jianshu.io/p/c45da9ef84eb


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值