为了账号安全,请及时绑定邮箱和手机立即绑定

KVO原理解析

标签:
iOS

KVO在我们项目开发中,经常被用到,但很少会被人关注,但如果面试一些大公司,针对KVO的面试题可能如下:

  • 知道KVO嘛,底层是怎么实现的?

  • 如何动态的生成一个类?

  • 可不可以自己写一个KVO?

今天我们围绕上面几个问题,我们先看KVO底层实现原理,以及怎么自己写一个KVO?

 

一、KVO

1. KVO定义

KVO:可以监听一个对象的某个属性是否发生了改变,或者通知其他对象的指定属性发生了改变。

 

2.KVO实现

2.1 监听某个对象的属性

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

 

2.2 实现协议

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context;

 

2.3 移除监听

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

下面是一个简单的演示:

复制代码

- (void)viewDidLoad {
    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.     
    self.person = [[ZJPerson alloc] init];
   
    [self.person setName:@"zhangsan"];
   
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
     
} 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [self.person setName:@"lisi"];
} 
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"%@", change);
} 
- (void)dealloc{
    [self.person removeObserver:self forKeyPath:@"name"];
}

复制代码

运行结果

https://img1.sycdn.imooc.com//5b7a903f00016fab07000141.jpg

通过以上demo,我们来思考KVO为什么能监听到属性变化,底层又是怎么样实现的呢?

 

3. KVO底层实现

在查看KVO底层实现,我们首先用runtime在添加监听之前以及之后的类对象

1 NSLog(@"%@", object_getClass(self.person));2 [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];3 NSLog(@"%@", object_getClass(self.person));

可以查看结果如下:

2018-05-19 22:48:18.726028+0800 KVO[33804:3059947] ZJPerson2018-05-19 22:48:18.726535+0800 KVO[33804:3059947] NSKVONotifying_ZJPerson

通过上面发现,添加监听之后,实例对象的类对象发生了改变,系统自动为我们动态添加了一个NSKVONotifying_+类名的类,改变属性的值是通过setter方法进行实现,很明显是系统已经动态生成了NSKVONotifying_ZJPerson类,并重写了setter方法,所以不可以创建NSKVONotifying_ZJPerson类了,如果创建了NSKVONotifying_ZJPerson类,会报以下错误:

2018-05-19 22:56:32.223288+0800 KVO[33919:3068985] [general] KVO failed to allocate class pair for name NSKVONotifying_ZJPerson, automatic key-value observing will not work for this class

错误提示的是:创建NSKVONotifying_ZJPerson失败。

 

那么问题又来了,重写的setter方法内部又做了什么?我们再次利用runtime打印下面方法的实现。

https://img1.sycdn.imooc.com//5b7a904d000122d007000195.jpg

通过上面发现,发现内部调用了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

复制代码

简化成伪代码如下:

复制代码

 1 - (void)setName:(NSString *)name{ 2     _NSSetObjectValueAndNotify(); 3 } 4   5 void _NSSetObjectValueAndNotify { 6     [self willChangeValueForKey:@"name"]; 7     [super setName:name]; 8     [self didChangeValueForKey:@"name"]; 9 }10  11 - (void)didChangeValueForKey:(NSString *)key{12     [observe observeValueForKeyPath:key ofObject:self change:nil context:nil];13 }

复制代码

 

拓展》〉》NSKVONotifying_ZJPerson内部重写了方法?

利用runtime打印方法列表

复制代码

 1 unsigned int count; 2 Method *methods = class_copyMethodList(object_getClass(self.person), &count); 3       4 for (NSInteger index = 0; index < count; index++) { 5    Method method = methods[index]; 6     7    NSString *methodStr = NSStringFromSelector(method_getName(method)); 8     9    NSLog(@"%@\n", methodStr);10 }

复制代码

打印结果

2018-05-20 08:57:07.883400+0800 KVO[35888:3218908] setName:2018-05-20 08:57:07.883571+0800 KVO[35888:3218908] class2018-05-20 08:57:07.883676+0800 KVO[35888:3218908] dealloc2018-05-20 08:57:07.883793+0800 KVO[35888:3218908] _isKVOA

 

二、如何动态生成类

说到动态生成一个类,也就是利用了苹果的runtime机制,下面我们来动态创建生成类。

