这篇文章简单介绍苹果的KVO底层是怎么实现的,自己仿照KVO的底层实现,写一个自己的KVO监听

#pragma mark--KVO底层实现

第一步:新建一个Person类继承NSObject

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

//字符串类型的属性name
@property (nonatomic, strong) NSString *name; @end

Person.m

#import "Person.h"

@implementation Person
- (void)setName:(NSString *)name
{ //别问为什么(下面有用处),就是要自己处理set方法
_name = [NSString stringWithFormat:@"%@aaaa",name];
}
@end

第二步:在控制器中创建一个Person类型的对象p,利用苹果的KVO来监听该对象p的name属性的变化

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

ViewController.m

#import "ViewController.h"
#import "Person.h" @interface ViewController () @property (nonatomic, strong) Person *p; @end @implementation ViewController - (void)viewDidLoad
{
[super viewDidLoad]; Person *p = [[Person alloc] init]; // 监听name属性有没有改变
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.p = p;
} //点击屏幕修改self.p的name属性的值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
static int i = ;
i++;
self.p.name = [NSString stringWithFormat:@"%d",i];
} // 所有的KVO监听都会来到这个方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
NSLog(@"%@",self.p.name);
} @end

会打印1aaaa,2aaaa,3aaaa...... //从打印可以看出KVO监听的是set方法的调用!!!

实际上:KVO的本质就是监听一个对象有没有调用set方法!!!

怎么验证呢?

1.将Person.h中的代码修改为

#import <Foundation/Foundation.h>

@interface Person : NSObject
{
@public
NSString *_name;//为了验证KVO监听的是setter方法
} @end

将ViewController.m的viewDidLoad中的代码修改为

- (void)viewDidLoad
{
[super viewDidLoad]; Person *p = [[Person alloc] init]; // 监听_name有没有改变
[p addObserver:self forKeyPath:@"_name" options:NSKeyValueObservingOptionNew context:nil];
self.p = p;
}

运行后,点击屏幕是没有任何打印的-->结论1:KVO的本质就是监听一个对象有没有调用set方法!!!

第三步:打断点看一下addObserver 到底干了什么事情?

继续走一步:

这个NSKVONotifying_Person类是什么鬼?

估计你已经猜到了! 没错这个NSKVONotifying_Person类就是系统帮我们实现的!!!

怎么验证呢? 想到了吗?我们自己把这个类给重写了!看看会发生什么?

第四步:创建NSKVONotifying_Person类

NSKVONotifying_Person.h

#import <Foundation/Foundation.h>

@interface NSKVONotifying_Person : NSObject

@end

NSKVONotifying_Person.m

#import "NSKVONotifying_Person.h"

@implementation NSKVONotifying_Person

@end

第五步运行:

恭喜猜测没错,验证通过!!!

结论2:系统创建了一个NSKVONotifying_XXX的类

结论3:修改了对象p的isa指针

那么系统帮我们创建的NSKVONotifying_XXX类有没有继承自我们自己常见的XXX类呢?

我既然这么问,肯定是继承啦!!因为我们已经把属性对应的set方法给重写了!!!因为已经修改了对象的指针(用户调用对象p的方法就不是Person的方法了,是isa指针指向的类的对应的方法),如果不继承的话,相当于把用户重写的set方法给覆盖了,用户调用自己写的set方法就不起作用了!!!也就有了 结论4:重写对应的set方法再内部实现父类做法,通知观察者

KVO底层实现结论:(都是系统自动帮我们实现的)

1> 创建NSKVONotifying_XXX的类

2> 重写对应属性的set方法,在内部实现父类做法,通知观察者

3> 修改当前对象的isa指针,指向创建的NSKVONotifying_XXX(这样实际调用的时候就会走NSKVONotifying_XXX类中对应的方法)

#pragma mark--仿写KVO实现

知道了KVO底层实现的原理就可以仿照KVO写一个自己的"KVO"了

步骤一:给NSObject写一个分类

NSObject+KVO.h

#import <Foundation/Foundation.h>

@interface NSObject (KVO)

//添加监听
- (void)ey_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; //移除监听
- (void)ey_removeObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath context:(nullable void *)context; @end

NSObject+KVO.m

    //
