如何使用 Runtime 给现有的类添加 weak 属性

724 阅读3分钟

runtime 中association_policy中所提供的只有 assign 却没有 weak。相比于别的修饰符,assign 是最接近 weak 的,所以我们会改造 assign 去完成 weak 的工作。

在持有对象的情况下,weak 和 assign 最大的区别,也就是本文研究方向就在于最后一条,对象销毁后如何将引用设置为nil? 这里扩充一下,对象销毁后,weak 修饰的 property 会自动设置为 nil,这个最大的好处就是之后发送的消息都不会因为对象销毁而出错;assign 修饰的 property 并不会自动变为 nil,形成野指针,所以在此之后如果没有判断对象是否销毁的话,很有可能就会对野指针发送消息导致crash。 官方来说,如果不想增加持有对象的引用计数器的话,推荐使用 weak 而不是 assign,这一点从 Apple 提供的头文件就可以看出——所有 delegate 的修饰符都是 weak。

关联对象是 runtime 中的一个比较重要的技能,在此我假设你已经了解了关联对象的操作,并且你也会使用关联对象为已有的类添加属性。如果你对 runtime 的知识还不够了解的话,可以去网络上搜寻一些文章来看,或者去我的 GitHub 看我写的 runtime 系列文章。关联对象最主要的就是下面这两个c函数:

void objc_setAssociatedObject(id obj, const void *key, id value, objc_AssociationPolicy policy); id objc_getAssociatedObject(id obj, const void *key);

/** 这是 某个已有类 的分类 CategoryProperty 在 .h 文件中的一个新增的属性 */ @property (nonatomic, strong) NSObject *categoryProperty;

/** 这是 .m 文件中的 set 方法的实现 */

  • (void)setCategoryProperty:(id)categoryProperty { objc_setAssociatedObject(self, "categoryProperty", categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } /** 这是 .m 文件中的 get 方法的实现 */
  • (id)categoryProperty { return objc_getAssociatedObject(self, "categoryProperty"); }

这个关联对象可以封装成 NSObject 的一个分类,以便日后操作。 /**

  • 这是 NSObject 基于 Runtime 而增加的分类,之后如果想要给现有的类添加属性的话,可以直接调用这个分类 **/ @implementation NSObject (Association)

/** 所有要增加的属性的 set 方法都可以调用这个方法来实现 */

  • (void)setAssociatedObject:(id)obj forKey:(NSString )key { objc_setAssociatedObject(self, key.UTF8String, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } /* 所有要增加的属性的 get 方法都可以调用这个方法来实现 */
  • (id)associatedObjectForKey:(NSString *)key { return objc_getAssociatedObject(self, key.UTF8String); } @end

/** 这是 某个已有类 的分类,并基于上述的 NSObject 的 Runtime 分类而实现的增加属性 */ @property (nonatomic, strong) NSObject *categoryProperty;

/** 利用上述的分类添加属性的 set 方法的实现 */

  • (void)setCategoryProperty:(id)categoryProperty { [self setAssociatedObject:categoryProperty forKey:@"categoryProperty"]; }

/** 利用上述的分类添加属性的 get 方法的实现 */

  • (id)categoryProperty { return [self associatedObjectForKey:@"categoryProperty"]; }

在 NSDictionary 中,用这两个 name 作为 key 可以取到相同的对象,所以我们可以考虑将 const char * 作为 value ,NSString * 作为 key 保存在一个 NSDictionary 中,然后利用这个字典将内容相同的 NSString 统一转为同一个 const char * 值就好了么~似乎很有道理,但是 const char * 是基本数据类型,不能保存到 OC 容器类中,所以我们需要用 NSValue 来包装一下。 /** 这是一个 NSString => NSValue< const char * >的字典 */ static NSMutableDictionary *keyBuffer;

@implementation NSObject (Association)

  • (void)load { keyBuffer = [NSMutableDictionary dictionary]; // 创建字典 }

/**

  • set 方法,以供以后添加属性时候给这个属性的 set 方法调用
  • @param object 要关联的对象,也就是要设置的新的属性值
  • @param key 属性名称,传入新增属性的名称 **/
  • (void)setAssociatedObject:(id)object forKey:(NSString *)key { const char *cKey = [keyBuffer[key] pointerValue]; // 先获取key if (cKey == NULL) { // 字典中不存在就创建 cKey = key.UTF8String; keyBuffer[key] = [NSValue valueWithPointer:cKey]; } objc_setAssociatedObject(self, cKey, object, OBJC_ASSOCIATION_RETAIN); }

/**

  • get 方法,以供以后添加属性时候给这个属性的 get 方法调用
  • @param key 属性名称 */
  • (id)associatedObjectForKey:(NSString *)key { const char *cKey = [keyBuffer[key] pointerValue]; if (cKey == NULL) { return nil; } else { return objc_getAssociatedObject(self, cKey); } }

@end

**

  • Policies related to associative references.
  • These are options to objc_setAssociatedObject() / typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, /* assign / OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /* retain, nonatomic / OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /* copy, nonatomic / OBJC_ASSOCIATION_RETAIN = 01401, /* retain, atmoic / OBJC_ASSOCIATION_COPY = 01403 /* copy, atomic */ };