iOS 使用Weak修饰关联对象

1,549 阅读1分钟

前言

项目中遇到了一个crash,抓取堆栈信息为EXC_BAD_ACCESS KERN_INVALID_ADDRESS。定位原因为weak修饰的关联对象提前释放。以此为契机记录下解决思路

Associated 分类关联对象

为NSObject对象绑定 Associated Object 时可以指定如下依赖关系:

  • OBJC_ASSOCIATION_ASSIGN: 弱引用
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC: 强引用,非原子操作
  • OBJC_ASSOCIATION_COPY_NONATOMIC: 先 copy,然后强引用
  • OBJC_ASSOCIATION_RETAIN: 强引用,原子操作
  • OBJC_ASSOCIATION_COPY: 先 copy,然后强引用,原子操作

最初实现

@interface UIView (XXTrack)
@property (weak, nonatomic) UIViewController *xx_trackVC;
@end
@implementation UIView (XXTrack)
- (void)setxx_trackVC:(UIViewController *)xx_trackVC {
    objc_setAssociatedObject(self, &xx_trackKey, xx_trackVC, OBJC_ASSOCIATION_ASSIGN);
}
- (UIViewController *)xx_trackVC {
    return objc_getAssociatedObject(self, &xx_trackKey);
}

这里看似使用了weak修饰xx_trackVC,但是属性内部实现的时候使用的OBJC_ASSOCIATION_ASSIGN修饰,而且苹果并未提供专门的weak绑定方式。具体原因感兴趣的同学可以看下weak Object与Associated Object的实现原理。既然不是真正的weak那就会存在一个致命的问题。

引发的问题

xx_trackVC对象提前释放所引起的EXC_BAD_ACCESS,也就是我们常说的野指针。

解决方案

既然我们的目的是为了弱引用xx_trackVC对象,并且在对象释放后可以安全的处理相关逻辑。这里我们可以引入包装类的概念,将包装类作为中间件进行strong强引用。

@interface UIView (XXTrack)
@property (nonatomic, strong) UIViewController *xx_trackVC;
@end

@interface PCWeakAssociatedObjectWrapper : NSObject
@property (nonatomic, weak) UIViewController *associatedVC;
@end

GET and SET时对包装类进行解包处理。

@implementation UIView (xxTrack)

- (void)setXx_trackVC:(UIViewController *)xx_trackVC {
    //添加中间件 解决weak提前释放问题
    PCWeakAssociatedObjectWrapper *wrapper = [[PCWeakAssociatedObjectWrapper alloc] init];
    wrapper.associatedVC = xx_trackVC;
    objc_setAssociatedObject(self, &Keys, wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIViewController *)xx_trackVC {
    PCWeakAssociatedObjectWrapper *wrapper = objc_getAssociatedObject(self, &Keys);
    return wrapper.associatedVC;
}