在YYKit的NSObject+YYAddForKVO
文件中以Block
的形式包装了普通的KVO方法,觉得很是巧妙,由此引发了对KVO原理的探索。
1.NSObject+YYAddForKVO的代码实现
@interface _KVOWithBlockTarget_ : NSObject
@property(nonatomic, copy) void (^block)(__weak id obj, __weak id oldVal, __weak id newVal);
- (instancetype)initWithBlock:(void (^)(__weak id obj, __weak id oldVal, __weak id newVal))block;
@end
@implementation _KVOWithBlockTarget_
- (instancetype)initWithBlock:(void (^)(__weak id, __weak id, __weak id))block {
if (self = [super init]) {
self.block = block;
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (!self.block) return;
BOOL isPrior = [[change objectForKey: NSKeyValueChangeNotificationIsPriorKey] boolValue];
if (isPrior) return;
NSKeyValueChange changeKind = [[change objectForKey: NSKeyValueChangeKindKey] integerValue];
if (changeKind != NSKeyValueChangeSetting) return;
id oldVal = [change objectForKey: NSKeyValueChangeOldKey];
if (oldVal == [NSNull null]) oldVal = nil;
id newVal = [change objectForKey: NSKeyValueChangeNewKey];
if (newVal == [NSNull null]) newVal = nil;
self.block(object, oldVal, newVal);
}
@end
static const int block_key;
@implementation NSObject (KVOWithBlock)
// 添加观察block
- (void)addObserverForKeyPath:(NSString *)keyPath block:(void (^)(id, id, id))block {
_KVOWithBlockTarget_ *target = [[_KVOWithBlockTarget_ alloc] init];
NSMutableDictionary *dic = [self _allNSObjectObserverBlocks];
NSMutableArray *arr = dic[keyPath];
if (!arr) {
arr = [NSMutableArray new];
dic[keyPath] = arr;
}
[arr addObject:target];
[self addObserver: target forKeyPath: keyPath options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context: NULL];
}
// 移除该对象某个keyPath对应的Blocks
- (void)removeObserverBlocksForkeyPath: (NSString *)keyPath {
if (!keyPath) return;
NSMutableDictionary *dic = [self _allNSObjectObserverBlocks];
NSMutableArray *arr = dic[keyPath];
if (!arr) return;
[arr enumerateObjectsUsingBlock:^(id target, NSUInteger index, BOOL *stop) {
[self removeObserver:target forKeyPath:keyPath];
}];
[dic removeObjectForKey:keyPath];
}
// 移除该对象所有Blocks
- (void)removeAllObserverBlocks {
NSMutableDictionary *dic = [self _allNSObjectObserverBlocks];
[dic enumerateKeysAndObjectsUsingBlock:^(NSString *keyPath, NSArray *arr, BOOL *stop) {
[arr enumerateObjectsUsingBlock:^(id target, NSUInteger index, BOOL *stop) {
[self removeObserver:target forKeyPath:keyPath];
}];
}];
[dic removeAllObjects];
}
// 管理观察集合 [keyPath: [target]...] (多个keyPath,每个keyPath多个观察者)
- (NSMutableDictionary *) _allNSObjectObserverBlocks {
NSMutableDictionary *targets = objc_getAssociatedObject(self, &block_key);
if (!targets) {
targets = [NSMutableDictionary new];
objc_setAssociatedObject(self, &block_key, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return targets;
}
@end
分析:自定义_KVOWithBlockTarget_
对象作为observer
,然后自身通过数组字典来管理这些observer
,包装非常巧妙,并未触及KVO的底层原理。
2.KVO原理分析
-
KVO
是基于runtime机制
实现的,当某个类的属性对象第一次被观察
时,系统就会在运行期动态
地创建该类的一个派生类(子类)
,A类
的派生类名为NSKVONotifying_A
。后面可能会应用到的方法objc_allocateClassPair
和objc_registerClassPair
- 每个类对象中都一个
isa指针
指向当前类,当一个类对象的属性第一次被观察时,那么系统会将isa指针
指向动态生成的派生类
,进而在赋值时执行的是派生类
的setter
方法;而此时苹果为了隐藏该派生类,也重写了class
方法,所以在执行[ojb class]
方法时返回的依旧是原来的类。 -
KVO
的观察通知则依赖于NSObject的两个方法willChangeValueForKey:
和didChangevlueForKey:
;在一个被观察属性发生改变之前,willChangeValueForKey:
会被调用,这就记录旧值;而当发生改变之后,didChangevlueForKey:
会被调用,继而observeValueForKey:ofObject:change:context:
也会被调用。所以可以预测伪码如下
- (void)setName:(id)name {
// 记录旧值
[self willChangeValueForKey:];
[super setValue: name forKey: @"name"];
// 记录新值
[self didChangevlueForKey:];
}
- 以上,如果我们想手动触发KVO,可通过手动调用上面描述的两个方法
// “手动触发self.name的KVO”,必写。
[self willChangeValueForKey:@"name"];
// “手动触发self.name的KVO”,必写。
[self didChangeValueForKey:@"name"];
-
原理图如下
3.自定义实现简单的KVO机制
与NSObject+YYAddForKVO
不同,这里我们完全自定义实现KVO机制,具体步骤如下
- 创建派生类,并仿照苹果隐藏派生类(重写class方法)
- 重写派生类的setter方法
- 类似YYAddForKVO,创建observer类来传递block(下面代码也是包装Block实现KVO)
#import "NSObject+KVO.h"
#import <objc/message.h>
NSString *const kYYKVOClassPrefix = @"YYKVOClassPrefix_";
NSString *const kYYKVOObservers = @"YYKVOObservers";
typedef void(^YYKVOBlock)(__weak id observer, NSString *observerdKey, __weak id oldVal, __weak id newVal) ;
// observer类
@interface YYObserveationTarget: NSObject
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) YYKVOBlock block;
- (instancetype)initWithObserverForKey: (NSString *)key block: (YYKVOBlock)block;
@end
@implementation YYObserveationTarget
- (instancetype)initWithObserverForKey: (NSString *)key block: (YYKVOBlock)block {
if (self = [super init]) {
_key = key;
_block = block;
}
return self;
}
@end
static NSString * setterForGetter(NSString *getter) {
if (getter.length <= 0) return nil;
// 首字母大写
NSString *firstLetter = [[getter substringToIndex:1] uppercaseString];
NSString * remainLetters = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstLetter,remainLetters];
}
static NSString * getterForSetter(NSString *setter) {
if (setter.length <= 0 || ![setter hasPrefix:@"set"]) return nil;
NSRange range = NSMakeRange(3, setter.length - 4);
NSString *key = [setter substringWithRange:range];
// 首字母小写
NSString *firstLetter = [[key substringWithRange:NSMakeRange(0, 1)] lowercaseString];
key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstLetter];
return key;
}
static Class kvo_class(id self, SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}
static void kvo_setter(id self, SEL _cmd, id newVal) {
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = getterForSetter(setterName);
if (!getterName) return;
id oldValue = [self valueForKey:getterName];
struct objc_super superClazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 防止编译器多参报错
void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
// 调用原类的setter方法(容易忘记)
objc_msgSendSuperCasted(&superClazz, _cmd, newVal);
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kYYKVOObservers));
// Observer类来管理block的执行
for (YYObserveationTarget *each in observers) {
if ([each.key isEqualToString: getterName]) {
// 在一个全局队列中执行block回调
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
each.block(self, getterName, oldValue, newVal);
});
}
}
}
@implementation NSObject (KVO)
- (void)yy_addObserverForKey: (NSString *)key withBlock: (YYKVOBlock)block {
// 0.key对应的setter等信息
SEL setterSelector = NSSelectorFromString(setterForGetter(key));
Method setterMethod = class_getInstanceMethod([self class], setterSelector);
// 1. 判断是否为派生类,如果不是则创建
Class cls = object_getClass(self);
NSString *clsName = NSStringFromClass(cls);
// 1.1如果不包含前缀,说明不是派生类
if (![clsName hasPrefix:kYYKVOClassPrefix]) {
// 1.2 创建派生类
cls = [self makeKVOClass];
// 1.3 将isa指针指向派生类
object_setClass(self, cls);
}
// 2.重写派生类的setter方法,判断是否已经重写过了
if (![self hasSelector:setterSelector]) {
const char *types = method_getTypeEncoding(setterMethod);
class_addMethod(cls, setterSelector, (IMP)kvo_setter, types);
}
// 3.管理observers
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)kYYKVOObservers);
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void *)kYYKVOObservers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
YYObserveationTarget *observer = [[YYObserveationTarget alloc]initWithObserverForKey: key block:block];
[observers addObject: observer];
}
- (void)yy_removeObserverForKey: (NSString *)key {
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void*)kYYKVOObservers);
NSMutableArray<YYObserveationTarget *> *infoToRemoveAry = [NSMutableArray array];
for (YYObserveationTarget *observer in observers) {
if ([observer.key isEqualToString:key]) {
[infoToRemoveAry addObject:observer];
}
}
[observers removeObjectsInArray:infoToRemoveAry];
}
- (Class)makeKVOClass {
// 1.派生类类名
NSString *kvoClsName = [kYYKVOClassPrefix stringByAppendingString:NSStringFromClass([self class])];
Class kvoCls = NSClassFromString(kvoClsName);
// 如果该类已经存在,则直接返回
if (kvoCls) return kvoCls;
Class originalCls = object_getClass(self);
// 2. 动态创建该派生类,派生类继承自原类
kvoCls = objc_allocateClassPair(originalCls, kvoClsName.UTF8String, 0);
// 2.1 仿照苹果做法,重写class方法,隐藏该子类
Method clsMethod = class_getInstanceMethod(originalCls, @selector(class));
const char *types = method_getTypeEncoding(clsMethod);
// kvo_class 返回父类类名,即返回原类,从而达到隐藏的效果
class_addMethod(kvoCls, @selector(class), (IMP)kvo_class, types);
// 2.2成对使用动态创建派生类
objc_registerClassPair(kvoCls);
return kvoCls;
}
- (BOOL)hasSelector: (SEL)selector {
// 这里因为对象将isa指针指向了派生类,所以返回的应该是kvoCls
Class cls = object_getClass(self);
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(cls, &methodCount);
for (unsigned int i = 0 ; i < methodCount; i++) {
SEL aSelector = method_getName(methodList[i]);
if (aSelector == selector) {
free(methodList);
return YES;
}
}
free(methodList);
return NO;
}
@end
可以看到代码中大量应用了runtime
知识,因而熟悉runtime
对我们学习iOS原理有很大的帮助。
4.Swift4中KVO的使用方式
- 只有继承自NSObject类才能使用KVO
- 在Swift4中需要标记
@objcMembers
和dynamic
才能使用KVO - 不在需要手动
移除observer
, 而是返回一个NSKeyValueObservation
闭包,这带来了一个新的陷阱,我们需要主动去控制这个闭包的生命周期
;如果没有对其强引用,该函数结束后闭包就会被回收。
@objcMembers class KVOClass: NSObject {
dynamic var name: String
init(name: String) {
self.name = name
}
}
class ViewController: UIViewController {
var aClass: KVOClass!
var ob: NSKeyValueObservation!
override func viewDidLoad() {
super.viewDidLoad()
aClass = KVOClass(name: "kvo")
ob = aClass.observe(\.name) { (ob, changed) in
let new = ob.name
print(new)
}
aClass.name = "swift4"
}
}
以上是对KVO的一点小结,主要参考了以下的几篇文章
探究KVO的底层实现原理
如何自己动手实现 KVO
Swift 4新知:KVC和KVO新姿势