2.1 创建类

Class customClass = objc_allocateClassPair([NSObject class], "ZJCustomClass", 0);

 

2.2 添加实例变量

// 添加实例变量
    class_addIvar(customClass, "age", sizeof(int), 0, "i");

 

2.3 添加方法,V@:表示方法的参数和返回值

    class_addMethod(customClass, @selector(hahahha), (IMP)hahahha, "V@:");

 

需要实现的方法:

复制代码

void hahahha(id self, SEL _cmd)
{
    NSLog(@"hahahha====");
} 
- (void)hahahha{
}

复制代码

然后注册到运行时环境

objc_registerClassPair(customClass);

 

下面是打印方法列表以及成员变量列表

复制代码

 1 #pragma mark - Util 2   3 - (NSString *)copyMethodsByClass:(Class)cls{ 4     unsigned int count; 5     Method *methods = class_copyMethodList(cls, &count); 6      7     NSString *methodStrs = @""; 8       9     for (NSInteger index = 0; index < count; index++) {10         Method method = methods[index];11         12         NSString *methodStr = NSStringFromSelector(method_getName(method));13                 14         methodStrs = [NSString stringWithFormat:@"%@ ", methodStr];15     }16     17     free(methods);18      19     return methodStrs;20 }21  22 - (NSString *)copyIvarsByClass:(Class)cls{23     unsigned int count;24     Ivar *ivars = class_copyIvarList(cls, &count);25     26     NSMutableString *ivarStrs = [NSMutableString string];27      28     for (NSInteger index = 0; index < count; index++) {29         Ivar ivar = ivars[index];30         31         NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];  //获取成员变量的名字32         33         NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; //获取成员变量的数据类型34         35         [ivarStrs appendString:@"\n"];36         [ivarStrs appendString:ivarName];37         [ivarStrs appendString:@"-"];38         [ivarStrs appendString:ivarType];39          40     }41      42     free(ivars);43    44     return ivarStrs;45 }

复制代码

 

三、自己动手写一个KVO

给NSObject添加一个Category,NSObject+KVO监听方法

3.1 .h文件

复制代码

#import <Foundation/Foundation.h>@interface NSObject (MY_KVO)- (void)My_addObserver:(NSObject *_Nonnull)observer forKeyPath:(NSString *_Nonnull)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;@end

复制代码

 

3.2  .m实现文件

复制代码

#import "NSObject+MY_KVO.h"#import "MY_KVONotifying_Person.h"#import <objc/runtime.h>@implementation NSObject (MY_KVO)-(void)My_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{    // 修改isa指针(runtime) 系统的MY_KVONotifying_Person这个类是动态生成的,我们直接手动创建
    object_setClass(self, [MY_KVONotifying_Person class]);   // 给对象动态添加属性,之前文章介绍过了.目的是保存observer,好在set方法里面拿到,调用My_addObserver:forKeyPath:options:context:这个方法

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

}@end

复制代码

 

3.3 重写MY_KVONotifying_Person set方法

复制代码

#import "MY_KVONotifying_Person.h"#import "NSObject+MY_KVO.h"#import <objc/runtime.h>@implementation MY_KVONotifying_Person- (void)setAge:(int)age{   id observer = objc_getAssociatedObject(self, @"age");    if (observer && [observer respondsToSelector:@selector(My_addObserver:forKeyPath:options:context:)]) {

        [observer My_addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

    }
    [super setAge:age];
}@end

复制代码

 

3.4 使用

复制代码

#import "ViewController.h"#import "Person.h"#import "NSObject+MY_KVO.h"@interface ViewController ()
@property (nonatomic,strong)Person * p;@end@implementation ViewController- (void)viewDidLoad {
    [super viewDidLoad];
    Person * p =[[Person alloc] init];
    [p My_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
    _p= p;
}- (void)My_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
    NSLog(@"age++ 自己的KVO");

}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
     _p.age ++ ;

}

复制代码

 

注意点:修改Xcode 的一个配置,设为NO

https://img1.sycdn.imooc.com//5b7a90690001157b07000155.jpg

 

以上就是KVO的基本内容,希望通过本篇博客,大家对KVO原理以及基本使用有更深的了解!!!

原文出处:https://www.cnblogs.com/guohai-stronger/p/9473551.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消