NSPointerArray原文 : iOS官方文档 Foundation篇---NSPointerArray - 简书
封装弱引用delegate集合demo: https://github.com/guochaoshun/weakPointerArray
iOS 中有很多种集合类型,最为常见的可能就 NSArray、NSDictionary、NSSet,但其实还有 NSPointerArray、NSMapTable、NSHashTable 等类型,虽然后面三个类型不常见,但是它们能在关键时刻,「救你一命」。 比如你有一个对象,需要对外抛代理方法, 但是一个普通的delegate已经不能满足要求了, 是一个集合来接受代理方法, 直接使用NSArray会导致代理对象的引用计数+1, 此时如果换成NSPointerArray、NSHashTable 就可以在不改变原对象生命周期的情况下完成需求.
先来看看传统的集合类型都有哪些短板:
1.放到集合中的对象,只能强引用
2.如果想要用void *,要先用 NSValue 打包
3.不能放入 nil
而对于 NSPointerArray、NSMapTable、NSHashTable 来说,除了解决以上问题外,还各有各的「特长」,先看看共同的地方 。
集合类型(包括传统集合)都是无法被继承的.
NSPointerFunctionsOptions,它是个 option,主要分为三大类:
内存管理
NSPointerFunctionsStrongMemory:默认值,强引用成员
NSPointerFunctionsZeroingWeakMemory:已废弃,在 GC 下,弱引用指针,防止悬挂指针
NSPointerFunctionsMallocMemory 与 NSPointerFunctionsMachVirtualMemory: 用于 Mach 的虚拟内存管理
NSPointerFunctionsWeakMemory:弱引用成员
特性,用于标明对象判等方式
NSPointerFunctionsObjectPersonality:hash、isEqual、对象描述
NSPointerFunctionsOpaquePersonality:pointer 的 hash 、直接判等
NSPointerFunctionsObjectPointerPersonality:pointer 的 hash、直接判等、对象描述
NSPointerFunctionsCStringPersonality:string 的 hash、strcmp 函数、UTF-8 编码方式的描述
NSPointerFunctionsStructPersonality:内存 hash、memcmp 函数
NSPointerFunctionsIntegerPersonality:值的 hash
内存标识
NSPointerFunctionsCopyIn:根据第二类的选择,来具体处理。如果是 NSPointerFunctionsObjectPersonality,则根据 NSCopying 来拷贝。
所以在使用时,可以多个组合,比如:需要强引用成员、使用对象方式对比、并且 add 时 copy 对象:
NSPointerArray
类似于数组的集合,但具有更广泛的可用内存语义;继承自NSObject;NSPointerArray具有以下特点:
- 与NSMutableArray一样,使用下标有序的插入或移除元素,且可修改数组内容;
- 可以插入或删除nil,并且 nil 参与 count 的计算 , 超出后会自动扩容;
- count 可以 set,如果直接 set count,那么会使用 nil 占位;
- 可以使用 weak 来修饰成员;
- 成员可以是所有指针类型;
- 遵循 NSFastEnumeration,可以通过 for...in 来进行遍历。
创建和初始化新的指针数组
// 根据指定选项返回新指针数组
NSPointerArray *pointerArray = [[NSPointerArray alloc]initWithOptions:NSPointerFunctionsStrongMemory];
NSPointerFunctions *functions = [[NSPointerFunctions alloc]initWithOptions:NSPointerFunctionsStrongMemory];
// 根据指定函数返回新指针数组
NSPointerArray *pointerArray1 = [[NSPointerArray alloc]initWithPointerFunctions:functions];
// 返回一个强引用元素的数组
NSPointerArray *pointerArray2 = [NSPointerArray strongObjectsPointerArray];
// 返回一个弱引用元素的数组
NSPointerArray *pointerArray3 = [NSPointerArray weakObjectsPointerArray];
管理集合
// 设置数组元素数量 , 会使用 nil 占位 , 超出会自动扩容
pointerArray.count = 5;
// 数组中元素数量
NSUInteger count = [pointerArray count];//5
// 数组中所有对象
[pointerArray allObjects];
// 指定索引处的指针
void *point = [pointerArray pointerAtIndex:0];//nil
// 数组中添加指针对象
[pointerArray addPointer:@"2"];//(2)
// 移除指定索引处的元素
[pointerArray removePointerAtIndex:0];//(2)
// 指定索引出插入元素
[pointerArray insertPointer:@"1" atIndex:0];//(1,2)
// 替换指定索引处的对象
[pointerArray replacePointerAtIndex:0 withPointer:@"2"];//(2,2)
// 删除数组中的nil值
[pointerArray compact];
// 获取数组的功能项
NSPointerFunctions *Functions = [pointerArray pointerFunctions];
// pointerArray有个bug,
如果不调用[pointerArray addPointer:nil],直接调用-compact,
不会移除pointerArray弱引用自动置nil的指针,导致pointerArray的下表和pointerArray下标不一致
pointerArray数组存在nil时,而allobject的值不包含nil,
参考链接:https://stackoverflow.com/questions/31322290/nspointerarray-weird-compaction
// 使用下面的写法, 可以保证把pointerArray中的所有nil移除掉
[pointerArray addPointer:nil];
[pointerArray compact];
NSMapTable
NSMapTable 是在 NSDictionary 之后的一个可变集合模型化的类 , 主要特点是在存入数据的时候可以设置对象为weak引用,key 可以不用遵循 NSCopying 协议;key 和 value 的内存管理方式可以分开,如:key 是强引用,value 是弱引用;
它们具有以下区别:
1. NSMapTable 会在集合里的对象被回收的时候删除此对象来保持对键 和/或 值保持“弱引用”;
2. 当 NSMapTable 添加一个键值对的时候其键或值可以被复制,也可以使用指针标识来进行相等和散列判断;
3. NSMapTable 可以包含任意指针(其内容不被约束为对象)。
你可以将 NSMapTable 实例配置为对任意指针进行操作,而不仅仅是对象,鼓励使用 C 的 API: void * 指针来操作。 基于对象的 API(例如 setObject:forKey :)将无法在不进行类型转换的情况下对无对象指针操作。
配置映射表时,请注意,只有NSMapTableOptions中列出的选项才能保证其余的API能够正常工作,包括复制,归档和快速枚举。虽然其他NSPointerFunctions选项用于某些配置,例如保留任意指针,但并不是所有选项的组合都有效。使用某些组合时映射表可能无法正常工作,甚至可能无法正确初始化。
创建方法
-
- (instancetype)initWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions capacity:(NSUInteger)initialCapacity
通过指定的选项来初始化NSMapTable对象;
keyOptions:一个位域,用于指定地NSMapTable中的键的选项。
valueOptions:一个位域,用于指定地NSMapTable中的值的选项
initialCapacity:NSMapTable的初始容量。 这只是一个提示; 随后可以根据需要增加和缩小NSMapTable。值必须与键中指定的所有索引的条目对应。
-
+ (NSMapTable<KeyType,ObjectType> *)mapTableWithKeyOptions:(NSPointerFunctionsOptions)keyOptions valueOptions:(NSPointerFunctionsOptions)valueOptions
同上,只不过没有初始指定容量;
-
- (instancetype)initWithKeyPointerFunctions:(NSPointerFunctions *)keyFunctions valuePointerFunctions:(NSPointerFunctions *)valueFunctions capacity:(NSUInteger)initialCapacity
使用指定的keyFunctions(NSPointerFunction的一个实例是一个适当的调用函数来管理其他地方持有的指针的引用)来初始化NSMapTable对象;
keyFunctions:用来管理key的NSPointerFunction;
valueFunctions:用来管理value的NSPointerFunction;
-
+ (NSMapTable<KeyType,ObjectType> *)strongToStrongObjectsMapTable
返回一个对key & value有强引用的NSMapTable对象;
-
+ (NSMapTable<KeyType,ObjectType> *)weakToStrongObjectsMapTable
返回一个对key弱引用,对value强引用的NSMapTable对象
不建议使用这种映射表,因为NSMapTable可能会在自动调整大小的时候,将weak key对应的value清空;
-
+ (NSMapTable<KeyType,ObjectType> *)strongToWeakObjectsMapTable
返回一个对value弱引用,对key强引用的NSMapTable对象
-
+ (NSMapTable<KeyType,ObjectType> *)weakToWeakObjectsMapTable
返回一个对value弱引用,对key也弱引用的NSMapTable对象
其实,这么多的初始化方法就对应着四种搭配:
key 为 strong,value 为 strong
key 为 strong,value 为 weak
key 为 weak,value 为 strong
key 为 weak,value 为 weak
当用 weak 修饰 key 或 value 时,有一方被释放,则该键值对移除。
获取里面的值
-
- (ObjectType)objectForKey:(KeyType)aKey
查找指定key对应的value,如果不存在此key则返回nil;
-
- (NSEnumerator<KeyType> *)keyEnumerator
返回一个允许访问映射表中每个键的枚举器对象,。
以下代码片段说明了如何使用该方法:NSEnumerator *enumerator = [myMapTable keyEnumerator]; id value; while ((value = [enumerator nextObject])) { /* code that acts on the map table's keys */ }
NOTE:使用快速枚举协议中的方法会更有效;(NSFastEnumeration)
-
- (NSEnumerator<ObjectType> *)objectEnumerator
同上,不过是对value的枚举;
-
@property(readonly) NSUInteger count
NSMapTable中键值对的个数;
修改里面的值
-
- (void)setObject:(ObjectType)anObject forKey:(KeyType)aKey
在映射表中添加指定的键值对;
-
- (void)removeObjectForKey:(KeyType)aKey
删除指定的键值对,如果key不存在则什么都不做;
-
- (void)removeAllObjects
删除所有的键值对;
根据NSMapTable创建一个字典
-
- (NSDictionary<KeyType,ObjectType> *)dictionaryRepresentation
返回NSMapTable的NSDictionary表示形式。
映射表的值和键必须符合NSMutableDictionary中setObject: forKey:
的所有要求。
获取指针函数
-
@property(readonly, copy) NSPointerFunctions *keyPointerFunctions
用于管理映射表中key的NSPointerFunctions对象;
-
@property(readonly, copy) NSPointerFunctions *valuePointerFunctions
用于管理映射表中value的NSPointerFunctions对象;
-
typedef NSUInteger NSMapTableOptions
用来指定NSMapTable对象中元素(键和值)的行为的常量。
NSHashTable
NSHashTable类似于NSSet,特别的是支持弱引用。区别如下:
1. NSHashTable可以对其内的成员进行弱引用;
2. NSHashTable的成员可以在添加的时候被拷贝一份副本;并且可以控制在将对象添加到NSHashTable中时是否调用对象上的 isEqualTo: 和 hash方法;
3. 它可以包含任意指针(其成员不被约束为对象)。
你可以将 NSHashTable 实例配置为对任意指针进行操作,而不仅仅是对象,鼓励使用 C 的 API: void * 指针来操作。基于对象的 API(例如 addObject:)将无法在不进行类型转换的情况下对无对象指针操作。
由于它的一些选项,NSHashTable不是一个集合,因为它可以有不同的行为(例如,如果指定了两个isEqual:相等的字符串可能都被添加)。
配置NSHashTable时,请注意,只有NSHashTableOptions中列出的选项才能保证其余的API能正常工作,包括复制,归档和快速枚举。 虽然其他NSPointerFunctions选项用于某些配置,例如保留任意指针,但并不是所有选项的组合都有效。 使用某些组合,NSHashTable可能无法正常工作,甚至可能无法正确初始化。
-
- (instancetype)initWithOptions:(NSPointerFunctionsOptions)options capacity:(NSUInteger)initialCapacity
根据使用的属性来初始化NSHashTable;
options:NSHashTable里面元素的支持的选项;
initialCapacity:NSHashTable可以容纳的元素的初始数量。
-
- (instancetype)initWithPointerFunctions:(NSPointerFunctions *)functions capacity:(NSUInteger)initialCapacity
便利构造器
-
+ (NSHashTable<ObjectType> *)weakObjectsHashTable
返回一个用于存储对其内容的弱引用的新NSHashTable对象。
一个新的NSHashTable,它使用NSPointerFunctionsWeakMemory和NSPointerFunctionsObjectPersonality,初始容量为0。
-
+ (NSHashTable<ObjectType> *)hashTableWithOptions:(NSPointerFunctionsOptions)options
使用给定的NSPointerFunctionsOptions去初始化NSHashTable;
获取里面的值
-
@property(nonatomic, readonly) ObjectType anyObject
NSHashTable对象里面的一个数据;
如果哈希表不包含对象,则为nil。返回的对象不能保证是随机的。
-
@property(readonly, copy) NSArray<ObjectType> *allObjects
返回NSHashTable中的所有数据;
-
@property(readonly, copy) NSSet<ObjectType> *setRepresentation
NSHashTable转为NSSet的形式;
-
@property(readonly) NSUInteger count
NSHashTable中元素的数量;
-
- (BOOL)containsObject:(ObjectType)anObject
给定的对象是否存在在NSHashTable中;
所使用的相等性比较取决于所选择的选项。 例如,使用NSPointerFunctionsObjectPersonality选项将使用
isEqual:
方法来判断相等。
-
- (ObjectType)member:(ObjectType)object
确定NSHashTable是否包含给定的对象,并返回该对象(如果存在),如果不存在返回nil;
所使用的相等性比较取决于所选择的选项。 例如,使用NSPointerFunctionsObjectPersonality选项将使用isEqual:
方法来判断相等。
-
- (NSEnumerator<ObjectType> *)objectEnumerator
返回包含NSHashTable中所有元素的枚举器对象。
你可以按照如下方法使用:NSEnumerator *enumerator = [myHashTable objectEnumerator]; id value; while ((value = [enumerator nextObject])) { /* code that acts on the hash table's values */ }
NOTE:使用快速枚举协议中的方法会更有效;(NSFastEnumeration)
-
- (void)addObject:(ObjectType)object
给hashTable添加指定元素;
-
- (void)removeObject:(ObjectType)object
删除hashTable中的指定元素;
-
- (void)removeAllObjects
删除hashTable中的所有元素;
-
- (void)intersectHashTable:(NSHashTable<ObjectType> *)other
从调用该方法的hashTable中删除不是另一个给定hashTable的成员的每个元素。
所使用的相等性比较取决于所选择的选项。 例如,使用NSPointerFunctionsObjectPersonality选项将使用
isEqual:
方法来判断相等。
-
- (BOOL)intersectsHashTable:(NSHashTable<ObjectType> *)other
返回一个布尔值,指示给定的hashTable是否与调用者有交集。
-
- (BOOL)isSubsetOfHashTable:(NSHashTable<ObjectType> *)other
返回一个布尔值,指示调用者中的每个元素是否也存在于给定的hashTable中。
-
- (BOOL)isEqualToHashTable:(NSHashTable<ObjectType> *)other
调用者是否与给定的hashTable相等;
如果两个hashTable的每个成员具有相同数量的成员,并且如果一个hashTable的每个成员存在于另一个hashTable中,则它们相等。
-
- (void)minusHashTable:(NSHashTable<ObjectType> *)other
从接收hashTable中删除给定hashTable中的每个元素(如果存在)。
-
- (void)unionHashTable:(NSHashTable<ObjectType> *)other
将给定hashTable的每个元素添加到接收hashTable(如果不存在)。
-
@property(readonly, copy) NSPointerFunctions *pointerFunctions
用于管理hashTable中元素的NSPointerFunctions对象;
这类集合类型在 ios6 之后引入,比传统的集合类型更为强大,但是它们的方法却没有传统集合类型多,比如对于 NSPointerArray 来说:
操作均基于 index,无法通过 object 来进行操作;
无法直接插入 array,或用 array 初始化;
查找功能没有 NSArray 强大;
没有逆序、排序等 API 提供
以上几点仅仅是举的例子,所以 NSPointerArray 也并没有看起来的那么强大,一切选择标准,都应该依据具体需求。
所以,还是要先熟悉各种集合的特性,然后去匹配需求,才是最好的。