// NSObject+KVO.m
// KVO底层实现
//
// Created by lieryang on 2017/6/17.
// Copyright © 2017年 lieryang. All rights reserved.
// #import "NSObject+KVO.h"
#import <objc/message.h> static NSString * const EYKVONotifying_ = @"EYKVONotifying_";
static NSString * const observerKey = @"observer";
@implementation NSObject (KVO) //添加监听
- (void)ey_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
if (keyPath.length == ) {//如果传进来的keyPath为@""或者为nil 直接返回
return;
} // 1. 检查对象的类有没有相应的 setter 方法。
SEL setterSelector = NSSelectorFromString([self setterForGetter:keyPath]); Method setterMethod = class_getInstanceMethod([self class], setterSelector); if (!setterMethod) {//如果没有直接返回,不需要任何处理
NSLog(@"找不到该方法");
return;
} // 2. 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类
Class clazz = object_getClass(self);
NSString *className = NSStringFromClass(clazz); if (![className hasPrefix:EYKVONotifying_]) {
clazz = [self ey_KVOClassWithOriginalClassName:className];
object_setClass(self, clazz);
} // 3. 为EYKVONotifying_XXX添加setter方法的实现
const char *types = method_getTypeEncoding(setterMethod);
class_addMethod(clazz, setterSelector, (IMP)ey_setter, types); // 4. 添加该观察者到观察者列表中
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey));
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
if ([observers indexOfObject:observer] == NSNotFound) {
[observers addObject:observer];
}
} //移除监听
- (void)ey_removeObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath context:(nullable void *)context
{ NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(observerKey)); [observers removeObject:observer];
}
#pragma mark - 注册自己的EYKVONotifying_XXX
- (Class)ey_KVOClassWithOriginalClassName:(NSString *)className
{
// 生成EYKVONotifying_XXX的类名
NSString *kvoClassName = [EYKVONotifying_ stringByAppendingString:className];
Class kvoClass = NSClassFromString(kvoClassName); // 如果EYKVONotifying_XXX已经被注册过了, 则直接返回
if (kvoClass) {
return kvoClass;
} // 如果EYKVONotifying_XXX不存在, 则创建这个类
Class originClass = object_getClass(self);
kvoClass = objc_allocateClassPair(originClass, kvoClassName.UTF8String, ); // 修改EYKVONotifying_XXX方法的实现, 学习Apple的做法, 隐瞒这个EYKVONotifying_XXX
Method classMethod = class_getInstanceMethod(kvoClass, @selector(class));
const char *types = method_getTypeEncoding(classMethod);
class_addMethod(kvoClass, @selector(class), (IMP)ey_class, types); // 注册EYKVONotifying_XXX
objc_registerClassPair(kvoClass); return kvoClass;
} Class ey_class(id self, SEL cmd)
{
Class clazz = object_getClass(self); // EYKVONotifying_XXX
Class superClazz = class_getSuperclass(clazz); // origin_class
return superClazz; // origin_class
} /**
* 重写setter方法, 新方法在调用原方法后, 通知每个观察者
*/
static void ey_setter(id self, SEL _cmd, id newValue)
{
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = [self getterForSetter:setterName]; if (!getterName) {
NSLog(@"找不到getter方法");
} // 调用原类的setter方法
struct objc_super superClazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 这里需要做个类型强转, 否则会报too many argument的错误
((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue); // 找出观察者的数组
NSArray *observers = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey));
// 遍历数组
for (id observer in observers) {
// 调用监听者observer的observeValueForKeyPath 方法,因为observer为id类型,所以就偷懒调用了系统的监听回调,要是自己定义方法,会报找方法的错误,可以在添加监听的时候,传进来一个block代码块,在此处回调block,更方便外界的调用
[observer observeValueForKeyPath:getterName ofObject:self change:nil context:nil];
}
} #pragma mark - 生成对应的setter方法字符串
- (NSString *)setterForGetter:(NSString *)key
{
// 1. 首字母转换成大写
NSString * firstString = [[key substringToIndex:] uppercaseString];
// 2. 剩下的字母
NSString * remainingString = [key substringFromIndex:]; // 3. 最前增加set, 最后增加: setName:
NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstString, remainingString]; return setter; }
#pragma mark - 生成对应的getter方法字符串
- (NSString *)getterForSetter:(NSString *)key
{
// setName
if (key.length <= || ![key hasPrefix:@"set"] || ![key hasSuffix:@":"]) {
return nil;
} // 移除set和:
NSRange range = NSMakeRange(, key.length - );
NSString *getter = [key substringWithRange:range]; // 小写
NSString *firstString = [[getter substringToIndex:] lowercaseString];
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(, )
withString:firstString]; return getter;
}
@end

外界使用

创建Person类

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString * name;

@end

Person.m

#import "Person.h"

@implementation Person

@end
#import "ViewController.h"
#import "Person.h"
#import "NSObject+KVO.h" @interface ViewController () @property (nonatomic, strong) Person * p; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; Person * p = [[Person alloc] init];
[p ey_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.p = p;
} // 监听的回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
NSLog(@"%@", self.p.name);
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event]; self.p.name = [NSString stringWithFormat:@"%u", arc4random_uniform()];
} @end

更多内容--> 博客导航 每周一篇哟!!!

有任何关于iOS开发的问题!欢迎下方留言!!!或者邮件lieryangios@126.com 虽然我不一定能够解答出来,但是我会请教iOS开发高手!!!解答您的问题!!!

