iOS 避免常见崩溃(二)

avatar
奇舞团移动端团队 @奇舞团

级别: ★★☆☆☆
标签:「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。

    1. 系统KVO的简单使用;
    1. FBKVOController的简单使用;
    1. FBKVOController的类图,思维导图,使用流程图;
    1. FBKVOController避免写错待观察属性;
    1. FBKVOController初始化过程;
    1. FBKVOController 观察某对象的某属性;
    1. FBKVOController 观察对象属性变化;
    1. FBKVOController不需要开发者removeObserver;
    1. FBKVOController的线程安全实现方式之互斥锁;
    1. _FBKVOInfo重写isEqual:、 hash;
    1. NSMapTable之keyOptions;
    1. NSHashTable之weakObjectsHashTable;

系统KVO的简单使用

KVO的基础使用可查看大成哥的iOS KVC与KVO简介

笔者下边贴出的是一个系统方式观察Person的name属性的代码。

    1. addObserver
    1. 在observeValueForKeyPath方法中查看person的name属性的变化情况;
    1. 最后在控制器的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中下载) FBKVOClassDiagram.png

FBKVOMind.png

FBKVOFlowChart.png

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. 
当conditionfalse的时候会终端当前操作,并且终端操作的原因会展示为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,下文会提到。

这里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

参考学习网址


推荐文章:
iOS 避免常见崩溃(一)
算法小专栏:选择排序
iOS Runloop(一)
iOS 常用调试方法:LLDB命令
iOS 常用调试方法:断点
iOS 常用调试方法:静态分析
iOS消息转发
奇舞周刊