专栏名称: Cocoa开发者社区
CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务。
目录
相关文章推荐
51好读  ›  专栏  ›  Cocoa开发者社区

OC弱引用容器实现方案总结

Cocoa开发者社区  · 公众号  · ios  · 2017-07-24 11:17

正文


在OC中Foundation框架中的常用容器类(NSSet,NSDictionary,NSArray)及其可变子类在加入元素时,均会对元素进行强引用。有的时候(比如持有多个Delegate对象时),希望有对应的弱引用容器使用。


实现思路


根据加入元素这个操作,很容易发现实现方案从容器本身、加入对象这个行为、对象本身三个方向入手。


容器本身支持弱引用对象


1.Foundation框架支持弱引用的容器类

  • NSHashTable-对应NSMutebleSet

  • NSMapTable-对应NSMutebleDictionary

  • NSPointerArray-对应NSMutebleArray


本身都是可变的,没有不可变父类。其共同点是addObject方法参数声明为nullable即不用判断是否为nil来避免崩溃,且都拥有初始化方法 - (instancetype)initWithOptions:(NSPointerFunctionsOptions)options 而参数options代表其所支持的放入对象的指针管理选项。根据苹果官方文档显示,这些枚举值被分为三类。


  • Memory Options(内存语义管理选项)


NSPointerFunctionsStrongMemory // 和strong一样,默认

NSPointerFunctionsOpaqueMemory // 在指针去除时不做任何动作

NSPointerFunctionsMallocMemory // 去除时调用free() , 加入时calloc()

NSPointerFunctionsMachVirtualMemory //使用可执行文件的虚拟内存

NSPointerFunctionsWeakMemory //和weak一样


  • Personaility Options(对象处理选项-如何进行哈希算法,判定等同性,描述)


NSPointerFunctionsObjectPersonality //使用NSObject的hash、isEqual、description,默认

NSPointerFunctionsOpaquePersonality //使用偏移后指针,进行hash和直接比较等同性

NSPointerFunctionsObjectPointerPersonality //和上一个相同,多了description方法

NSPointerFunctionsCStringPersonality //使用c字符串的hash和strcmp比较,%s作为decription

NSPointerFunctionsStructPersonality // 使用内存的hash和memcmp

NSPointerFunctionsIntegerPersonality //使用偏移量作为hash和等同性判断


  • Copy Options(对象拷贝选项)


NSPointerFunctionsCopyIn//通过NSCopying方法,复制后存入


另外,文档还强调, 每种类别的选项互斥,只能每种类别选择一个


当然,这三种容器还提供了快捷实例化类方法,含有weakObjects字样。


其中NSPointerArray稍微和其他两个有点特殊。

  1. 不支持OC的轻量级泛型(未被声明为

  2. 猜测和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方法有两种


  1. 将分类.m文件将编译选项切换为MRC(在Build Phases中的Compile Sources中加入编译标记-fno-objc-arc),直接使用[object release]

  2. 在ARC中,使用CFFoundation的release方法CFRelease







请到「今天看啥」查看全文