目录
1. 原理
2. 使用
3. 自己实现KVO
4. 第三方框架KVOController
KVO(key Value Observing) 键值观察
一个对象监听另一个对象指定属性的变化。
KVO和NSNotificationCenter同属于观察者模式。
KVO对监听对象无侵入性(不修改对象内部代码)。
系统自带KVO的缺点:
1、很容易导致崩溃(见下面的例子中的说明)。
2、keyPath是字符串,不进行合法性检查 且 修改属性明后要全局搜索并修改。
3、不支持block语法,多个被观察者时需要在observeValueForKeyPath做很多判断
- KVO的原理
在运行时会创建一个中间类(继承原类),并动态修改当前对象的isa指向中间类(即动态修改了对象的类型),重写class方法(返回原类的Class,是其更像没有变动过一样)。
分析原理
第一步:创建自定义类
@interface PersonM : NSObject
@property (nonatomic,copy) NSString *name;
@end
@implementation PersonM
- (NSString *)description {
NSLog(@"object address : %p \n", self);
IMP nameIMP = class_getMethodImplementation(object_getClass(self), @selector(setName:));
NSLog(@"object setName: IMP %p object", nameIMP);
Class objectMethodClass = [self class];
Class objectRuntimeClass = object_getClass(self);
Class superClass = class_getSuperclass(objectRuntimeClass);
NSLog(@"objectMethodClass : %@, ObjectRuntimeClass : %@, superClass : %@ \n", objectMethodClass, objectRuntimeClass, superClass);
NSLog(@"object method list \n");
unsigned int count;
Method *methodList = class_copyMethodList(objectRuntimeClass, &count);
for (NSInteger i = 0; i < count; i++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
NSLog(@"method Name = %@\n", methodName);
}
return @"";
}
@end
第二步:VC中+
PersonM *personM=[PersonM new];
PersonM *personM2=[PersonM new];
[personM description];
[personM2 description];
[personM addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
[personM description];
[personM2 description];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
第三步:查看打印信息
第一次打印
object address : 0x6000026ba740
object setName: IMP 0x10fad55f0 object
objectMethodClass : PersonM, ObjectRuntimeClass : PersonM, superClass : NSObject
object method list
method Name = .cxx_destruct
method Name = description
method Name = name
method Name = setName:
object address : 0x6000026ba6e0
object setName: IMP 0x10fad55f0 object
objectMethodClass : PersonM, ObjectRuntimeClass : PersonM, superClass : NSObject
object method list
method Name = .cxx_destruct
method Name = description
method Name = name
method Name = setName:
第二次打印
object address : 0x6000026ba740
object setName: IMP 0x10fef590a object
objectMethodClass : PersonM, ObjectRuntimeClass : NSKVONotifying_PersonM, superClass : PersonM
object method list
method Name = setName:
method Name = class
method Name = dealloc
method Name = _isKVOA
object address : 0x6000026ba6e0
object setName: IMP 0x10fad55f0 object
objectMethodClass : PersonM, ObjectRuntimeClass : PersonM, superClass : NSObject
object method list
method Name = .cxx_destruct
method Name = description
method Name = name
method Name = setName:
从打印中可以看出:
KVO在运行时会根据原类创建一个中间类(继承原类,NSKVONotifying_PersonM类型)
动态修改当前对象的isa指向中间类
对KVO属性name覆写setName方法(调用willChangeValueForKey->改变值->didChangeValueForKey),未KVO则不覆写。
_isKVOA标识KVO
- 使用
PersonModel *personM=[PersonModel new];
第一步:注册观察者
// 给personM添加观察者self,观察name属性的变化
[personM addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
/*
options :会观察到并传入observeValueForKeyPath方法
NSKeyValueObservingOptionOld 旧值
NSKeyValueObservingOptionNew 新值(默认仅接受新值)
NSKeyValueObservingOptionInitial 注册观察者后立即调用一次回调
NSKeyValueObservingOptionPrior 分2次调用(在值改变之前和值改变之后)
context:任意对象,observeValueForKeyPath方法中可获取
*/
第二步:在观察者中实现observeValueForKeyPath,没有实现则属性改变后会崩溃
// 观察方法(当所观察的东西发生变化时调用)
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
//NSKeyValueChangeNewKey NSKeyValueChangeOldKey
}
第三步:不需要监听时移除观察者
// 需要在观察者消失前即观察者不能为nil,否则崩溃。因为KVO未对观察者强引用
// 必须和addObserver成对出现,忘记remove则属性改变后会崩溃
// 多次remove会导致崩溃
// delloc中移除观察者
[personM removeObserver:self forKeyPath:@"name"];
手动调用
默认 set方法、kvc设值 后会自动调用observeValueForKeyPath
// 手动调用
-(void)setName:(NSString *)name{
if(![_name isEqualToString: name]){
[self willChangeValueForKey:@"name"];
_name=name;
[self didChangeValueForKey:@"name"];
}
}
// true则自动调用,false则手动调用
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"name"]) {
automatic = NO;
} else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
- 自己实现KVO
注意:只用于了解原理,不能在项目中使用,尚存在许多问题。
1、创建NSObject+KVOBlock文件和KVOObserverItem文件
2、使用
添加观察者
[personM addObserver:self originalSelector:@selector(name) callback:^(id _Nonnull observedObject, NSString * _Nonnull observedKey, id _Nonnull oldValue, id _Nonnull newValue) {
NSLog(@"====================");
}];
移除观察者
[personM removeObserver:self originalSelector:@selector(name)];
NSObject+KVOBlock.h
#import <Foundation/Foundation.h>
#import "KVOObserverItem.h"
@interface NSObject (KVOBlock)
/**
添加观察者
@param observer 观察者
@param originalSelector 用于获取被观察者的属性
@param callback 属性发生变化后的回调
*/
- (void)addObserver:(NSObject *)observer
originalSelector:(SEL)originalSelector
callback:(KVOObserverBlock)callback;
/**
移除观察者
@param observer 观察者
@param originalSelector 用于获取被观察者的属性
*/
- (void)removeObserver:(NSObject *)observer
originalSelector:(SEL)originalSelector;
@end
NSObject+KVOBlock.m
#import "NSObject+KVOBlock.h"
#import <objc/runtime.h>
#import <objc/message.h>
static void *const KVOObserverAssociatedKey = (void *)&KVOObserverAssociatedKey;
static NSString *KVOClassPrefix = @"my_KVONotifying_";
@implementation NSObject (KVOBlock)
- (void)addObserver:(NSObject *)observer
originalSelector:(SEL)originalSelector
callback:(KVOObserverBlock)callback {
// 1.判断是否有这个key对应的selector,如果没有则Crash。在编译时异常及早发现,避免在运行时异常。
SEL originalSetter = NSSelectorFromString(setterForGetter(originalSelector));
Method originalMethod = class_getInstanceMethod(object_getClass(self), originalSetter);
if (!originalMethod) {
NSString *exceptionReason = [NSString stringWithFormat:@"%@ Class %@ setter SEL not found.", NSStringFromClass([self class]), NSStringFromSelector(originalSelector)];
NSException *exception = [NSException exceptionWithName:@"NotExistKeyExceptionName" reason:exceptionReason userInfo:nil];
[exception raise];
}
// 2.判断当前类是否是KVO子类,如果不是则创建,并设置其isa指针
Class kvoClass = object_getClass(self);
NSString *kvoClassString = NSStringFromClass(kvoClass);
if (![kvoClassString hasPrefix:KVOClassPrefix]) {
kvoClass = [self makeKVOClassWithName:kvoClassString];
object_setClass(self, kvoClass);
}
// 3.如果没有实现setter方法则添加。
if (![self hasMethodWithKey:originalSetter]) {
class_addMethod(kvoClass, originalSetter, (IMP)kvoSetter, method_getTypeEncoding(originalMethod));
}
// 4.创建观察者对象,并添加到观察者数组中,并动态设置为属性。
KVOObserverItem *observerItem = [[KVOObserverItem alloc] initWithObserver:observer key:NSStringFromSelector(originalSelector) block:callback];
NSMutableArray<KVOObserverItem *> *observers = objc_getAssociatedObject(self, KVOObserverAssociatedKey);
if (observers == nil) {
observers = [NSMutableArray array];
}
[observers addObject:observerItem];
objc_setAssociatedObject(self, KVOObserverAssociatedKey, observers, OBJC_ASSOCIATION_RETAIN);
}
- (void)removeObserver:(NSObject *)observer
originalSelector:(SEL)originalSelector {
// 获取观察者数组。并移除指定观察者
NSMutableArray <KVOObserverItem *>* observers = objc_getAssociatedObject(self, KVOObserverAssociatedKey);
[observers enumerateObjectsUsingBlock:^(KVOObserverItem * _Nonnull mapTable, NSUInteger idx, BOOL * _Nonnull stop) {
SEL selector = NSSelectorFromString(mapTable.key);
if (mapTable.observer == observer && selector == originalSelector) {
[observers removeObject:mapTable];
}
}];
}
#pragma mark 私有Method、Funcation
/**
重写set方法实现
@param self <#self description#>
@param selector <#selector description#>
@param value <#value description#>
*/
static void kvoSetter(id self, SEL selector, id value) {
// 1.
id (*getterMsgSend) (id, SEL) = (void *)objc_msgSend;
NSString *getterString = getterForSetter(selector);
SEL getterSelector = NSSelectorFromString(getterString);
id oldValue = getterMsgSend(self, getterSelector);
// 2.创建super的结构体,并向super发送属性的消息
id (*msgSendSuper) (void *, SEL, id) = (void *)objc_msgSendSuper;
struct objc_super objcSuper = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
msgSendSuper(&objcSuper, selector, value);
// 3.循环触发观察者的回调
NSMutableArray <KVOObserverItem *>* observers = objc_getAssociatedObject(self, KVOObserverAssociatedKey);
[observers enumerateObjectsUsingBlock:^(KVOObserverItem * _Nonnull mapTable, NSUInteger idx, BOOL * _Nonnull stop) {
if ([mapTable.key isEqualToString:getterString] && mapTable.block) {
mapTable.block(self, NSStringFromSelector(selector), oldValue, value);
}
}];
}
/**
是否拥有指定方法
@param key 方法SEL
@return 是否拥有
*/
- (BOOL)hasMethodWithKey:(SEL)key {
NSString *setterName = NSStringFromSelector(key);
unsigned int count;
Method *methodList = class_copyMethodList(object_getClass(self), &count);
for (NSInteger i = 0; i < count; i++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
if ([methodName isEqualToString:setterName]) {
return YES;
}
}
return NO;
}
/**
用于获取属性名(setName->name)
@param setter set方法SEL
@return 属性名
*/
static NSString * getterForSetter(SEL setter) {
NSString *setterString = NSStringFromSelector(setter);
if (![setterString hasPrefix:@"set"]) {
return nil;
}
NSString *getterString = [setterString substringWithRange:NSMakeRange(4, setterString.length - 5)];
NSString *firstString = [setterString substringWithRange:NSMakeRange(3, 1)];
firstString = [firstString lowercaseString];
getterString = [NSString stringWithFormat:@"%@%@", firstString, getterString];
return getterString;
}
/**
用于获取set方法名(name->setName)
@param getter 属性名
@return set方法名
*/
static NSString * setterForGetter(SEL getter) {
NSString *getterString = NSStringFromSelector(getter);
NSString *firstString = [getterString substringToIndex:1];
firstString = [firstString uppercaseString];
NSString *setterString = [getterString substringFromIndex:1];
setterString = [NSString stringWithFormat:@"set%@%@:", firstString, setterString];
return setterString;
}
/**
判断是否存在KVO类
如果存在则返回;
如果不存在,则创建KVO类,重写KVO类的class方法,指向父类。
@param name 原类名
@return KVO类
*/
- (Class)makeKVOClassWithName:(NSString *)name {
//
NSString *className = [NSString stringWithFormat:@"%@%@", KVOClassPrefix, name];
Class kvoClass = objc_getClass(className.UTF8String);
if (kvoClass) {
return kvoClass;
}
//
kvoClass = objc_allocateClassPair(object_getClass(self), className.UTF8String, 0);
objc_registerClassPair(kvoClass);
//
const char * types = NSStringFromSelector(@selector(class)).UTF8String;
class_addMethod(kvoClass, @selector(class), (IMP)super_kvoClass, types);
return kvoClass;
}
/**
获取父类
@param self <#self description#>
@param selector <#selector description#>
@return <#return value description#>
*/
static Class super_kvoClass(id self, SEL selector) {
return class_getSuperclass(object_getClass(self));
}
@end
KVOObserverItem.h
#import <Foundation/Foundation.h>
// 属性发生变化后回调
typedef void (^KVOObserverBlock) (id observedObject, NSString *observedKey, id oldValue, id newValue);
@interface KVOObserverItem : NSObject
// 观察者
@property (nonatomic, weak) NSObject *observer;
// 观察者要监测的属性
@property (nonatomic, copy) NSString *key;
// 属性发生变化后回调
@property (nonatomic, copy) KVOObserverBlock block;
/**
初始化观察者
@param observer 观察者
@param key 观察者要监测的属性
@param block 属性发生变化后回调
@return 实例
*/
- (instancetype)initWithObserver:(NSObject *)observer
key:(NSString *)key
block:(KVOObserverBlock)block;
@end
KVOObserverItem.m
#import "KVOObserverItem.h"
@implementation KVOObserverItem
- (instancetype)initWithObserver:(NSObject *)observer
key:(NSString *)key
block:(KVOObserverBlock)block {
self = [super init];
if (self) {
self.observer = observer;
self.key = key;
self.block = block;
}
return self;
}
@end
- 第三方框架KVOController
pod 'KVOController'
#import <KVOController/KVOController.h>
PersonM *personM=[PersonM new];
// 创建FBKVOController
FBKVOController *kvo=[FBKVOController controllerWithObserver:self];
添加被观察者 属性
// block
[kvo observe:personM keyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
// change[NSKeyValueChangeNewKey]
// change[NSKeyValueChangeOldKey]
}];
// selector
[kvo observe:personM keyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld action:@selector(handleChangeName:)];
// 一组值
[kvo observe:personM keyPaths:@[@"name",@"age"] options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
}];
[kvo observe:personM keyPaths:@[@"name",@"age"] options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld action:@selector(handleChange:)];
移除被观察者
// 移除指定对象
[kvo unobserve:personM];
// 移除指定对象的指定属性
[kvo unobserve:personM keyPath:@"name"];
// 移除全部
[kvo unobserveAll];
网友评论