在OC中Foundation框架中的常用容器类(NSSet,NSDictionary,NSArray)及其可变子类在加入元素时,均会对元素进行强引用。有的时候(比如持有多个Delegate对象时),希望有对应的弱引用容器使用。
实现思路
根据加入元素这个操作,很容易发现实现方案从容器本身、加入对象这个行为、对象本身三个方向入手。
容器本身支持弱引用对象
1.Foundation框架支持弱引用的容器类
-
NSHashTable-对应NSMutebleSet
-
NSMapTable-对应NSMutebleDictionary
-
NSPointerArray-对应NSMutebleArray
本身都是可变的,没有不可变父类。其共同点是addObject方法参数声明为nullable即不用判断是否为nil来避免崩溃,且都拥有初始化方法
- (instancetype)initWithOptions:(NSPointerFunctionsOptions)options
而参数options代表其所支持的放入对象的指针管理选项。根据苹果官方文档显示,这些枚举值被分为三类。
NSPointerFunctionsStrongMemory // 和strong一样,默认
NSPointerFunctionsOpaqueMemory // 在指针去除时不做任何动作
NSPointerFunctionsMallocMemory // 去除时调用free() , 加入时calloc()
NSPointerFunctionsMachVirtualMemory //使用可执行文件的虚拟内存
NSPointerFunctionsWeakMemory //和weak一样
NSPointerFunctionsObjectPersonality //使用NSObject的hash、isEqual、description,默认
NSPointerFunctionsOpaquePersonality //使用偏移后指针,进行hash和直接比较等同性
NSPointerFunctionsObjectPointerPersonality //和上一个相同,多了description方法
NSPointerFunctionsCStringPersonality //使用c字符串的hash和strcmp比较,%s作为decription
NSPointerFunctionsStructPersonality // 使用内存的hash和memcmp
NSPointerFunctionsIntegerPersonality //使用偏移量作为hash和等同性判断
NSPointerFunctionsCopyIn//通过NSCopying方法,复制后存入
另外,文档还强调,
每种类别的选项互斥,只能每种类别选择一个
。
当然,这三种容器还提供了快捷实例化类方法,含有weakObjects字样。
其中NSPointerArray稍微和其他两个有点特殊。
-
不支持OC的轻量级泛型(未被声明为
-
猜测和1有关,addPointer方法(相当于NSMuteableArray的addObject方法)参数类型是void指针,所以需要(__bridge void )转换
这个方案应该是最简单的了,毕竟是苹果Foundation框架里封装的高级对象。但是其缺点是性能相比对应强引用容器在性能上有所欠缺,根据Objc.io文章中的测试结果,
NSHashTable的addObject和NSPointerArray的addPointer
性能差距尤其严重,使用时需要注意。
2.用CFFoundation框架里对应容器类自定义内存管理选项
在初始化时,使用CFFoundation里的容器类实现后转成Foundation对象
以NSMutableSet为例,可以增加一个新的分类,自定义一个实例化方法
+ (instancetype)cdz_weakSet{
CFSetCallBacks callbacks = {0, NULL, NULL, CFCopyDescription, CFEqual};
return (NSMutableSet *)CFBridgingRelease(CFSetCreateMutable(0, 0, &callbacks));
}
建立容器类的步骤
生成CallBacks结构体,结构体中的对象其实就是一些描述容器的选项
同样以CFSetCallBacks为例,其结构体定义如下
typedef struct {
CFIndex version;
CFSetRetainCallBack retain;
CFSetReleaseCallBack release;
CFSetCopyDescriptionCallBack copyDescription;
CFSetEqualCallBack equal;
CFSetHashCallBack hash;
} CFSetCallBacks;
version暂时不管它,看下剩下变量类型名字,是否感觉比较熟悉
其实和之前NSPointerFunctionsOptions类似,还是分三种,retain和release是内存管理,equal和hash和是对象处理,copyDescription是复制处理。
所以我们就需要改变retain和release就好。类型是xxxCallBack那么说明应该这个类型是一个block,而去当这些操作执行执行对应的block。文档对retain和release这两个block的描述也是这样说的。那么如果要实现引用计数增减,就应该在这两个block里实现,所以我猜测Foundation框架里的容器类retain,应该就在这个两个block里实现。那么我们只需要block值为NULL,在retain和release操作里,就不会引用计数的变化,从而不持有对象。
使用CreatMutable生成可变容器
这时传入之前的自定义callBacks,也可以传入capacity之类的(默认为0就好),转换为Foundation对象
之后我们只要有这个实例化方法生成的容器类,其它就和正常使用一样了。
加入容器时不引用
方式很简单,在addObject后调用一次release减少引用技术。
release方法有两种
-
将分类.m文件将编译选项切换为MRC(在Build Phases中的Compile Sources中加入编译标记-fno-objc-arc),直接使用[object release]
-
在ARC中,使用CFFoundation的release方法CFRelease