iOS 避免常见崩溃(二)

作者: QiShare | 来源:发表于2019-03-28 18:22 被阅读56次

    级别: ★★☆☆☆
    标签:「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. 
    当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,下文会提到。

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

    参考学习网址


    小编微信:可加并拉入《QiShare技术交流群》。

    关注我们的途径有:
    QiShare(简书)
    QiShare(掘金)
    QiShare(知乎)
    QiShare(GitHub)
    QiShare(CocoaChina)
    QiShare(StackOverflow)
    QiShare(微信公众号)

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

    相关文章

      网友评论

        本文标题:iOS 避免常见崩溃(二)

        本文链接:https://www.haomeiwen.com/subject/grccbqtx.html