这篇文章简单介绍苹果的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. 如何优雅地运用 Chrome (Google)

    已经在很多工具类文章前言中,提及使用工具的重要性:以至于在写这篇时候,大为窘迫:穷尽了脑海中那些名句箴言,目测都已然在先前文章中被引用.鉴于杳让人心底意识到工具的重要性,并且能践行之,远比介绍如何使用 ...

  2. 使用Android studio作按键切换界面

    一.新建工程 二.新建一个按键             android:layout_width="wrap_content"         android:layout_hei ...

  3. Lua学习(1)——table

    table类型实现了“关联数组”.“关联数组”是一种具有特殊索引方式的数组.不仅可以通过证书来索引它,还可以使用字符串或其他类型(除了nil)来索引它.table是Lua中主要的数据结构机制(事实也是 ...

  4. php实现题目抢答、商品秒杀等类型的需求

    最近和其他部门合作项目,当然我是负责php接口方面的工作,get到一些东西,所以来分享记录一下. 项目需求: 题目将通过主持人ipad投射至大屏幕,选手按'抢答'按钮进行抢答.抢答成功,选手所在组,以 ...

  5. [原创]nagios搭建配置

    nagios搭建配置 一.环境 ubuntu 14.04系统 host1:172.17.0.2 serverhost2:172.17.0.3 client 二.安装 1.在两个主机上都执行一下命令: ...

  6. Java经典编程题50道之八

    求s=a+aa+aaa+aaaa+aa...a的值,其中a是一个数字.例如2+22+222+2222+22222(此时共有5个数相加),几个数相加由键盘控制. public class Example ...

  7. 关于用css实现文本和图片垂直水平居中

    关于用css实现文本和图片垂直水平居中   一直相信好记性不如烂笔头,最近遇到很多用到垂直居中的,整理一下以便日后查阅. 一.文本垂直水平居中 1.水平居中: 文字水平居中没什么好说的,用text-a ...

  8. hdu4638

    hdu4638 题意 给定一个序列,序列由1-N个元素全排列而成,求任意区间可组成的连续的段数,比如[1,2,4]两段{[1,2],[4]},[1,2,4,3]一段{[1,2,3,4]}. 对于查询的 ...

  9. 一天搞定CSS: 清除浮动(float)--13

    上一节已经说明了为什么要清除浮动了.这里我们就来解决浮动产生的各种问题. 为什么要清楚浮动? 地址:http://blog.csdn.net/baidu_37107022/article/detail ...

  10. PHP的SQL注入技术实现以及预防措施

    SQL 攻击(SQL injection,台湾称作SQL资料隐码攻击),简称注入攻击,是发生于应用程序之数据库层的安全漏洞.简而言之,是在输入的字符串之中注入SQL指 令,在设计不良的程序当中忽略了检 ...