Weak Associated Object
给分类添加weak属性的几种方法。
众所周知,通过Runtime的关联属性来给分类添加“属性”,这里的属性缺少了严格意义上的成员变量,而且是自己手动实现了getter方法和setter方法。几种关联策略中并没有与weak效果相媲美的选项,OBJC_ASSOCIATION_ASSIGN
策略与weak效果的主要区别在于weak自动能将指向已销毁对象的指针指为nil。
危险的ASSIGN
单纯使用ASSIGN容易诱发坏内存访问,原因无需多言。
1 | @interface NSObject(Default) |
极简方案
这是一种极好的给分类添加weak
属性的实现方式。看到这种实现方式后极为兴奋,实在太简洁、巧妙了。__weak
本身就会把指针指向nil,那直接利用就是了。使用OBJC_ASSOCIATION_COPY
关联策略将block copy到堆上,利用block把持有的weak
对象返回,如果对象不存在了,返回的便是空值。
1 | @interface NSObject(Weak) |
包装类
这种方式是通过包装一个对象实现的。要求设置的关联对象是YAWeakObject
类型。当这个对象销毁的时候调用deallocBlock
,而在这个block中把关联的对象重新设置为nil
(不可使用objc_removeAssociatedObjects
直接移除关联对象),这样访问这个关联对象的时候得到的就是nil值了。
这种方式会污染weak
属性,要求被设置为weak
属性的对象必须是某种类型,不是太好。当然根据这种思路,还可以进一步封装,最终的落脚点无非是提供新的方法接口替代原生的运行时方法(见参考文章)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19@interface NSObject(WeakClass)
@property (nonatomic) YAWeakObject *weakObject;
@end
@implementation NSObject(WeakClass)
- (YAWeakObject *)weakObject {
return objc_getAssociatedObject(self, @selector(weakObject));
}
- (void)setWeakObject:(YAWeakObject *)weakObject {
objc_setAssociatedObject(self, @selector(weakObject), weakObject, OBJC_ASSOCIATION_ASSIGN);
typeof(self) slf = self;
void (^block)(void) = ^{
typeof(slf) self = slf;
objc_setAssociatedObject(self, @selector(weakObject), nil, OBJC_ASSOCIATION_ASSIGN);
};
[weakObject setDeallocBlock:block];
}
@end
使用容器
实际上使用支持弱引用的容器如NSHashTable
、NSMapTable
、NSPointerArray
都是可以实现的。原理很简单,使用容器持有关联的对象,当该对象不存在时,容器自身便有自动移除已销毁对象的特性,这样就实现了weak
属性。
NSMapTable 可以持有键和值的弱引用,当键或者值当中的一个被释放时,整个这一项就会被移除掉。
NSHashTable 可以持有成员的弱引用。
NSPointerArray 可以持有成员的弱引用,当成员不存在时自动把所在index置为NULL。
这种做法需要创建一个容器,相对比较麻烦。
1 | @interface NSObject(WeakContainer) |
小结
其实看到作者的思路(极简方案)确实挺有感触的,完全利用现有的__weak
关键字配合block,没有冗余的包装,方法精简且巧妙。去年在想这个问题的时候,也是考虑很多,在《Runtime基础》一文中描述了我当时的思路,基本上也是从把指针置为nil这个角度出发,或者派生子类重写dealloc,或者使用弱引用容器,都不够巧妙。
很多时候,好的思路,灵光一现的想法,真的无比重要。就像bang哥在写JSPatch时:
当时继续苦苦寻找解决方案,若按 JS 语法,这是唯一的方法,但若不按 JS 语法呢?突然脑洞开了下,CoffieScript/JSX 都可以用 JS 实现一个解释器实现自己的语法,我也可以通过类似的方式做到,再进一步想到其实我想要的效果很简单,就是调用一个不存在方法时,能转发到一个指定函数去执行,就能解决一切问题了,这其实可以用简单的字符串替换,把 JS 脚本里的方法调用都替换掉。
还有一个东西在作者的文章里看到,比较有意思,这里提一下。
Weak Singleton
1 | + (instancetype)sharedWeakInstance { |
应用场景:不需要保存公共的信息、用户状态等,符合“用完就走”。如果类似LoginManager管理登录状态,继承自 AFHttpSessionManager的NetworkManager单例,App单例ClientManager等则不适用这种方式。
参考资料:
如何使用 Runtime 给现有的类添加 weak 属性
iOS给类别添加weak属性
iOS weak 关键字漫谈