级别: ★★☆☆☆
标签:「iOS 」「避免常见崩溃」「FBKVOController」「KVO」
作者: WYW
审校: QiShare团队
前言 项目中可能会用到KVO。关于KVO的基础使用可以查看大成哥的iOS KVC与KVO简介。系统提供的KVO的方式写起来代码较分散,有时候会出问题。
facebook有个开源项目KVOController下文用FBKVOController代指。FBKVOController用起来比较简单,可以在一定程度避免KVO的常见问题,本文笔者将通过分析FBKVOController,看看FBKVOController是如何避免KVO常见问题的。
使用KVO的常见问题有
- 添加观察者的时候,写错待观察的对象属性名;
- 多次添加对某对象的属性的观察;
- 忘记移除观察者,多次移除某观察者;
- 移除观察者的时候,观察者已释放;
FBKVOController封装了系统的KVO,解决上边提到的相应问题。下边笔者简单分析了FBKVOController是如何避免系统KVO相关问题的。笔者将会从如下几个方面来展开分析FBKVOController。
- 系统KVO的简单使用;
- FBKVOController的简单使用;
- FBKVOController的类图,思维导图,使用流程图;
- FBKVOController避免写错待观察属性;
- FBKVOController初始化过程;
- FBKVOController 观察某对象的某属性;
- FBKVOController 观察对象属性变化;
- FBKVOController不需要开发者removeObserver;
- FBKVOController的线程安全实现方式之互斥锁;
- _FBKVOInfo重写isEqual:、 hash;
- NSMapTable之keyOptions;
- NSHashTable之weakObjectsHashTable;
系统KVO的简单使用
KVO的基础使用可查看大成哥的iOS KVC与KVO简介。
笔者下边贴出的是一个系统方式观察Person的name属性的代码。
- addObserver
- 在observeValueForKeyPath方法中查看person的name属性的变化情况;
- 最后在控制器的dealloc中需要记得removeObserver。
_person = [Person new];
[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
_person.name = [NSString stringWithFormat:@"personName:QiShare_%u", arc4random() % 1000];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"SystemKVOChange:%@", change);
}
- (void)dealloc {
[_person removeObserver:self forKeyPath:@"name"];
}
FBKVOController的简单使用
FBKVOController是一个适用于iOS和OS X的简单的线程安全的KVO三方库。
线程安全是通过互斥锁方式保证的。
FBKVOController的简单体现在有时我们只需写一行代码即可。
其实FBKVOController也是对系统KVO的封装。
讨论FBKVOController简单使用的过程中,笔者将以观察Person类的name属性为例。
相关代码如下:
#import "NSObject+FBKVOController.h"
[self.KVOController observe:_person keyPath:FBKVOKeyPath(_person.name) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"FBKVOChange:%@", change);
}];
FBKVOController的类图,思维导图,使用流程图
为了便于更容易理解FBKVOController,笔者绘制了FBKVOController的类图,思维导图,使用流程图依次如下:
(思维导图比较模糊,如有需要请到QiSafeType中下载)
FBKVOController避免写错待观察属性;
FBKVOController为了使用过程中,避免写错待观察属性,设置了2个宏。
#define FBKVOKeyPath(KEYPATH) \
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))
#define FBKVOClassKeyPath(CLASS, KEYPATH) \
@(((void)(NO && ((void)((CLASS *)(nil)).KEYPATH, NO)), #KEYPATH))
笔者仍以观察Person类的name属性为例,查看这2个宏的使用方式;
这里的宏使用了逗号表达式。简单说逗号表达式的结果就是最右边的表达式的结果。如(3+5,6+8)的结果为14。
c语言提供一种特殊的运算符,逗号运算符,优先级别最低,它将两个及其以上的式子联接起来,从左往右逐个计算表达式,整个表达式的值为最后一个表达式的值。如:(3+5,6+8)称为逗号表达式,其求解过程先表达式1,后表达式2,整个表达式值是表达式2的值,如:(3+5,6+8)的值是14,a=(a=35,a4)的值是60,而(a=35,a4)的值是60, a的值在逗号表达式里一直是15,最后被逗号表达式赋值为60,a的值最终为60。 摘自360百科
FBKVOKeyPath使用了断言检测待观察对象的属性:
断言:当需要在一个值为FALSE时,中断当前操作的话,可以使用断言。断言
#define NSCAssert(condition, desc, ...)
Assertions evaluate a condition and, if the condition evaluates to false, call the assertion handler for the current thread, passing it a format string and a variable number of arguments.
当condition为false的时候会终端当前操作,并且终端操作的原因会展示为desc。
以_person.name为例,使用FBKVOKeyPath(_person.name),分析FBKVOKeyPath(KEYPATH)
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))
((void)KEYPATH, NO) 是为了编译_person.name;
编译通过了之后const char *fbkvokeypath = strchr(#KEYPATH, '.');
#keypath宏返回的是字符串"_person.name";
fbkvokeypath是'.'在#KEYPATH即"_person.name"中的指针。
fbkvokeypath + 1返回的即"name"。
最后结合@,即为待观察的Person的属性@"name"
以_person.name为例,使用FBKVOClassKeyPath(Person, name),分析FBKVOClassKeyPath(CLASS, KEYPATH);
#define FBKVOClassKeyPath(CLASS, KEYPATH) \
@(((void)(NO && ((void)((CLASS *)(nil)).KEYPATH, NO)), #KEYPATH))
((CLASS *)(nil)).KEYPATH 部分是为了编译_person.name
编译通过后,#KEYPATH返回的是"name",结合@。
即最后的待观察对象@"name"
FBKVOController初始化过程
初始化FBKVOController的过程。
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved {
self = [super init];
if (nil != self) {
_observer = observer;
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);
}
return self;
}
如上代码所示:
-
初始化了_observer = observer,这里的observer是weak修饰的,是为了避免出现Retain Cycle。
- 如果是strong修饰observer会出现 控制器 持有 FBKVOController,FBKVOController 持有 observer(控制器),出现Retain Cycle。
-
初始化了_objectInfosMap
- _objectInfosMap(
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
,用于存储待观察对象,待观察对象为key
,及待观察对象的属性,及回调相关信息,待观察对象的属性及其他信息为value
)。 - NSMapTable的keyOptions为NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality,可以做到,当key(此处为object)释放的时候,_objectInfosMap中的相应的key value会自动移除。
- 关于NSMapTable的keyOptions,下文会提到。
- _objectInfosMap(
这里FBKVOController实例,可以直接使用NSObject+FBKVOController.h
添加的属性kvoController(即self.kvoController);
也可以使用如下全能初始化方法创建。
- (instancetype)initWithObserver:(nullable id)observer
retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved {
self = [super init];
if (nil != self) {
_observer = observer;
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
pthread_mutex_init(&_lock, NULL);
}
return self;
}
注意:防止Retain Cycle
- 如果在当前类中,观察当前类的属性,传入的retainObserved参数需要传入NO。
retainObserved参数用于控制,创建FBKVOController实例的时候,_objectInfosMap对key持有弱引用还是强引用。
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
,key用于存储待观察者object,value用于存储待观察对象object的待观察信息。
在当前类中观察当前类的属性的示例:
[self.KVOControllerNonRetaining observe:self keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"FBKVOChange:%@", change);
}];
上例如果使用self.KVOController。将会出现
self(QiSafeKVOController) 持有 KVOController(FBKVOController)
KVOController(FBKVOController) 持有 _objectInfosMap
_objectInfosMap 持有 self(QiSafeKVOController)
的循环引用的问题。
FBKVOController 观察某对象的某属性;
FBKVOController观察的对象,及观察的对象的keyPath,option,block等信息都存储在了_FBKVOInfo实例中。
笔者以
- (void)observe:(nullable id)object
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(FBKVONotificationBlock)block;
分析观察某对象某属性的过程。
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block {
// 对keyPath block 及 待观察对象object的简单校验
NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
// 创建存储观察者信息的info(_FBKVOInfo实例) 存储观察者self keyPath options 及值改变的block
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// 使用info观察object
[self _observe:object info:info];
}
- 在观察对象属性的时候,FBKVOController用到了_FBKVOInfo的实例,
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
用于存储待观察对象的属性及回调信息。
- (void)_observe:(id)object info:(_FBKVOInfo *)info {
// 互斥锁加锁
pthread_mutex_lock(&_lock);
// 查看_objectInfosMap中是否已经添加过object对应的信息
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// 查看infos中是否已经添加过info信息 这里的查看方式是按照object的keypath的hash值确定的
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 查看与待观察对象object相应的infos中,已经添加过info信息,解锁返回
pthread_mutex_unlock(&_lock);
return;
}
// _objectInfosMap之前没有添加过对待观察对象object的信息,创建用于存储object相应的infos信息的内容
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// 同样会调用hash 添加info信息到infos中
[infos addObject:info];
// 解锁
pthread_mutex_unlock(&_lock);
[[_FBKVOSharedController sharedController] observe:object info:info];
}
- 如下代码可以避免重复观察某对象的某属性。
// 查看与待观察对象object的属性信息 是否已经添加过info信息
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 查看与待观察对象object相应的infos中,已经添加过info信息,解锁返回
pthread_mutex_unlock(&_lock);
return;
}
- _FBKVOShareController中的如下方法封装了系统的KVO,及改变FBKVOInfo的
_FBKVOInfoStateInitial
状态为_FBKVOInfoStateObserving
。
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) {
return;
}
// 存储待观察对象的信息 到_infos中
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// 系统的方式添加观察者
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
// 改变要观察的对象的info的观察状态为 _FBKVOInfoStateObserving
info->_state = _FBKVOInfoStateObserving;
} else if (info->_state == _FBKVOInfoStateNotObserving) {
// 这部分内容笔者没有复现
// this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
// and the observer is unregistered within the callback block.
// at this time the object has been registered as an observer (in Foundation KVO),
// so we can safely unobserve it.
// 系统方式移除观察者
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
FBKVOController 观察对象属性变化
在FBKVOController中有如下系统KVO的observeValueForKeyPath
方法及block回调代码。
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context
{
NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
_FBKVOInfo *info;
{
// lookup context in registered infos, taking out a strong reference only if it exists
pthread_mutex_lock(&_mutex);
info = [_infos member:(__bridge id)context];
pthread_mutex_unlock(&_mutex);
}
if (nil != info) {
// take strong reference to controller
FBKVOController *controller = info->_controller;
if (nil != controller) {
// take strong reference to observer
id observer = controller.observer;
if (nil != observer) {
// dispatch custom block or action, fall back to default action
if (info->_block) {
NSDictionary<NSKeyValueChangeKey, id> *changeWithKeyPath = change;
// add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
if (keyPath) {
NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
[mChange addEntriesFromDictionary:change];
changeWithKeyPath = [mChange copy];
}
info->_block(observer, object, changeWithKeyPath);
} else if (info->_action) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[observer performSelector:info->_action withObject:change withObject:object];
#pragma clang diagnostic pop
} else {
[observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
}
}
}
}
}
以下代码实现了简单校验info->controller、观察者、info->block,校验无误的情况下,进行block回调,实现回调到控制器中的block的部分。
if (nil != info) {
// take strong reference to controller
FBKVOController *controller = info->_controller;
if (nil != controller) {
// take strong reference to observer
id observer = controller.observer;
if (nil != observer) {
// dispatch custom block or action, fall back to default action
if (info->_block) {
NSDictionary<NSKeyValueChangeKey, id> *changeWithKeyPath = change;
// add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
if (keyPath) {
NSMutableDictionary<NSString *, id> *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
[mChange addEntriesFromDictionary:change];
changeWithKeyPath = [mChange copy];
}
info->_block(observer, object, changeWithKeyPath);
}
FBKVOController观察对象
FBKVOController观察的对象,及观察的对象的keyPath,option,block等信息都存储在了FBKVOInfo实例中。
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block {
// 使用断言 对keyPath block 的简单校验
NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
// 对keyPath block 及 待观察对象object的简单校验
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
// 创建存储观察者信息的info(_FBKVOInfo实例) 存储观察者self keyPath options 及值改变的block
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// 使用info观察object
[self _observe:object info:info];
}
- (void)_observe:(id)object info:(_FBKVOInfo *)info {
// 互斥锁加锁
pthread_mutex_lock(&_lock);
// 查看_objectInfosMap中是否已经添加过object对应的信息
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// 查看与待观察对象object相应的infos中 是否已经添加过info信息
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 查看与待观察对象object相应的infos中,已经添加过info信息,解锁返回
pthread_mutex_unlock(&_lock);
return;
}
// _objectInfosMap之前没有添加过对待观察对象object的信息,创建用于存储object相应的infos信息的内容
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// 同样会调用hash 添加info信息到infos中
[infos addObject:info];
// 解锁
pthread_mutex_unlock(&_lock);
[[_FBKVOSharedController sharedController] observe:object info:info];
}
封装系统KVO,改变info->_state。
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info {
if (nil == info) {
return;
}
// 存储待观察对象的信息 到_infos中
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// **系统的方式添加观察者**
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
// 改变要观察的对象的info的观察状态为 _FBKVOInfoStateObserving
info->_state = _FBKVOInfoStateObserving;
} else if (info->_state == _FBKVOInfoStateNotObserving) {
// 这部分内容笔者没有复现
// this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
// and the observer is unregistered within the callback block.
// at this time the object has been registered as an observer (in Foundation KVO),
// so we can safely unobserve it.
// 系统方式移除观察者
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
存储待观察的object及FBKVOInfo信息到_objectInfosMap,为了避免多次观察某对象的同一属性,在存储操作前有个简单的校验.
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// 查看与待观察对象object相应的infos中,已经添加过info信息,解锁返回
pthread_mutex_unlock(&_lock);
return;
}
FBKVOController不需要开发者removeObserver
在控制器销毁的时候,FBKVOController的实例也会销毁,FBKVOController在实现文件中重写了dealloc,依次移除之前objectsInfosMap中的需要移除观察者的object的观察者。
- (void)dealloc {
[self unobserveAll];
// 销毁互斥锁
pthread_mutex_destroy(&_lock);
}
- (void)unobserveAll {
[self _unobserveAll];
}
- (void)_unobserveAll {
// 互斥锁加锁
pthread_mutex_lock(&_lock);
// copy一份_objectInfosMap
NSMapTable *objectInfoMaps = [_objectInfosMap copy];
// 清空_objectInfosMap中的观察者object及观察的infos信息
[_objectInfosMap removeAllObjects];
// 解锁
pthread_mutex_unlock(&_lock);
// 获取单例
_FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
// 依次取消objectInfoMaps中observer的信息
for (id object in objectInfoMaps) {
// 取消观察每一个注册了观察的object及相应的观察的信息
NSSet *infos = [objectInfoMaps objectForKey:object];
[shareController unobserve:object infos:infos];
}
}
- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos {
// 如果没有待移除的object相关的info信息了, return
if (0 == infos.count) {
return;
}
// _infos移除infos中的info信息
/**
_infos中存放的是观察的所有object的info信息
infos存储的是当前的object的info信息
info指的的infos中的每个info(_FBKVOInfo *)信息
*/
pthread_mutex_lock(&_mutex);
for (_FBKVOInfo *info in infos) {
[_infos removeObject:info];
}
pthread_mutex_unlock(&_mutex);
// 移除info指定的keyPath及context信息的观察者 并且info的状态为_FBKVOInfoStateNotObserving
for (_FBKVOInfo *info in infos) {
if (info->_state == _FBKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = _FBKVOInfoStateNotObserving;
}
}
FBKVOController的线程安全实现方式之互斥锁;
- FBKVOController的线程安全是通过互斥锁实现的
mutext(MUTual EXclusion)是一种互斥设置。 用于保护共享数据免于并发访问、设置出现问题。还有一些其他挑剔的情况。
在FBKVOController中,在操作_objectInfosMap,_infos的时候使用了互斥锁。
就FBKVOController的实例变量_objectInfosMap而言。
在初始化FBKVOController的时候,初始化了互斥锁;在读写objectInfosMap之前锁定了互斥锁;在读写完objectInfosMap之后,解锁了互斥锁;在FBKVOController销毁的时候销毁了互斥锁。
// 使用互斥锁需要导入pthread.h
#import <pthread.h>
pthread_mutex_t _lock;
// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);
// 锁定互斥锁
pthread_mutex_lock(&_lock);
// 解锁互斥锁
pthread_mutex_unlock(&_lock);
// 销毁互斥锁
pthread_mutex_destroy(&_lock);
// 初始化互斥锁
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_init(pthread_mutex_t * __restrict,
const pthread_mutexattr_t * _Nullable __restrict);
// 锁定互斥锁
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_lock(pthread_mutex_t *);
// 解除锁定互斥锁
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_unlock(pthread_mutex_t *);
// 销毁互斥锁
__API_AVAILABLE(macos(10.4), ios(2.0))
int pthread_mutex_destroy(pthread_mutex_t *);
互斥锁的使用可以查看:使用互斥锁
关于锁的更多内容可以查看大成哥的:iOS 多线程之线程安全
_FBKVOInfo重写isEqual:、 hash
_FBKVOInfo重写了isEqual: 和hash方法。
_FBKVOInfo重写isEqual:和hash方法的原因是,FBKVOController想要自己去控制2个_FBKVOInfo的实例是否相等。这里_FBKVOInfo是根据的 _keypath的hash值判断是否相等的。
- (NSUInteger)hash {
return [_keyPath hash];
}
- (BOOL)isEqual:(id)object {
if (nil == object) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
}
If two objects are equal, they must have the same hash value. This last point is particularly important if you define isEqual: in a subclass and intend to put instances of that subclass into a collection. Make sure you also define hash in your subclass.
如果两个对象是相等的,他们一定有相同的hash值。尤其重要的是,如果我们打算把子类实例放到一个集合对象中,并且在子类中重写了isEqual方法的时候,请确保也在子类中重写了hash方法。
NSMapTable之keyOptions;
FBKVOController使用了NSMapTable存储要监听的对象。
NSMapTable<id, NSMutableSet<_FBKVOInfo *> *> *_objectInfosMap;
_objectInfosMap的key为要观察的对象
_objectInfosMap的value存储了FBKVOInfo的NSMutableSet信息.
- _objectInfosMap 初始化。
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
self = [super init];
if (nil != self) {
_observer = observer;
// _objectInfosMap 初始化
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
pthread_mutex_init(&_lock, NULL);
}
return self;
}
A collection similar to a dictionary, but with a broader range of available memory semantics.
Declaration
@interface NSMapTable<__covariant KeyType, __covariant ObjectType> : NSObject
Discussion
The map table is modeled after NSDictionary with the following differences:
Keys and/or values are optionally held “weakly” such that entries are removed when one of the objects is reclaimed.Its keys or values may be copied on input or may use pointer identity for equality and hashing.
It can contain arbitrary pointers (its contents are not constrained to being objects).
NSMapTable 是一中类似于NSDictionary的集合,不过NSMapTable有更广范的内存语义。
NSMapTable在NSDictionary的基础上做了部分修整,NSMapTable相比较NSDictionary有如下不同的地方:
NSMapTable的keys或者values是可选地持有weak类型对象的。当NSMapTable中的weak类性的key或者value释放的时候,相应的键值对会自动从NSMapTable中移除。
NSMapTable可以在添加键值对的时候进行拷贝操作,可以通过指针进行相等性和散列检查。
NSMapTable可以包含任意指针(她的内容不限于对象)。
NSMapTable关于keyOptions相关代码:
// weakKeyStrongObjectsMapTable
NSMapTable *weakKeyStrongObjectsMapTable = [NSMapTable weakToStrongObjectsMapTable];
/** // 相当于
NSPointerFunctionsOptions weakOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality;
NSPointerFunctionsOptions strongOptions = NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality;
weakKeyStrongObjectsMapTable = [NSMapTable mapTableWithKeyOptions:weakOptions valueOptions:strongOptions];
*/
NSObject *key0 = [NSObject new];
NSObject *obj0 = [NSObject new];
[weakKeyStrongObjectsMapTable setObject:obj0 forKey:key0];
NSLog(@"weakKeyStrongObjectsMapTable:%@", weakKeyStrongObjectsMapTable);
/*
weakKeyStrongObjectsMapTable:NSMapTable {
[3] <NSObject: 0x600001711180> -> <NSObject: 0x600001711190>
}
*/
key0 = nil;
NSLog(@"key0 =nil, weakKeyStrongObjectsMapTable:%@", weakKeyStrongObjectsMapTable);
/*
key0 =nil, weakKeyStrongObjectsMapTable:NSMapTable {
}
*/
// weakKeyWeakObjsMapTable
NSObject *key1 = [NSObject new];
NSObject *obj1 = [NSObject new];
NSObject *key2 = [NSObject new];
NSObject *obj2 = [NSObject new];
NSMapTable *weakKeyWeakObjsMapTable = [NSMapTable weakToWeakObjectsMapTable];
// 相当于
// weakKeyWeakObjsMapTable = [NSMapTable mapTableWithKeyOptions:weakOptions valueOptions:weakOptions];
[weakKeyWeakObjsMapTable setObject:obj1 forKey:key1];
[weakKeyWeakObjsMapTable setObject:obj2 forKey:key2];
NSLog(@"weakKeyWeakObjsMapTable:%@", weakKeyWeakObjsMapTable);
/*
weakKeyWeakObjsMapTable:NSMapTable {
[3] <NSObject: 0x600001711180> -> <NSObject: 0x600001710fa0>
[10] <NSObject: 0x6000017111a0> -> <NSObject: 0x6000017110b0>
}
*/
key1 = nil;
NSLog(@"key1 = nil, weakKeyWeakObjsMapTable:%@", weakKeyWeakObjsMapTable);
/*
key1 = nil, weakKeyWeakObjsMapTable:NSMapTable {
[10] <NSObject: 0x6000017111a0> -> <NSObject: 0x6000017110b0>
}
*/
obj2 = nil;
[weakKeyWeakObjsMapTable setObject:obj1 forKey:key1];
NSLog(@"obj2 = nil, weakKeyWeakObjsMapTable:%@", weakKeyWeakObjsMapTable);
/*
obj2 = nil, weakKeyWeakObjsMapTable:NSMapTable {
}
*/
以weakToStrongObjectsMapTable创建的NSMapTable的实例weakKeyStrongObjectsMapTable,当添加的key key1销毁时,相应的key1 obj1的键值对会自动移除。
NSHashTable之weakObjectsHashTable
NSHashTable
A collection similar to a set, but with broader range of available memory semantics.The hash table is modeled after NSSet with the following differences:
It can hold weak references to its members.
Its members may be copied on input or may use pointer identity for equality and hashing.
It can contain arbitrary pointers (its members are not constrained to being objects).
NSHashTable
NSHashTable是类似于NSSet的集合,不过NSHashTable有更加广泛的内存语义。NSHashTable是在NSSet的基础上做的调整,相比较NSSet,NSHashTable有如下不同之处:
NSHashTable可以持有成员的弱引用。
NSHashTable可以在加入成员时执行copy操作,可以通过isEqual:和hash检测成员的散列值和相等性。
NSHashTable可以存放任意的指针(NSHashTable的成员不限于对象)。
相关代码:
// NSHashTable
NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
// 相当于
[NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality];
NSObject *hashObj = [NSObject new];
[hashTable addObject:hashObj];
NSLog(@"hashTable:%@", hashTable);
/*
hashTable:NSHashTable {
[11] <NSObject: 0x600002528af0>
}
*/
hashObj = nil;
NSLog(@"hashObj = nil, hashTable:%@", hashTable);
/*
hashObj = nil, hashTable:NSHashTable {
}
*/
对于weakObjectsHashTable创建的NSHashTable实例hashTable,当hashTable中添加的obj,销毁后,hashTable中的之前添加的obj,会自动移除。
Demo
- 更多相关内容,可查看Demo QiSafeType。
参考学习网址
- facebook/KVOController
- 逗号表达式
- 断言
- ProcessOn
- Equality
- NSHashTable & NSMapTable
- 使用互斥锁
- iOS 多线程之线程安全
- iOS KVC与KVO简介
小编微信:可加并拉入《QiShare技术交流群》。
关注我们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)
推荐文章:
iOS 避免常见崩溃(一)
算法小专栏:选择排序
iOS Runloop(一)
iOS 常用调试方法:LLDB命令
iOS 常用调试方法:断点
iOS 常用调试方法:静态分析
iOS消息转发
奇舞周刊
网友评论