自己尝试等装一下恩美第二个APP项目iOS开发

KVO封装,应该是最精简的封装了。

2017-03-17  本文已影响887人  喵子G

1 KVO基础

KVO是用来做监听的,这个相信大多数人都知道,常规的代码如下:

[self.view addObserver:self
            forKeyPath:@"isSelected"
               options:NSKeyValueObservingOptionNew
               context:nil];

四个参数:
调用该方法的对象就是要监听的对象,即被监听的对象。
addOberver的参数就是监听者对象,即监听回调接收者。
KeyPath的参数就是要监听的健值。
context的参数这个其实就是传值参数,这里传什么值,监听回调方法的context就带过来什么值,可以用来传一些特征参数什么的,但是这个参数传进来并不会被强引用,如果传对象进来要注意让对象的在监听接收的时候没有被释放掉。

KVO到底干了什么:

2 KVO到底监听了什么

很多时候KVO都被用来监听属性,其实KVO并不是只能监听属性,只要KVC能够调用的KVO都能够监听。比如就算被监听没有 touch 属性, 也没有touch成员变量,但是如果对象中有setTouch/touch方法,那么KVO也会被被响应。
例如如下代码,对象并没有touch属性,但是有touch的get/set方法,如果调用了setTouch方法,那么KVO就会监听到,并通过touch方法返回值。
️:没有属性对应的情况下如果没有touch方法,KVO找不到值会崩溃。

事实上KVC也是一样,其取值和赋值也并不是直接取找对象的属性和成员变量,而是通过先找set/get方法 => 没找到再找变量 的顺序调用的。

    [self.rootView jkr_addObserver:self forKeyPath:@"touch" change:^(id newValue) {
        NSLog(@"%@", newValue);
    }];
...
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.isSelected = !self.isSelected;
    if (self.isSelected) {
        self.circleColor = [UIColor redColor];
        [self setNeedsDisplay];
    } else {
        self.circleColor = [UIColor orangeColor];
        [self setNeedsDisplay];
    }
    [self setTouch:YES];
}

#pragma mark - KVO测试
- (void)setTouch:(BOOL)touch {
    
}

- (BOOL)touch {
    return self.isSelected;
}

3 简单的实现KVO封装

如上的代码,我是通过block直接捕捉KVO的调用,就是一个KVO的封装。KVO默认的代码实现起来有一些问题:
1:添加和监听回调代码是分开的,很零散
2:回调的方法都在一个方法中,要自己通过keyPath区别
3:要自己手动移除监听,如果忘记移除还会报错

这个是我自己通过runtime相关方法来实现的一个KVO封装,网上也有很多KVO的封装,但是都是要新建一个自定义对象数组,通过自定义对象来保存监听者、被监听者、回调block来实现的。我想了些办法,通过runtime给监听和被监听对象动态添加了对应的属性来保存这些信息,不需要新建自定义对象。

NSObject+JKR_Observer.h

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

typedef void (^changeBlock)(id newValue);

@interface NSObject (JKR_Observer)

- (void)jkr_addObserver:(NSObject *)object forKeyPath:(NSString *)keyPath change:(changeBlock)change;

@end

NSObject+JKR_Observer.m

#import "NSObject+JKR_Observer.h"

@interface NSObject ()

@property NSMutableDictionary<NSString *, changeBlock> *jkr_observer_blocks;
@property NSMutableArray<NSString *> *jkr_observer_keyPaths;
@property NSObject *jkr_observer_observerdObject;

@end

@implementation NSObject (JKR_Observer)

- (void)jkr_addObserver:(NSObject *)object forKeyPath:(NSString *)keyPath change:(changeBlock)change {
    NSLog(@"%@ 添加监听者 %@ 被监听的值:%@", self, object, keyPath);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        method_exchangeImplementations(class_getInstanceMethod([object class], NSSelectorFromString(@"dealloc")), class_getInstanceMethod([self class], @selector(jkr_dealloc)));
    });
    [self addObserver:object forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:(__bridge void * _Nullable)([NSString stringWithFormat:@"%zd", self.jkr_observer_blocks.count])];
    [self.jkr_observer_blocks setObject:change forKey:[NSString stringWithFormat:@"%zd", self.jkr_observer_blocks.count]];
    
    NSMutableArray *keyPaths = [object valueForKey:@"jkr_observer_keyPaths"];
    [keyPaths addObject:keyPath];
    object.jkr_observer_observerdObject = self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"监听者:%@ 监听到被监听者 %@ 的值 %@ 改变", self, object, keyPath);
    NSString *key = (__bridge NSString *)(context);
    NSMutableDictionary *dict = [object valueForKey:@"jkr_observer_blocks"];
    if (!dict) return;
    changeBlock block = [dict valueForKey:key];
    if (block) {
        id newValue = [change valueForKey:NSKeyValueChangeNewKey];
        block(newValue);
    }
}

- (void)setJkr_observer_blocks:(NSMutableDictionary<NSString *,changeBlock> *)jkr_observer_blocks {
    objc_setAssociatedObject(self, "jkr_observer_blocks", jkr_observer_blocks, OBJC_ASSOCIATION_RETAIN);
}

- (NSMutableDictionary *)jkr_observer_blocks {
    NSMutableDictionary *blocks = objc_getAssociatedObject(self, "jkr_observer_blocks");
    if (!blocks) {
        blocks = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, "jkr_observer_blocks", blocks, OBJC_ASSOCIATION_RETAIN);
    }
    return blocks;
}

- (void)setJkr_observer_keyPaths:(NSMutableArray<NSString *> *)jkr_observer_keyPaths {
    objc_setAssociatedObject(self, @"jkr_observer_keyPaths", jkr_observer_keyPaths, OBJC_ASSOCIATION_RETAIN);
}

- (NSMutableArray<NSString *> *)jkr_observer_keyPaths {
    NSMutableArray *keyPaths = objc_getAssociatedObject(self, @"jkr_observer_keyPaths");
    if (!keyPaths) {
        keyPaths = [NSMutableArray array];
        objc_setAssociatedObject(self, @"jkr_observer_keyPaths", keyPaths, OBJC_ASSOCIATION_RETAIN);
    }
    return keyPaths;
}

- (NSObject *)jkr_observer_observerdObject {
    return objc_getAssociatedObject(self, @"jkr_observer_observerdObject");
}

- (void)setJkr_observer_observerdObject:(NSObject *)jkr_observer_observerdObject {
    objc_setAssociatedObject(self, @"jkr_observer_observerdObject", jkr_observer_observerdObject, OBJC_ASSOCIATION_ASSIGN);
}

- (void)jkr_dealloc {
    NSMutableArray *paths = self.jkr_observer_keyPaths;
    if (paths.count) {
        [paths enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"被监听者 %@ 移除监听者 %@ 被移除的监听值 %@", self.jkr_observer_observerdObject, self, obj);
            [self.jkr_observer_observerdObject removeObserver:self forKeyPath:paths[idx]];
        }];
    }
    NSLog(@"dealloc");
    [self jkr_dealloc];
}

@end

Demo: https://github.com/Joker-388/JKRKVODemo

获取授权

上一篇下一篇

猜你喜欢

热点阅读