iOS基础全面分析之三(KVO全面分析)

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 *)change context:(nullable void *)context;

KVO属性依赖关系

//观察者依赖关系  需要对象中重写
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));  //全部属性的依赖关系建立
+ (NSSet *)keyPathsForValuesAffecting<#DependentKey#> //单独属性的依赖关系建立

KVO属性观察自动通知

//KVO属性观察自动通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;//只有添加到观察者中的key才能调用这个方法,根据key的名称来确认这个key是否能够被观察  YES可以NO不可以

+ (BOOL)automaticallyNotifiesObserversOf  //单个属性是否需要通知

上述返回值为YES,设置好观察属性以及观察回调以后就会自动通知


KVO属性观察手动通知

//手动通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;//只有添加到观察者中的key才能调用这个方法,根据key的名称来确认这个key是否能够被观察  YES可以NO不可以

+ (BOOL)automaticallyNotifiesObserversOf  //单个属性是否需要通知

上述返回值为NO,设置好观察属性以及观察回调以后,属性修改并不会自动的去调用回调,而是需要在修改属性的前后加上两个方法。
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

KVO的原理分析

isa指向问题

在原理分析中我们需要知道isa指针的含义,先简单的回顾一下,isa指向的一个结构图:

iOS基础全面分析之三(KVO全面分析)_第1张图片
  • isa 指针指向
    • 类的实例对象的 isa 指向该类;该类的 isa 指向该类的 MetaClass
    • MetaCalss的isa对象指向RootMetaCalss
    • 如果MetaClass是RootMetaCalss,那么该MetaClass的isa指针指向它自己

创建KVO以后的isa指向

验证上述改变

在类的实例对象设置观察者以后,类的isa指向发生了改变,如下图:

iOS基础全面分析之三(KVO全面分析)_第2张图片
KVO原理之isa指向.png

NSKVONotifying_这个对象如何得到的,观察者设置好以后,debug时在Xcode``lldb我们可以得到这个对象,如下图:

iOS基础全面分析之三(KVO全面分析)_第3张图片
isa指向变更验证.png

什么时候生成的 NSKVONotifying_

代码如下:

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_是在注册观察者以后生成的

NSKVONotifying_

NSKVONotifying_当中都有什么方法

在注册了观察者以后我们查看,新添加的NSKVONotifying_中都有哪些方法。

[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_中方法以及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_的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_,为什么我们打印calss的时候还是

猜测可能是在NSKVONotifying_重载了-(Class)class

+ (Class)class{
    return class_getSuperclass(object_getClass(self));
}

移除监听以后NSKVONotifying_存在还是不存在?

- (void)dealloc中移除观察者

- (void)dealloc
{
    [p removeObserver:self forKeyPath:@"name"];
}
iOS基础全面分析之三(KVO全面分析)_第4张图片
移除监听观察者类是否还存在.png

然后在打印所有的子类

2019-06-29 16:14:20.841977+0800 KVO的全面分析[4807:728105] classes = (
    Person3,
    "NSKVONotifying_Person3"
)

并没有消失

自定义KVO

自定义KVO的步骤

  1. 定义添加观察者- (void)GG_addObserver:(NSObject *)observer forKeyPath:(NSString *)KeyPath options:(NSKeyValueObservingOptions)options context:(void *)context和移除观察者方法- (void)gv_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
  2. 创建一个子类 NSKVONotifying_
  3. 动态添加一些方法(setter class)
  4. 修改isa的指向
  5. 用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_


//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_动态添加一些方法(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
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的实现步骤

  1. 在NSObject的分类NSObject+KVO中添加一个blockGGKVOBlock- (void)GG_addobserverrBlock:(NSObject *)observer forkeyPath:(NSString *)keyPath handle:(GGKVOBlock)handleBlock观察者方法

  2. 观察者方法方法实现一个存储GGInfo的一个数组,来存储观察者,并使用Runtime方法关联到这个对象当中

  3. 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自动销毁

第一种自动销毁的方式

  1. 在被观察者对象别名person中添加dealloc方法
    • 如果不添加dealloc,NSObject->isa指针会一直往上去寻找,这样你在交换的时候会交换很多次数这个方法
  2. 如果需要自动移除观察者,我们必须在person``dealloc中移除
  3. 在对象中自定义Mydealloc并交换其dealloc
  4. 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_中添加dealloc方法。

在我们创建子类NSKVONotifying_的时候其中添加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);

KVO的概括图

iOS基础全面分析之三(KVO全面分析)_第5张图片
KVO的内容.png

你可能感兴趣的:(iOS基础全面分析之三(KVO全面分析))