如何更安全的使用Block

如何更安全的使用Block

OCT 19TH, 2014

前段时间,在公司的项目中发现了内存泄露。查到最后发现是由于没有正确使用Block导致的内存引用循环。简单的解决方案就是补上关键字__weak。那么问题来了,为何[UIView animateWithDuration:animations:completion:]或者GCD中可以不需要声明__weak呢? 
导语

如何选择回调方式

在讲这个问题之前,我们先看看Objcio上是怎么判断使用何种回调方式: 选择
从这张图可以看到选择block的回掉方式的前提条件是sender能保证将callback被设为nil。定这个条件的目的是为了避免内存泄露。为何这样可以避免内存泄露呢?我们先看看通常我们是如何解决由使用block引入的内存引用循环问题。

引用循环

下面是一个内存引用循环的例子

1
2
3
4
5
6
typedef void(^my_completion_block_t)(NSArray* result);

@interface UsersViewController : UIViewController
@property (nonatomic, copy) my_completion_block_t completion;
@property (nonatomic) NSArray* users;
@end

这里可以看到,UsersViewController持有一个block的强引用。如果一个不小心,我们会写出下面这样的代码:

1
2
3
4
self.completion = ^(NSArray* users){
    self.users = users;
}
[self fetchUsers];

self持有了对completion的强引用,而completion的代码块内引用了self会引起对self的强引用,这便是一个经典的引用循环的案例。 
在MRC下,__block id x;可以让block不强引用x。在ARC下,__block id x;默认的行为会强引用x。为了在ARC下解决这个问题,你可以使用__unsafe_unretained __block id x;。但是,这样做非常危险,因为当你的block里访问x时,x可能已经被释放了,变成野指针。你有两个更好的解决办法,一是换成使用__weak,二是在block内设置__block值为nil来打破引用循环。
如下:
1.使用__weak来表示对self的弱引用(代码里面不直接使用weakSelf的原因是为了在block里面保持对self的强引用,避免代码运行到一半self被释放)

1
2
3
4
5
6
7
8
9
10
11
UsersViewController* __weak weakSelf = self;
self.completion = ^(NSArray* users) {
    UsersViewController* strongSelf = weakSelf;
    if (strongSelf) {
        strongSelf.users = users;
    }
    else {
        // the view controller does not exist anymore
    }
}
[usersViewController fetchUsers];

2.使用__block关键字修饰self,然后在block里设置为nil

1
2
3
4
5
6
UsersViewController* __block blockSelf = self;
self.completion = ^(NSArray* users) {
    blockSelf.users = users;
    blockSelf = nil;
}
[usersViewController fetchUsers];

更友好的方式

这个时候我们的疑问来了,既然都可以解决内存循环引用的问题,为何还会有前面这个条件呢? 原因就是这种方式严重依赖调用者的使用方式,在日常使用中带来严重的代码质量隐患。而在苹果的sdk中几个使用block的方法中调用者都不需要特别的处理。 下面是一个测试代码:

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad
{
    [super viewDidLoad];
    NSString *temp=[[NSString alloc] initWithFormat:@"%@",@"我是temp"];
    NSLog(@"%p",temp);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        NSLog(@"%@",temp);
    });
    [temp release];
}

上面代码的作用是进入一个controller后然后延时30s打印一个log,为了方便分析内存,我们使用的是mrc。使用Instructment分析内存如下: 
内存1从图片我们可以看到,temp是在30s后被释放。从Responsible Caller我们可以得出结论,block被持有,然后在30s触发执行后被释放。block中被capture的变量也被释放。如此也避免了内存问题。 把代码修改一下:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad
{
    [super viewDidLoad];
    NSString *temp=[[NSString alloc] initWithFormat:@"%@",@"我是temp"];
    NSLog(@"%p",temp);
    __block NSString *temp2=temp;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        NSLog(@"%@",temp2);
    });
    [temp release];
}

同样使用Instructment分析,结果等到NSLog代码执行的时候代码crash了,内存记录如下:
内存2
这说明,如果我们滥用__block,可能会出现在block运行的时候,访问对象已经被释放,造成访问野指针错误。 系统SDK向我们展示的是一种更友好使用block的方式,让调用者不需要担心内存引用循环也不会有访问野指针的风险。

结论

1.block的持有方式必须保证是copy,这样block中capture的对象才会被retain,保证block在执行的时候capture对象不会被提前释放。
2.为了避免引用循环,被copy的block必须保证在某个时刻被释放,以破坏引用循环。使得所有对象能被正常的释放。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值