iOS-设计一个在dealloc中自动移除KVO的分类

KVO在项目中使用很多,主要是两种原因会使KVO崩溃

  • 1、KVO没有被移除
  • 2、KVO移除的次数比添加的次数多

设计思路

  • 1、利用runtime交换了addObserver:forKeyPath:options:context:
  • 2、在替换的addObserver:forKeyPath:options:context:
    • a、创建一个MapkeyPath作为keyValueKVOItem对象,而KVOItem保存是的被监听的对象,监听的属性,这个Map是保存利用关联属性保存的
    • b、调用原生的addObserver:forKeyPath:options:context:方法,
    • c、最后利用method_setImplementation修改了监听者的dealloc方法的实现,里面先是判断是否被当作监听对象,如果有,遍历并移除KVO,然后调用原有的dealloc方法 。没有使用交换方法的原因是,没有被当作监听的对象在dealloc方法中,也会判断被当作监听的对象。
  • 3、但是在项目中,可能会有开发人员、系统自己在dealloc中移除KVO,这样就会一个问题,因为在调用dealloc之前, KVO已经被移除了,这时候再次移除会崩溃,所以利用runtime交换removeObserver:forKeyPath:方法,由于removeObserver:forKeyPath:context底层也是调用removeObserver:forKeyPath:, 所以这个方法不用替换。
  • 4、在替换的removeObserver:forKeyPath:
    • a、利用observer对象取出对应的Map,判断是否存在对应的keyPath, 如果存在,就移除KVO,并且从Map移除对应的KeyPath
//
//  NSObject+KVO.m
//  CrashSafe
//
//  Created by 无头骑士 GJ on 2019/1/26.
//  Copyright © 2019 无头骑士 GJ. All rights reserved.
//

#import "NSObject+KVO.h"
#import 

static const char KVOArrayKey;

@interface WTKVOItem: NSObject

@property (nonatomic, weak) id obj;

@property (nonatomic, strong) NSString *keyPath;


@end

@implementation WTKVOItem


@end

@implementation NSObject (KVO)

+ (void)load
{
    Method addObserver = class_getInstanceMethod(self, @selector(addObserver:forKeyPath:options:context:));
    Method wt_addObserver = class_getInstanceMethod(self, @selector(wt_addObserver:forKeyPath:options:context:));
    method_exchangeImplementations(addObserver, wt_addObserver);
    
    Method removeObserver = class_getInstanceMethod(self, @selector(removeObserver:forKeyPath:));
    Method wt_removeObserver = class_getInstanceMethod(self, @selector(wt_removeObserver:forKeyPath:));
    method_exchangeImplementations(removeObserver, wt_removeObserver);
}


- (void)wt_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
    if (observer == nil || keyPath == nil || keyPath.length == 0) return;
    
    NSMutableDictionary *keyPathdict = objc_getAssociatedObject(observer, &KVOArrayKey);
    if (keyPathdict == nil)
    {
        keyPathdict = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(observer, &KVOArrayKey, keyPathdict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    WTKVOItem *item = [WTKVOItem new];
    item.keyPath = keyPath;
    item.obj = self;
    
    keyPathdict[keyPath] = item;

    [self wt_addObserver: observer forKeyPath: keyPath options: options context: context];
    
    
    [self replaceImpl: observer.class];
}

- (void)wt_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
    NSMutableDictionary *keyPaths = objc_getAssociatedObject(observer, &KVOArrayKey);
    if (keyPaths == nil) return;
    
    if ([keyPaths objectForKey: keyPath])
    {
        [self wt_removeObserver: observer forKeyPath: keyPath];
        
        [keyPaths removeObjectForKey: keyPath];
    }
}


- (void)wt_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context
{
    NSMutableDictionary *keyPaths = objc_getAssociatedObject(observer, &KVOArrayKey);
    if (keyPaths == nil) return;
    
    if ([keyPaths objectForKey: keyPath])
    {
        [self wt_removeObserver: observer forKeyPath: keyPath context: context];
        
        [keyPaths removeObjectForKey: keyPath];
    }
}

- (void)replaceImpl:(Class)cls
{
    Method dealloc = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
    
    __block IMP deallocIMP = method_setImplementation(dealloc, imp_implementationWithBlock(^(__unsafe_unretained id self){
        
        ((void(*)(id, SEL))objc_msgSend)(self, @selector(cleanupSEL));
        
        ((void(*)(id, SEL))deallocIMP)(self, NSSelectorFromString(@"dealloc"));
    
    }));
}

- (void)cleanupSEL
{
    NSMutableDictionary *keyPaths = objc_getAssociatedObject(self, &KVOArrayKey);
    if (keyPaths == nil) return;
    
    [keyPaths enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, WTKVOItem * _Nonnull obj, BOOL * _Nonnull stop) {
        
        [obj.obj removeObserver: self forKeyPath: obj.keyPath];
        
        
    }];
    
    objc_setAssociatedObject(self, &KVOArrayKey, NULL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


@end

你可能感兴趣的:(iOS-设计一个在dealloc中自动移除KVO的分类)