这篇文章简单介绍苹果的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. 看完48秒动画,让你不敢再登录HTTP网站(附完整示例代码)

    在我的 单点登录SSO示例代码 一文中,强烈不建议部署HTTP的SSO服务站点. 在此写个基于网络包嗅探的HTTP会话劫持程序,给大家一个直观的危害性展示. 示例中,我在一台Mac上登录58同城,被另 ...

  2. JUnit学习

    很早以前就知道JUnit也知道它用来做单元测试.今天突然又想到还是要学一下这个JUnit,不然说出去不知道怎么用JUnit做单元测试……作为一个程序员怪丢人的.第一篇JUnit不算是一个总结性的文章, ...

  3. TDE: Transparent Data Encryption brief introduction

    1. What is TDE? Briefly speaking, TDE is used to encrypted data. 2. The benifits: Belows are come fr ...

  4. Hibernate createQuery调用joincolumn

    1. Beans a. Version Bean package locationService.beans; import java.sql.Timestamp; import java.util. ...

  5. shell脚本中$参数的介绍

    $$Shell本身的PID(ProcessID)$!Shell最后运行的后台Process的PID$?最后运行的命令的结束代码(返回值)$-使用Set命令设定的Flag一览$*所有参数列表.如&quo ...

  6. Spring自动化装配bean

    1. 场景 用CD(Compact disc)和CD播放器(CDPlayer)阐述DI(依赖注入). 如果不将CD插入(注入)到CDPlayer中,那么CDPlayer其实没有太大的用处,所以,可以这 ...

  7. 关于MS12-020一次简单尝试

    由于之前着重于web漏洞,主机漏洞这块比较薄弱.也没有用过metasploit,对于很多系统漏洞还不熟悉,正好这几天不忙,就想着慢慢学习,再写点简单的东西,进行总结记录. 这次尝试的是MS12-020 ...

  8. PHP漏洞之session会话劫持

    本文主要介绍针对PHP网站Session劫持.session劫持是一种比较复杂的攻击方法.大部分互联网上的电脑多存在被攻击的危险.这是一种劫持tcp协议的方法,所以几乎所有的局域网,都存在被劫持可能. ...

  9. html5-表格的建立

    用表格显示信息调理清楚,使浏览者一目了然.表格在网页中还有协助布局的作用,可以把文字.图像等组织到表格的不同行列.那么,接下来我将讲解一下表格的常用属性. 首先,表格命令 表格的行:tr  每行中的列 ...

  10. 水平方向的RecyclerView

    最近做了一个项目需要实现一个卡片式的水平滑动,但是不能手势滑动,点击卡片上的按钮之后滑动到下一个卡片,所以想到用RecyclerView实现,去掉它的手势滑动,点击按钮之后再代码控制滑动到下一个卡片. ...