iOS学习笔记CoderHG.iOS程序员

KVO 的进一步理解

2018-05-13  本文已影响23人  CoderHG

这是一篇简单又丰富的简书,周末愉快!

一、KVO概要及简单使用

KVO 就是一种监听,那是如何做到监听的呢?首先创建一个简单的 Class,代码如下:

#import <Foundation/Foundation.h>

@interface KVOObject : NSObject

// 姓名
@property (nonatomic, copy) NSString* name;

@end



#import "KVOObject.h"

@implementation KVOObject

@end

很简单, 就一个 Class,然后定义了一个 name 属性而已。

一个简单的试验如下:

// 创建一个 KVO 对象
KVOObject* kvObj = [[KVOObject alloc] init];
kvObj.name = @"HG";

// 添加 KVO 监听
[kvObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];

kvObj.name = @"CoderHG";

// 移除 KVO 监听, 在 iOS 10之前不移除的话直接 crash, 之后的就没事了
[kvObj removeObserver:self forKeyPath:@"name"];

具体的监听方法代码如下:

// KVO 的系统监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@, %@, %@", keyPath, object, change);
}

以上就是一个简单而完整的 KVO 的使用场景,但是具体是什么原理呢?

二、KVO 的原理初步认识

都知道一个对象一旦添加了 KVO 监听,在本质上是系统动态的改变了该对象的 isa 指针。如果对 isa 不了解的话,可以看这个 OC 小专题
想要知道 isa 有什么样的变动,先实现如下一个方法:

// 打印具体的 cls 中的方法信息
- (NSString*)printMethodNamesOfClass:(Class)cls {
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[i];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    
    // 释放
    free(methodList);
    
    // 返回类名与方法列表
    return [NSString stringWithFormat:@"类名: %@ \n方法列表: %@", NSStringFromClass(cls), methodNames];
}

然后将以上的试验修改一下,如下:

// 常规用法
- (void)convention {
    // 创建一个 KVO 对象
    KVOObject* kvObj = [[KVOObject alloc] init];
    kvObj.name = @"HG";
    
    Class cls = object_getClass(kvObj);
    NSString* isaInfo = [self printMethodNamesOfClass:cls];
    
    NSLog(@"\n\n添加 KVO 之前:\n%@", isaInfo);
    
    
    // 添加 KVO 监听
    [kvObj addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
    
    kvObj.name = @"CoderHG";
    
    cls = object_getClass(kvObj);
    isaInfo = [self printMethodNamesOfClass:cls];
    NSLog(@"\n\n添加 KVO 之后:\n%@", isaInfo);
    
    // 移除 KVO 监听, 在 iOS 10之前不移除的话直接 crash, 之后的就没事了
    [kvObj removeObserver:self forKeyPath:@"name"];
}

日志有如下的打印:

添加 KVO 之前:
类名: KVOObject 
方法列表: .cxx_destruct, name, setName:

添加 KVO 之后:
类名: NSKVONotifying_KVOObject 
方法列表: setName:, class, dealloc, _isKVOA

说明在添加 KVO 监听之后,isa 指针的值确实是变了,具体变化为:

三、KVO 与 KVC 的那一份藕断丝连

关于 KVC,强烈建议看一下这篇文章KVC 的原理概述,接下来将会在这篇文章的基础上做介绍。如果不看的话,可能你很难理解我所说的 非常规 KVC 调用是什么意思。虽然,我在这里仅仅是用到了那么一丁点的内容。
首先创建一个 Class, 代码如下:

#import <Foundation/Foundation.h>

@interface KVO8KVCObject : NSObject

@end


#import "KVO8KVCObject.h"

@interface KVO8KVCObject ()
{
    // 非常规试验
    NSString* isGoddess;
}

@end

@implementation KVO8KVCObject

@end

那接下来,我们想要表达一个什么问题呢?

KVC 能否触发 KVO 监听?

看了上面的 KVO8KVCObject 定义,我即将使用一个 KVC 的非常规调用来介绍,具体代码如下:

// KVO 与 KVC 那一段藕断丝连的区域
- (void)kvo8kvc {
   // 创建一个 KVO8KVC 对象
   KVO8KVCObject* kvO_CObj = [[KVO8KVCObject alloc] init];
   
   // 通过 KVC 赋值
   [kvO_CObj setValue:@"KJ" forKey:@"goddess"];
   
   Class cls = object_getClass(kvO_CObj);
   NSString* isaInfo = [self printMethodNamesOfClass:cls];
   
   NSLog(@"\n\n添加 KVO 之前:\n%@", isaInfo);
   
   
   // 添加 KVO 监听
   [kvO_CObj addObserver:self forKeyPath:@"goddess" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
   
   // 通过 KVC 赋值
   [kvO_CObj setValue:@"JK" forKey:@"goddess"];
   
   cls = object_getClass(kvO_CObj);
   isaInfo = [self printMethodNamesOfClass:cls];
   NSLog(@"\n\n添加 KVO 之后:\n%@", isaInfo);
   
   // 移除 KVO 监听, 在 iOS 10之前不移除的话直接 crash, 之后的就没事了
   [kvO_CObj removeObserver:self forKeyPath:@"goddess"];
}

看一下具体的 Log 打印,如下:

添加 KVO 之前:
类名: KVO8KVCObject 
方法列表: .cxx_destruct

goddess, <KVO8KVCObject: 0x60c00001b080>, {
    kind = 1;
    new = JK;
    old = KJ;
}

添加 KVO 之后:
类名: NSKVONotifying_KVO8KVCObject 
方法列表: class, dealloc, _isKVOA

可以得出结论:
KVC 能触发 KVO 监听。

看到这里,也推翻了之前的一个结论:KVO 的正常触发的入口是 setter 方法,其实不是这样的,就如同上面的这个实验,在 NSKVONotifying_KVO8KVCObject 与 KVO8KVCObject 中根本就没有其对应的 setter 方法。

本篇介绍,到这里就要告一段落了。在写本简书的时候,我有一个试验 Demo # OC2Nature,可以作为一个参考。具体请看 KVO 目录。
同时别忘了看看本专题的其它文章 OC 小专题


在之前也写过关于 KVO 的简书,虽然那时候理解还不是太深入,但是里面依旧是有新东西的。感兴趣的话可以去看看:

上一篇下一篇

猜你喜欢

热点阅读