kvo :
1).观察这模式的一种体现
;
2).然后原理是,通过isa 混写技术
实现了 把原实例对象的isa (比如指向OriginClass)
指向了新的子类(NSKVONotifing_OriginClass)
3).通过 重写setValue:
方法 通过在 内部 实现willChargeForValue:(就是监听的老值)
和didChargeForValue: (监听新值)
1. 普通的 kvo监听
实现 self
对 test
对象的监听
- (void)kvoTest {
Test * test = [Test new];
[test addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
test.value = 2;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
NSLog(@"keyPath = %@;change = %@",keyPath,change);
}
被监听的对象的isa 指针 ,已经指向了 NSKVONotifing_Test
2. 第三方的库KVOController
- (void)fbKVO {
Test * test = [Test new];
self.kvo = [[FBKVOController alloc]initWithObserver:self];
[self.kvo observe:test keyPath:@"value" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"observer:%@",observer);
NSLog(@"object = %@",object);
NSLog(@"change = %@",change);
}];
test.value = 2;
}
解析fbkvo
FBKVO 其实是封装了系统的KVO;
主要有 被观察者 对象object
, 观察者的信息_FBKVOInfo
;
FBKVOInfo:
属性1:FBKVOController => 封装了一个 观察者(弱引用)
属性2:_keyPath (观察者的keyPath)
属性3:NSKeyValueObservingOptions (新旧值)
属性4:FBKVONotificationBlock (回调值 (id observer, id object, NSDictionary *change))
添加 监听 是通过 单例 [_FBKVOSharedController sharedController]
把我们的 object
和 FBKVOInfo
添加进来[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
if (nil == info) {
return;
}
// register info
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// add observer, 单例 作为系统的观察者, 把info 传入context,后面监听值的时候 可以方便的取值
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
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];
}
}
在 单例[_FBKVOSharedController sharedController]
获取监听
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSString *, 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<NSString *, 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];
}
}
}
}
}
3. 自定义KVO
主要是 在监听某个
object
的时候,去让 object的isa
指针 指向子类
,并且在子类
中 添加 当前的keyPath
的setter
方法。
这样外界 调用了这个setter
方法,就会先进入子类
的setter
方法,这样我们就可以 自定义操作了。
#import "NSObject+KVOBlock.h"
#import <objc/message.h>
static char * __KVOListKey = "__KVOListKey";
@implementation NSObject (KVOBlock)
- (void)observerkeyPath:(NSString *)keyPath
block:(KVOBlock)block {
[self registerSubClassWithKeyPath:keyPath];
if (block && keyPath) {
self.map[keyPath] = [block copy];
}
}
// 管理 keyPath 和 block 回调
- (NSMutableDictionary<NSString * , id > *)map {
NSMutableDictionary * dic = objc_getAssociatedObject(self, &__KVOListKey);
if (dic == nil) {
dic = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &__KVOListKey, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return dic;
}
// 注册类对,就是子类
- (void)registerSubClassWithKeyPath:(NSString *)keyPath {
//找到源类中的 setterMethod
SEL setterSelector = NSSelectorFromString([self setterFromGetter:keyPath]);
Method setterMethod = class_getInstanceMethod([self class], setterSelector);
if (setterMethod == nil) {
return;
}
// 注册 self的 isa 指针为 【self class】的子类 subClass
NSString * subClassName = [NSString stringWithFormat:@"__KVONotifying__%@", NSStringFromClass([self class])];
Class subClass = objc_getClass(subClassName.UTF8String);
if (subClass == nil) {
// 传入父类 ,子类的名称
subClass = objc_allocateClassPair([self class], subClassName.UTF8String, 0);
}
objc_registerClassPair(subClass);
//isa 指向 subClass
object_setClass(self, subClass);
//给 子类 subClass 添加setter方法,这样 只要外界调用setter方法 就好调用 子类的setter方法
const char * types = method_getTypeEncoding(setterMethod);
class_addMethod(subClass, setterSelector, (IMP)kvo_setter, types);
}
// 获取 setter 方法
- (NSString *)setterFromGetter:(NSString *)keyPath {
if (keyPath.length == 0) {
return nil;
}
NSString *setter = [[[keyPath substringToIndex:1] uppercaseString] stringByAppendingString:[keyPath substringFromIndex:1]];
setter = [NSString stringWithFormat:@"set%@:", setter];
return setter;
}
// 获取getter方法
- (NSString *)getterFromSetter:(NSString *)setter {
if (setter.length == 0 && [setter hasPrefix:@"set"]) {
return nil;
}
NSString * getter = [setter substringFromIndex:3];
getter = [NSString stringWithFormat:@"%@%@",[getter substringToIndex:1].lowercaseString,[getter substringFromIndex:1]];
if ([getter hasSuffix:@":"]) {
getter = [getter substringToIndex:getter.length-1];
}
return getter;
}
// 监听setter 方法
static void kvo_setter(id self, SEL _cmd, id newValue) {
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = [self getterFromSetter:setterName];
if (getterName == nil) {
return;
}
id oldValue = [self valueForKey:getterName];
// https://tech.glowing.com/cn/implement-kvo/
struct objc_super superclazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
void (* objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
// 当的【self class】 其实就是subClass, 所以它的superClass,就是源类
objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
NSMutableDictionary *change = [NSMutableDictionary dictionary];
change[@"keyPath"] = getterName;
if (newValue) {
change[NSKeyValueChangeNewKey] = newValue;
}
if (oldValue) {
change[NSKeyValueChangeOldKey] = oldValue;
}
NSDictionary * dic = [self map];
KVOBlock block = dic[getterName];
if (block) {
block(change);
}
}
@end
例子
Test * test = [Test new];
// self.test = test;
[test observerkeyPath:@"num" block:^(NSDictionary *change) {
NSLog(@"num = %@",change);
}];
test.num = @(10);
网友评论