前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >kvo底层实现 以及自己实现kvo

kvo底层实现 以及自己实现kvo

原创
作者头像
conanma
修改2021-05-12 18:05:20
7120
修改2021-05-12 18:05:20
举报
文章被收录于专栏:正则正则

调用 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil

系统为我们动态添加了一个NSKVONotifying_+类名的类,因为我们改变对象属性的值是通过setter方法实现了,所以很明显是系统动态生成的NSKVONotifying_ZJPerson类重写了setter方法。

发现方法实现变了,内部调用了系统Foundation框架下的_NSSetObjectValueAndNotify方法。

_NSSetObjectValueAndNotify函数内部实现过程如下

1. `-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]:

2. -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:]:

3. [ZJPerson setName:];

4. `NSKeyValueDidChange:

5. `NSKeyValueNotifyObserver:

6. - (void)observeValueForKeyPath:ofObject:change:context

简化成OC的伪代码大致如下:

- (void)setName:(NSString *)name{

_NSSetObjectValueAndNotify();

}

void _NSSetObjectValueAndNotify {

[self willChangeValueForKey:@"name"];

[super setName:name];

[self didChangeValueForKey:@"name"];

}

- (void)didChangeValueForKey:(NSString *)key{

[observe observeValueForKeyPath:key ofObject:self change:nil context:nil];

}

可以利用runtime方法打印一下方法列表:

unsigned int count;

Method *methods = class_copyMethodList(object_getClass(self.person), &count);

for (NSInteger index = 0; index < count; index++) {

   Method method = methods[index];

   NSString *methodStr = NSStringFromSelector(method_getName(method));

NSLog(@"%@\n", methodStr);

}

2018-05-20 08:57:07.883400+0800 KVO[35888:3218908] setName:

2018-05-20 08:57:07.883571+0800 KVO[35888:3218908] class

2018-05-20 08:57:07.883676+0800 KVO[35888:3218908] dealloc

2018-05-20 08:57:07.883793+0800 KVO[35888:3218908] _isKVOA

简单分析下重写这些方法的作用:

class:重写这个方法,是为了伪装苹果自动为我们生成的中间类。

dealloc:应该是处理对象销毁之前的一些收尾工作

_isKVOA:告诉系统使用了kvo

自己动手写一个KVO

KVO底层实现还是很复杂的,下面我只是简单的写下实现过程:

  • 因为它是一个非正式协议,给NSObject新建一个Category,NSObject+kvo.h,添加监听方法:

.h

#import <Foundation/Foundation.h>

@interface NSObject (kvo)

- (void)zj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

@end

.m

#import "NSObject+kvo.h"

#import <objc/runtime.h>

#import <objc/message.h>

@implementation NSObject (kvo)

- (void)zj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{

//动态添加一个类

    NSString *originClassName = NSStringFromClass([self class]);

    NSString *newClassName = [@"ZJKVO_" stringByAppendingString:originClassName];

const char *newName = [newClassName UTF8String];

// 继承自当前类,创建一个子类

    Class kvoClass = objc_allocateClassPair([self class], newName, 0);

// 添加setter方法

class_addMethod(kvoClass, @selector(setName:), (IMP)setName, "v@:@");

//注册新添加的这个类

objc_registerClassPair(kvoClass);

// 修改isa指针,由ZJPerson指向ZJKVO_Person

object_setClass(self, kvoClass);

// 保存观察者属性到当前类中

objc_setAssociatedObject(self, (__bridge const void *)@"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

#pragma mark - 重写父类方法

void setName(id self, SEL _cmd, NSString *name) {

// 保存当前KVO的类

    Class kvoClass = [self class];

// 将self的isa指针指向父类ZJPerson,调用父类setter方法

object_setClass(self, class_getSuperclass([self class]));

// 调用父类setter方法,重新复制

objc_msgSend(self, @selector(setName:), name);

// 取出ZJKVO_Person观察者

    id objc = objc_getAssociatedObject(self, (__bridge const void *)@"observer");

// 通知观察者,执行通知方法

objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:), name, self, nil, name);

// 重新修改为ZJKVO_Person类

object_setClass(self, kvoClass);

}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 自己动手写一个KVO
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档