iOS基础全面分析之一(KVC全面分析)
iOS基础全面分析之二(RunLoop全面分析)
iOS基础全面分析之三(KVO全面分析)
KVO全面分析
简介
KVO的全程是 Key-Value Observing,翻译过来就是键值监听,可以用于监听某个对象属性值的改变
API
相关的API在NSKeyValueObserving.h文件中都有声明
常用的API
主要是下面几种方法,这是KVO的基本组成部分
基本使用API
//基本使用
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; //添加观察者
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0)); //删除观察者
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; //删除观察者
观察者的回调
//观察者的回调
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
KVO属性依赖关系
//观察者依赖关系 需要对象中重写
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); //全部属性的依赖关系建立
+ (NSSet<NSString *> *)keyPathsForValuesAffecting<#DependentKey#> //单独属性的依赖关系建立
KVO属性观察自动通知
//KVO属性观察自动通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;//只有添加到观察者中的key才能调用这个方法,根据key的名称来确认这个key是否能够被观察 YES可以NO不可以
+ (BOOL)automaticallyNotifiesObserversOf<key> //单个属性是否需要通知
上述返回值为YES,设置好观察属性以及观察回调以后就会自动通知
KVO属性观察手动通知
//手动通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;//只有添加到观察者中的key才能调用这个方法,根据key的名称来确认这个key是否能够被观察 YES可以NO不可以
+ (BOOL)automaticallyNotifiesObserversOf<key> //单个属性是否需要通知
上述返回值为NO,设置好观察属性以及观察回调以后,属性修改并不会自动的去调用回调,而是需要在修改属性的前后加上两个方法。
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
KVO的原理分析
isa指向问题
在原理分析中我们需要知道isa指针的含义,先简单的回顾一下,isa指向的一个结构图:
- isa 指针指向
- 类的实例对象的 isa 指向该类;该类的 isa 指向该类的 MetaClass
- MetaCalss的isa对象指向RootMetaCalss
- 如果MetaClass是RootMetaCalss,那么该MetaClass的isa指针指向它自己
创建KVO以后的isa指向
验证上述改变
在类的实例对象设置观察者以后,类的isa指向发生了改变,如下图:
NSKVONotifying_<ObjectClass>这个对象如何得到的,观察者设置好以后,debug时在Xcode``lldb
我们可以得到这个对象,如下图:
什么时候生成的 NSKVONotifying_<ObjectClass
代码如下:
p = [Person3 new];
Class class = NSClassFromString(@"NSKVONotifying_Person3");
if(class){
NSLog(@"NSKVONotifying_Person3 存在");
}else{
NSLog(@"NSKVONotifying_Person3 不存在");
}
[p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
Class class1 = NSClassFromString(@"NSKVONotifying_Person3");
if(class1){
NSLog(@"NSKVONotifying_Person3 存在");
}else{
NSLog(@"NSKVONotifying_Person3 不存在");
}
上述代码证明, NSKVONotifying_<ObjectClass
是在注册观察者以后生成的
NSKVONotifying_<ObjectClass
NSKVONotifying_<ObjectClass
当中都有什么方法
在注册了观察者以后我们查看,新添加的NSKVONotifying_<ObjectClass
中都有哪些方法。
[p addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
Class class1 = NSClassFromString(@"NSKVONotifying_Person3");
[self printfMethod:class1];
- (void)printfMethod:(Class)class{
unsigned int count = 0;
Method *methods = class_copyMethodList(class, &count);
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i< count ; i++) {
Method method = methods[i];
SEL sel = method_getName(method);
IMP im = method_getImplementation(method);
NSString * selstr = NSStringFromSelector(sel);
NSLog(@"%@",selstr);
[array addObject:selstr];
}
NSLog(@"%@",array);
free(methods);
}
用lldb查看NSKVONotifying_<ObjectClass
中方法以及imp的实现
//1
(lldb) po im
(Foundation`_NSSetObjectValueAndNotify)
2019-06-29 15:32:24.974049+0800 KVO的全面分析[4370:607232] setName:
//2
(lldb) po im
(Foundation`NSKVOClass)
2019-06-29 15:36:09.822166+0800 KVO的全面分析[4370:607232] class
//3
(lldb) po im
(Foundation`NSKVODeallocate)
2019-06-29 15:36:37.790595+0800 KVO的全面分析[4370:607232] dealloc
//4
(lldb) po im
(Foundation`NSKVOIsAutonotifying)
2019-06-29 15:36:56.961576+0800 KVO的全面分析[4370:607232] _isKVOA
NSKVONotifying_<ObjectClass
的SuperClass验证
[self printClasses:[Person3 class]];
/// 打印这个类常见的实例的所有父类
- (void) printClasses:(Class) cls {
/// 注册类的总数
int count = objc_getClassList(NULL, 0);
/// 创建一个数组, 其中包含给定对象
NSMutableArray* array = [NSMutableArray arrayWithObject:cls];
/// 获取所有已注册的类
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
/// 遍历s
for (int i = 0; i < count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[array addObject:classes[i]];
}
}
free(classes);
NSLog(@"classes = %@", array);
}
打印结果:
2019-06-29 15:50:23.740570+0800 KVO的全面分析[4607:663940] classes = (
Person3,
"NSKVONotifying_Person3"
)
上述证明NSKVONotifying_Person3
是Person3的子类
_NSSetObjectValueAndNotify
这个方法的实现逻辑
在lldb中我们添加一个观察:
wetchpoint set variable self->p->_name
从汇编当中我们可以看到这个方法大概使用了哪些逻辑
willChangeValueForKey
[Person setName];
didChangeValueForKey
NSKeyValueNotifyobserver
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
注册监听以后Class应该是NSKVONotifying_<ObjectClass>
,为什么我们打印calss的时候还是<ObjectClass>
?
猜测可能是在NSKVONotifying_<ObjectClass>
重载了-(Class)class
+ (Class)class{
return class_getSuperclass(object_getClass(self));
}
移除监听以后NSKVONotifying_<ObjectClass>
存在还是不存在?
在- (void)dealloc
中移除观察者
- (void)dealloc
{
[p removeObserver:self forKeyPath:@"name"];
}
然后在打印所有<ObjectClass>
的子类
2019-06-29 16:14:20.841977+0800 KVO的全面分析[4807:728105] classes = (
Person3,
"NSKVONotifying_Person3"
)
并没有消失
自定义KVO
自定义KVO的步骤
- 定义添加观察者
- (void)GG_addObserver:(NSObject *)observer forKeyPath:(NSString *)KeyPath options:(NSKeyValueObservingOptions)options context:(void *)context
和移除观察者方法- (void)gv_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
- 创建一个子类
NSKVONotifying_<Objectclass>
- 动态添加一些方法(setter class)
- 修改isa的指向
- 用runtime关联对象方法动态添加观察者server
步骤一:定义添加观察者
- (void)GG_addObserver:(NSObject *)observer forKeyPath:(NSString *)KeyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
}
/// 移除观察者
- (void)gv_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
}
步骤二:在添加观察者方法中创建一个子类 NSKVONotifying_<Objectclass>
//1. 拼接子类名 //person
NSString *oldName = NSStringFromClass([self class]);
NSString *newName = [NSString stringWithFormat:@"NSKVONotifying_%@",oldName];
//2. 创建并注册类
Class newclass = NSClassFromString(newName);
if(!newclass){
//创建并注册类
newclass = objc_allocateClassPair([self class], newName.UTF8String, 0);
objc_registerClassPair(newclass);
}
步骤三:在添加观察者方法中NSKVONotifying_<Objectclass>
动态添加一些方法(setter class)
//添加一些方法
//class
Method classMethod = class_getInstanceMethod([self class], @selector(class));
const char* classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newclass, @selector(class), (IMP)GG_class, classTypes);
//自定义的class方法
Class GG_class(id self, SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
//setter
NSString *setterMethodName = setterForGetter(keyPath);
SEL setterSEL = NSSelectorFromString(setterMethodName);
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char * setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newclass, setterSEL, (IMP)GG_setter, setterTypes);
//自定义的setter方法
static void GG_setter(id self, SEL _cmd, id newValue) {
NSLog(@"%s", __func__);
struct objc_super superStruct = {
self,
class_getSuperclass(object_getClass(self))
};
// 改变父类的值
objc_msgSendSuper(&superStruct, _cmd, newValue);
// 通知观察者, 值发生改变了
// 观察者
id observer = objc_getAssociatedObject(self, (__bridge void *)@"objc");
NSString* setterName = NSStringFromSelector(_cmd);
NSString* key = getterForSetter(setterName);
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), key, self, @{key:newValue}, nil);
}
//key ===>>> setKey:
static NSString * setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil; }
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
//set<Key>:===> Key
static NSString * getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
return getter;
}
步骤四:修改isa的指向
object_setClass(self, newclass);
步骤五:用runtime关联对象方法动态添加观察者server并在自定义setter方法中实现改变父类的值,并通知观察者
// 关联方法
objc_setAssociatedObject(self, (__bridge void *)@"objc", observer, OBJC_ASSOCIATION_ASSIGN);
//setter方法干的内容
static void GG_setter(id self, SEL _cmd, id newValue) {
NSLog(@"%s", __func__);
struct objc_super superStruct = {
self,
class_getSuperclass(object_getClass(self))
};
// 改变父类的值
objc_msgSendSuper(&superStruct, _cmd, newValue);
// 通知观察者, 值发生改变了
// 观察者
id observer = objc_getAssociatedObject(self, (__bridge void *)@"objc");
NSString* setterName = NSStringFromSelector(_cmd);
NSString* key = getterForSetter(setterName);
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), key, self, @{key:newValue}, nil);
}
自定义BlockKVO
自定义BlockKVO的实现步骤
在NSObject的分类
NSObject+KVO
中添加一个blockGGKVOBlock
和- (void)GG_addobserverrBlock:(NSObject *)observer forkeyPath:(NSString *)keyPath handle:(GGKVOBlock)handleBlock
观察者方法观察者方法方法实现一个存储
GGInfo
的一个数组,来存储观察者,并使用Runtime方法关联到这个对象当中在
setter
方法方法中获取GGInfo
的数组,便利其中回调GGInfo
中的block
自定义BlockKVO第一步
typedef void(^GGKVOBlock)(id observer,NSString *keyPath,id oldValue, id newValue);
- (void)GG_addobserverrBlock:(NSObject *)observer forkeyPath:(NSString *)keyPath handle:(GGKVOBlock)handleBlock;
自定义BlockKVO第二步
static const char* GGKVOAssiociateKey = "GGKVOAssiociateKey";
@interface GGInfo : NSObject
@property (nonatomic, weak) NSObject* observer;
@property (nonatomic, strong) NSString* keyPath;
@property (nonatomic, copy) GGKVOBlock hanleBlock;
@end
@implementation GGInfo
- (instancetype) initWithObserver:(NSObject*)observer forKeyPath:(NSString*) keyPath handleBlock:(GGKVOBlock) block {
if (self == [super init]) {
_observer = observer;
_keyPath = keyPath;
_hanleBlock = block;
}
return self;
}
@end
// 信息保存
GGInfo* info = [[GGInfo alloc] initWithObserver:observer forKeyPath:keyPath handleBlock:handleBlock];
NSMutableArray* array = objc_getAssociatedObject(self, GGKVOAssiociateKey);
if (!array) {
array = [NSMutableArray array];
objc_setAssociatedObject(self, GGKVOAssiociateKey, array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[array addObject:info];
自定义BlockKVO第三步
static void GG_setter(id self, SEL _cmd, id newValue) {
NSLog(@"%s", __func__);
struct objc_super superStruct = {
self,
class_getSuperclass(object_getClass(self))
};
NSString* keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = objc_msgSendSuper(&superStruct, NSSelectorFromString(keyPath));
// 改变父类的值
objc_msgSendSuper(&superStruct, _cmd, newValue);
NSMutableArray* array = objc_getAssociatedObject(self, GGKVOAssiociateKey);
if (array) {
for (GGInfo* info in array) {
if ([info.keyPath isEqualToString:keyPath]) {
info.hanleBlock(info.observer, keyPath, oldValue, newValue);
return;
}
}
}
自定义KVO自动销毁
第一种自动销毁的方式
- 在被观察者对象别名
person
中添加dealloc
方法- 如果不添加
dealloc
,NSObject->isa指针会一直往上去寻找,这样你在交换的时候会交换很多次数这个方法
- 如果不添加
- 如果需要自动移除观察者,我们必须在
person``dealloc
中移除 - 在对象中自定义
Mydealloc
并交换其dealloc
- 在
Mydealloc
移除我们的观察者,从而实现KVO的自动销毁
@implementation Person
// NSObject dealloc
- (void) dealloc {
NSLog(@"%s", __func__);
}
- (void) hookDealloc {
Method m1 = class_getInstanceMethod(object_getClass(self), NSSelectorFromString(@"dealloc"));
Method m2 = class_getInstanceMethod(object_getClass(self), @selector(myDealloc));
method_exchangeImplementations(m1, m2);
}
- (void) myDealloc {
Class superClass = class_getSuperclass(object_getClass(self));
object_setClass(self, superClass);
[self myDealloc];
}
@end
第二种自动销毁的方式
第一种的销毁方式,我们必须要在Person
被观察者中实现dealloc
方法来实现我们的方法交换,为什么?
RunLoop在不断的处理事情,中间是由一些临时对象的,当RunLoop休眠或者退出的时候会调用对象的析构函数,而我们在NSObject分类中交换的该方法,所以会出现一只调用的现象,类中的方法是如果查找了,我们可以查看上面isa指向的一个图
所以第二种自动销毁方式就是在新建的子类
NSKVONotifying_<Objectclass>
中添加dealloc
方法。
在我们创建子类NSKVONotifying_<Objectclass>
的时候其中添加dealloc
析构方法
// 添加析构方法
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char* deallocTypes = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)myDealloc, deallocTypes);