天天看点

iOS 开发中使用block的注意点

iOS 开发中使用block的注意点

最近工作较忙,比较少写博客了,最近研究block的时候遇到一些小坑,就写出来避免大家走我的旧路,做一个帮大家填坑的填坑人,进入主题。

相信大家对使用block是又爱又恨,爱它给开发带来了多大的便利,但是又恨它是否循环引用掌握不够准确,导致经常出现循环引用的问题。对于新手来说,出现循环引用时,有时候通过Leaks不一定能检测出来,更重要的还是得靠自己有一双能看出哪里循坏引用的一双慧眼。

代码与分析:

我们先定义一个view,用于与Controller相互传值。当点击view的按钮时,就会通过block回调给controller,也就反馈到控制器了,并将对应的数据传给控制器以记录:

.h头文件

typedef void(^KTTestBlock)(id model);

@interface KTTestView : UIView

- (instancetype)initWithBlock:(KTTestBlock)block;

@end
           

.m文件

@interface KTTestView ()

@property (nonatomic, copy) KTTestBlock block;  // 私有属性

@end

@implementation KTTestView

- (void)dealloc {
  NSLog(@"dealloc: %@", [[self class] description]);
}

- (instancetype)initWithBlock:(KTTestBlock)block {
  if (self = [super init]) {
    self.block = block;

    UIButton *button = {(
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setTitle:@"进行与controller的传值" forState:UIControlStateNormal];
        btn = CGRectMake(, , , );
        btn.backgroundColor = [UIColor redColor];
        [btn setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
        [btn addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside];
        return ban;
     )}

  }
  return self;
}

- (void)feedback {
  if (self.block) { // 需要判断block是否有值
    // 传模型回去,这里没有数据,假设传nil
    self.block(nil);
  }
}

@end
           

接下来看KTTestViewController,有两个属性,在viewDidLoad时,创建了aView属性:

@interface KTTestViewController()

@property (nonatomic, strong) KTTestView *aView;

@property (nonatomic, strong) id currentModel;

@end

@implementation KTTestViewController

- (instancetype)initWithCallback:(KTCallbackBlock)callback {
  if (self = [super init]) {

  }

  return self;
}

- (void)viewDidLoad {
  [super viewDidLoad];

  self.title = @"KTTestViewController";
  self.view.backgroundColor = [UIColor whiteColor];

  self.aView = [[KTTestView alloc] initWithBlock:^(id model) {
    // 假设要更新model
    self.currentModel = model;
  }];

  // 假设占满全屏
  self.aView.frame = self.view.bounds;
  [self.view addSubview:self.aView];
  self.aView.backgroundColor = [UIColor whiteColor];
}

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];

  NSLog(@"进入控制器:%@", [[self class] description]);
}

- (void)dealloc {
  NSLog(@"控制器被dealloc: %@", [[self class] description]);
}

@end
           

很明显,现在已经循环引用了

所形成的环有2个:

vc->aView->block->vc(self)

vc->aView->block->vc.currentModel

—-解决的办法可以是:在创建aView时,block内对currentModel的引用改成弱引用:

__weak __typeof(self) weakSelf = self;
self.aView = [[KTTestView alloc] initWithBlock:^(id model) {
    // 假设要更新model
    weakSelf.currentModel = model;
}];
           

我见过很多类似这样的代码,直接使用成员变量,而不是属性,然后他们以为这样就不会引用self,也就是控制器,从而不形成环:

self.aView = [[KTTestView alloc] initWithBlock:^(id model) {
    // 假设要更新model
    _currentModel = model;
}];
           

这是错误的理解,当我们引用了_currentModel时,它是控制器的成员变量,因此也就引用了控制器。要解决此问题,也是要改成弱引用:

__block __weak __typeof(_currentModel) weakModel = _currentModel;
self.aView = [[KTTestView alloc] initWithBlock:^(id model) {
  // 假设要更新model
  weakModel = model;
}];
           

千万要记得这里还要加上__block哦!

暂时就讲这么多吧,目的是教大家如何分析内存是否形成环,只要懂得了如何去分析内存是否循环引用了,那么在开发时一定会特别注意内存管理问题,而且查找内存相关的问题的bug时,也就少走弯路,填好坑。