iOS开发精进block

iOS开发笔记(二):block循环引用

2017-12-28  本文已影响118人  天空当被子

写这篇文章的缘由是第一次面试时被问到了block循环引用的问题,当时回答的不是很好,首先要明确的是,block是否用copy修饰决定不了循环引用的产生,在此再一次进行补强,有不对的地方还请多多指教。

1.block为什么要用copy修饰

1.1 内存堆栈理解

由编译器自动分配释放,存放函数的参数值,局部变量的值等,不需要程序员来操心。其操作方式类似于数据结构中的栈。

一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。尽管后边苹果引入了ARC机制,但是ARC的机制其实仅仅是系统帮助程序员添加了retain,release,autorelease代码,并不是说系统就可以自动管理了。他的系统管理的原理还是MRC,并没有本质区别。注意内存堆区与数据结构中的堆是两回事,分配方式倒是类似于链表。

1.2 block作用域

首先,block是一个对象,所以block理论上是可以retain/release的。但是block在创建的时候它的内存是默认是分配在栈(stack)上,而不是堆(heap)上的。所以它的作用域仅限创建时候的当前上下文(函数, 方法...),当你在该作用域外调用该block时,block占用的内存已经释放,无法进行访问,程序就会崩溃,出现野指针错误。

1.3 三种block

1.4 另一种理解方式:函数返回

关于函数返回,在一个函数的内部,return的时候返回的都是一个拷贝,不管是变量、对象还是指针都是返回拷贝,但是这个拷贝是浅拷贝。在这里我需要理解以下两点:

明确上边两点之后,我们再来说,在MRC下,如果一个block作为参数,没有经过copy就返回。后果是什么呢?由于return的时候返回的是浅拷贝,也就是说返回的是对象的地址,因为在返回后这个block对应的栈内存就销毁了。如果你多次调用这个block就会发现,程序会崩溃。崩溃原因就是上边所说,block占用的空间已经释放了,你不可以进行访问了。

解决方案:就是在返回的时候,把block进行拷贝作为参数进行返回。这样做的好处是返回的那个block存储空间是在堆内,堆内的空间需要程序员自己去释放,系统不会自动回收,也就不会出现访问已释放内存导致的崩溃了。也就是我们在MRC下需要使用copy修饰符的原因。(此处是否是通过深复制在堆中申请内存不求甚解,在此标记,继续深究)

1.5 ARC下block用什么修饰

首先前面讲的内容都是在MRC下,MRC下block需要用copy修饰,但是在ARC下使用copy或strong修饰其实都一样,因为block的retain就是用copy来实现的。

2.block循环引用

在开始之前我们需要明确一点:是不是所有的block,使用self都会出现循环引用?其实不然,系统和第三方框架的block绝大部分不会出现循环引用,只有少数block以及我们自定义的block会出现循环引用。而我们只要抓住本质原因就可以了,如下:

如果block没有直接或者间接被self存储,就不会产生循环引用。就不需要用weak self。(retainCount无法变为0)

2.1 直接强引用:self -> block -> self

由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用。如下

typedef void(^block)();

@property (copy, nonatomic) block myBlock;
@property (copy, nonatomic) NSString *blockString;

- (void)testBlock {
    self.myBlock = ^() {
        //其实注释中的代码,同样会造成循环引用
        NSString *localString = self.blockString;
        //NSString *localString = _blockString;
        //[self doSomething];
    };
}

注:以下调用注释掉的代码同样会造成循环引用,因为不管是通过self.blockString还是_blockString,或是函数调用[self doSomething],因为只要block中用到了对象的属性或者函数,block就会持有该对象而不是该对象中的某个属性或者函数。

强引用1 强引用2

2.2 间接强引用:self -> 某个类 -> block -> self

间接强引用中,self并没有直接拥有block属性。来看下面一个例子:

这是一个持有block的view: XXSubmitBottomView

typedef void(^BtnPressedBlock)(UIButton *btn);

@interface XXSubmitBottomView : UIView

@property(strong,nonatomic)UILabel *allPriceLab;
@property(strong,nonatomic)UIButton *submittBtn;
@property(nonatomic, weak)XXConfirmOrderController *currentVc;
@property(nonatomic, weak)XXConfimOrderModel *model;

@property(nonatomic, copy)BtnPressedBlock block;
-(void)submittBtnPressed:(BtnPressedBlock)block;

这是一个持有bottomView属性的控制器: XXConfirmOrderController

@interface XXConfirmOrderController ()

@property(nonatomic, strong) XXConfimOrderTableView *tableView;
@property(nonatomic, strong) XXSubmitBottomView *bottomView;
@property(nonatomic, strong) XXConfimOrderModel *confimModel;

@end

@implementation XXConfirmOrderController

-(void)viewDidLoad{
    [super viewDidLoad];
    self.title = @"确认下单";
    self.view.backgroundColor = DDCJ_Gray_Color;

    //UI
    [self.view addSubview:self.tableView];
    [self.view addSubview:self.bottomView];

    //Data
    [self loadData];
}

下面是self.bottomView的懒加载以及block的回调处理

-(XXSubmitBottomView *)bottomView{
    if (!_bottomView) {
        _bottomView = [[XXSubmitBottomView alloc] initWithFrame:CGRectMake(0, self.view.height - 50, Width, 50)];
        _bottomView.currentVc = self;
    
    
#warning self.bottomView.block  self间接持有了BtnPressedBlock 必须使用weak!
    
        WEAKSELF  //ps: weakSelf的宏定义#define WEAKSELF typeof(self) __weak weakSelf = self;
   
    
        [_bottomView submittBtnPressed:^(UIButton *btn) {
        
            NSLog(@"do提交订单");
            
            MBProgressHUD *hud = [MBProgressHUD showMessage:@"加载中..." toView:weakSelf.view];
        
            NSMutableDictionary *dynamic = [NSMutableDictionary dictionary];
            [dynamic setValue:weakSelf.confimModel.orderRemark forKey:@"orderRemark"];
            if (weakSelf.agreementId) {
                [dynamic setValue:weakSelf.agreementId forKey:@"agreementId"];
            }
            if (weakSelf.isShoppingCartEnter) {
                [dynamic setValue:@"0" forKey:@"orderOrigin"];
            }else{
                [dynamic setValue:@"1" forKey:@"orderOrigin"];
            }
                    
            [[APIClientFactory sharedManager] requestConfimOrderWithDynamicParams:dynamic success:^(NSMutableArray *dataArray) {
            
                [hud hideAnimated:YES];                
                [weakSelf handlePushControllerWithModelList:dataArray];
            
            } failure:^(NSError *error) {
                [hud hideAnimated:YES];
                [MBProgressHUD showError:error.userInfo[@"message"]];
            }];
        }];
    }

    return _bottomView;
}

此处的控制器self并没有直接持有block属性,但是却强引用了bottomView,bottomView强引用了block属性,这就造成了间接循环引用。block回调内必须使用[weak self]来打破这个循环,否则就会导致这个控制器self永远都不会被释放掉产生常驻内存。

2.3 实际开发中的循环引用

使用通知(NSNotifation),调用系统自带的Block,在Block中使用self会发生循环引用。

twoVC发送通知 --> 给oneVC oneVC 接收通知 使用通知-发生循环引用

注:自定义的block出现循环引用时都会出现警告,所以出问题时容易解决。但在这里,在block中的确出现了循环引用,也的确没有出现警告,这才是我们真正需要注意的,也是为什么我们需要理解block循环引用的原因。

2.4 解决办法

弱引用1
弱引用2

2.5 weak的缺陷

3.参考

上一篇下一篇

猜你喜欢

热点阅读