KVO底层实现原理,仿写KVO的更多相关文章

  1. Object-C知识点 (三) 单例 蒙版 刷新 KVO底层

    #pragma mark - 单例方法(完整的方法) 系统的单例方法名称 sharedApplication defaultManager standardUserDefaults currentDe ...

  2. KVC和KVO的理解(底层实现原理)

    1.KVC,即是指 NSKeyValueCoding,一个非正式的Protocol,提供一种机制来间接访问对象的属性.而不是通过调用Setter.Getter方法访问.KVO 就是基于 KVC 实现的 ...

  3. IOS-详解KVO底层实现

    一.KVO (Key-Value Observing) KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现.也是 Cocoa Binding 的基础.当被观察对 ...

  4. KVO内部实现原理

    KVO的原理: 只要给一个对象注册一个监听, 那么在运行时, 系统就会自动给该对象生成一个子类对象, (格式如:NSKVONotifying_className), 并且重写自动生成的子类对象的被监听 ...

  5. PHP底层工作原理

    最近搭建服务器,突然感觉lamp之间到底是怎么工作的,或者是怎么联系起来?平时只是写程序,重来没有思考过他们之间的工作原理: PHP底层工作原理 图1 php结构 从图上可以看出,php从下到上是一个 ...

  6. Java并发之底层实现原理学习笔记

    本篇博文将介绍java并发底层的实现原理,我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为 ...

  7. Spring(二)IOC底层实现原理

    IOC原理 将对象创建交给Spring去管理. 实现IOC的两种方式 IOC配置文件的方式 IOC注解的方式 IOC底层实现原理 底层实现使用的技术 1.1 xml配置文件 1.2 dom4j解析xm ...

  8. iOS分类底层实现原理小记

    摘要:iOS分类底层是怎么实现的?本文将分如下四个模块进行探究分类的结构体编译时的分类分类的加载总结本文使用的runtime源码版本是objc4-680文中类与分类代码如下//类@interfaceP ...

  9. java并发编程系列七:volatile和sinchronized底层实现原理

    一.线程安全 1.  怎样让多线程下的类安全起来 无状态.加锁.让类不可变.栈封闭.安全的发布对象 2. 死锁 2.1 死锁概念及解决死锁的原则 一定发生在多个线程争夺多个资源里的情况下,发生的原因是 ...

随机推荐

  1. (window,parent,opener,top).location.reload方法汇总

    今天在火狐浏览器上碰到个bug,调用parent.location.reload()时只刷新子页面,没有整个浏览器刷新,谷歌上没有问题,网上搜了一下 改成parent.location.reload( ...

  2. Ubuntu设置终端相对短路径

    这个设置相对实际上是比较简单的.在自己的家目录打开.bashrc 找到PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$' 只需要将w修改为大写W保存, ...

  3. 传感器系列之4.12GPS定位传感器

    4.12 GPS定位实验 一.实验目的 了解GPS的基本概念 了解NMEA-0183格式数据串的组成和关于GPS的常用语句 GPS的数据串解析 二.实验材料 具有串口通讯的电脑一台 ADS1.2开发环 ...

  4. servlet context 和 servlet config

    servletConfig Servlet容器初始化一个servlet对象时,会为这个servlet对象创建一个servletConfig对象,该对象中包含了servlet的<init-para ...

  5. 《Android进阶》之第三篇 深入理解android的消息处理机制

    Android 异步消息处理机制 让你深入理解 Looper.Handler.Message三者关系 android的消息处理机制(图+源码分析)——Looper,Handler,Message an ...

  6. Nginx教程(二) Nginx虚拟主机配置

    Nginx教程(二) Nginx虚拟主机配置 1 虚拟主机管理 1.1 Nginx管理虚拟主机 虚拟主机使用的是特殊的软硬件技术,它把一台运行在因特网上的服务器主机分成一台台“虚拟”的主机,每台虚拟主 ...

  7. Dubbo微容器(Cooma)详解

    ExtensionLoader ExtensionLoader是Dubbo中的SPI的实现方法,它是Dubbo框架的微容器,也为框架提供各种组件的扩展点 三种注解 SPI Adaptive Activ ...

  8. git底层原理(二)

    git对象模型 在git系统中有四种类型的对象,所有的Git操作都是基于这四种类型的对象:"blob":这种对象用来保存文件的内容."tree":可以理解成一个 ...

  9. 弹出框插件layer使用

    layer是一款近年来备受青睐的web弹层组件,她具备全方位的解决方案,致力于服务各水平段的开发人员,您的页面会轻松地拥有丰富友好的操作体验. 插件官方地址:http://layer.layui.co ...

  10. Zxing 的集成 ---- Maven 对应 Gradle 的写法

    Zxing 的集成 ---- Maven 对应 Gradle 的写法 刚刚想耍耍二维码,想到了zxing和zbar,又想到zxing是Google老爹的,想想就算了吧,虽然zbar快但是识别错误率也高 ...