如何在 Category 中添加一个 Weak 属性?

1,830 阅读2分钟

 当我们想要在分类中添加属性时,需要手动为属性添加 Setter 和 Getter 函数,否则读取或者写入属性值时会因为没有对应的 Setter 和 Getter 函数而直接触发 unrecognized selector sent to instance 崩溃,那么我们便借助 Associated Object 机制手动为属性添加 Setter 和 Getter 函数,看到这里便引出第一个面试题:分类中添加的属性可以使用 KVO 监听吗?答案很显然是可以的,然后接着往下,既然刚刚我们提到了关联对象,那么我们再延伸一下,我们手动实现 Setter 函数时会使用 objc_setAssociatedObject 这个函数,它的的最后一个参数表示关联策略,但是其中没有对应 weak 属性修饰符的策略,那么我们怎么手动实现一个呢?

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSObject name]: unrecognized selector sent to instance 0x6000000290a0'
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSObject setName:]: unrecognized selector sent to instance 0x600000010330'
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, _cmd);
}

 不由自主打开 Xcode,可以看到关联策略中,唯独缺少 OBJC_ASSOCIATION_WEAK,但是有 assignretain,最先想到的可能就是利用中间层使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC + weak 属性来实现。

/* Associative References */

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies an unsafe unretained reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

 1️⃣:借助中间类,关联值被中间类弱引用:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface WeakWrapper : NSObject

@property(nonatomic, weak) id weakObj;

@end

NS_ASSUME_NONNULL_END

#import "WeakWrapper.h"

@implementation WeakWrapper

- (instancetype)initWithWeakObj:(id)weakObj {
    self = [super init];
    
    if (self) {
        _weakObj = weakObj;
    }
    
    return self;
}

@end
- (void)setObj:(NSObject *)obj {
    WeakWrapper *wrapper = objc_getAssociatedObject(self, @selector(obj));
    if (!wrapper) {
        wrapper = [[WeakWrapper alloc] initWithWeakObj:obj];
    } else {
        wrapper.weakObj = obj;
    }
    
    objc_setAssociatedObject(self, @selector(obj), wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSObject *)obj {
    WeakWrapper *wrapper = objc_getAssociatedObject(self, _cmd);
    return wrapper.weakObj;
}

 2️⃣:借助 block 弱引用关联值:

- (void)setObj:(NSObject *)obj {
    id __weak weakObj = obj;
    id (^block)(void) = ^{ return weakObj; };
    objc_setAssociatedObject(self, @selector(obj), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSObject *)obj {
    id (^block)(void) = objc_getAssociatedObject(self, _cmd);
    return (block ? block() : nil);
}

 3️⃣:关联策略使用 OBJC_ASSOCIATION_ASSIGN,重写关联值的 dealloc 函数,当 dealloc 执行时回调宿主对象把关联值置 nil

参考链接

参考链接:🔗