利用runtime实现KVO
KVO实现原理
一.关于KVO
KVO(Key-Value Observing)提供一种机制,当指定对象的属性被修改后,就会通知观察者。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
KVO其实也是“观察者”设计模式的一种应用。这种模式有利于两个类间的解耦合,尤其是对于业务逻辑与视图控制这两个功能的解耦合。
二.KVO用法
假设已经有一个Person类,包含name属性,我们可以通过KVO监听到name属性被修改的事件:
_p = [Person new];
[_p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
即当_p对象的name属性被修改后,会调用self的以下方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{NSLog(@"kvo监听到%@对象的%@属性被修改为%@",object,keyPath,[change objectForKey:@"new"]);
}
三.KVO实现原理
当调用以下函数前:
[_p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
我们可以看到_p的isa指针指向类Person:
但是当单步执行过后,_p的isa指针会变为指向NSKVONotifying_Person类
:
即对于上面的代码,系统会执行如下操作:
1、利用runtime动态创建Person类的子类NSKVONotifying_Person,重写name属性的set方法:
-(void)setName:(NSString*)name{
[self willChangeValueForKey:@"name"];
[super setName:name];
[self didChangeValueForKey:@"name"];
}
2、将p对象的isa指针指向新建的子类
当我们修改_p的name属性时,会执行NSKVONotifying_Person类对象的set方法。
四.自己实现KVO
我们可以按照上面的思路,自己用代码实现KVO的基本功能,同时优化为以block的方式进行回调而非使用-observeValueForKeyPath:ofObject:change:context:方法。
1.新建一个NSObjct的Category,声明添加监听和移除监听方法:
NSObject+KVO.h
typedef void(^JXObserveingBlock)(id observer,NSString *key,id oldValue,id newValue);@interface NSObject (KVO)-(void)JX_addObserver:(NSObject *)observer forKeyPath:(NSString *)key block:(JXObserveingBlock*)block;-(void)JX_removeObserver:(NSObject *)observer forKey:(NSString *)key;@end
2.实现JX_addObserver:forKeyPath:block:方法:
-(void)JX_addObserver:(NSObject *)observer forKeyPath:(NSString *)key block:(JXObserveingBlock)block{//1.检查对象是否存在对应的set方法,没有则抛出异常。SEL setterSelector = NSSelectorFromString([self setterForGetter:key]);Method setterMethod = class_getInstanceMethod([self class], setterSelector);if (!setterMethod) {// throw invalid argument exception}//2.检查isa指针指向的类是否已经是KVO的类,不是则新建原类的子类,并把isa指针指向新类。Class class = object_getClass(self);NSString *className = NSStringFromClass(class);if (![className hasPrefix:kJXKVOClassPrefix]) {class = [self makeKvoClassWithOriginalClassName:className];object_setClass(self, class);}//3.检查KVO类是否已经重写了set方法,没有则重写set方法。if (![self hasSelector:setterSelector]) {const char *types = method_getTypeEncoding(setterMethod);class_addMethod(class, setterSelector, (IMP)kvo_setter, types);}//添加观察者JXObservationInfo *info = [[JXObservationInfo alloc] initWithObserver:observer Key:key block:block];NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void*)kJXKVOAssociatedObservers);if (!observers) {observers = [NSMutableArray array];objc_setAssociatedObject(self, (__bridge const void*)kJXKVOAssociatedObservers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}[observers addObject:info];
}
其中setterForGetter:方法是获取属性对应的set方法名:
-(NSString*)setterForGetter:(NSString*)key{if (key.length <= 0) {return nil;}//1.首字母大写key = [key capitalizedStringWithLocale:[NSLocale currentLocale]];//2.添加set前缀和:后缀NSString *setter = [NSString stringWithFormat:@"set%@:",key];return setter;
}
hasSelector:方法判断是否已经重写了set方法
- (BOOL)hasSelector:(SEL)selector{Class clazz = object_getClass(self);unsigned int methodCount = 0;Method* methodList = class_copyMethodList(clazz, &methodCount);for (unsigned int i = 0; i < methodCount; i++) {SEL thisSelector = method_getName(methodList[i]);if (thisSelector == selector) {free(methodList);return YES;}}free(methodList);return NO;
}
makeKvoClassWithOriginalClassName:方法根据类名返回kvo类:
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName{NSString *kvoClassName = [kJXKVOClassPrefix stringByAppendingString:originalClazzName];Class clazz = NSClassFromString(kvoClassName);if (clazz) {return clazz;}//如果还没创建对应的kvo类,则创建Class originalClazz = object_getClass(self);Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClassName.UTF8String, 0);//重写class方法Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));const char *types = method_getTypeEncoding(clazzMethod);class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);objc_registerClassPair(kvoClazz);return kvoClazz;
}static Class kvo_class(id self, SEL _cmd)
{return class_getSuperclass(object_getClass(self));
}
最后,重写setter 方法:
static void kvo_setter(id self, SEL _cmd, id newValue)
{NSString *setterName = NSStringFromSelector(_cmd);NSString *getterName = getterForSetter(setterName);if (!getterName) {NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];@throw [NSException exceptionWithName:NSInvalidArgumentExceptionreason:reasonuserInfo:nil];return;}id oldValue = [self valueForKey:getterName];struct objc_super superclazz = {.receiver = self,.super_class = class_getSuperclass(object_getClass(self))};// cast our pointer so the compiler won't complainvoid (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;// call super's setter, which is original class's setter methodobjc_msgSendSuperCasted(&superclazz, _cmd, newValue);// look up observers and call the blocksNSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kJXKVOAssociatedObservers));for (JXObservationInfo *each in observers) {if ([each.key isEqualToString:getterName]) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{each.block(self, getterName, oldValue, newValue);});}}
}static NSString * getterForSetter(NSString *setter)
{if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {return nil;}// 移除‘set’前缀以及后面的‘:’NSRange range = NSMakeRange(3, setter.length - 4);NSString *key = [setter substringWithRange:range];// 首字母小写NSString *firstLetter = [[key substringToIndex:1] lowercaseString];key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)withString:firstLetter];return key;
}
五.测试实现的KVO
把原来的:
[_p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
修改为:
[_p JX_addObserver:self forKeyPath:@"name" block:^(id observer, NSString *key, id oldValue, id newValue) {NSLog(@"kvo监听到%@对象的%@属性被修改为%@",observer,key,newValue);}];
在修改_p的name属性之后,会打印出修改信息。
完整代码下载:http://download.csdn.net/detail/dolacmeng/9849000
参考:http://tech.glowing.com/cn/implement-kvo/
利用runtime实现KVO相关推荐
- 利用Runtime修改UIdatePicker的字体颜色
用过苹果原生UIdatePicker的Ios开发者都知道,UIdatePicker这个时间选择器的字体颜色默认的是黑色,并且官方API并没有提供可以修改字体颜色的API.如下: UIDatePicke ...
- 利用Runtime类,来操作电脑关机。。
import java.io.IOException; public class Demo01 { public static void main(String[] args) throws Exce ...
- iOS 利用RunTime检测控制器是否销毁
本文是在控制器的基础上添加了一个 Category ,只需导入 Category 并引用头文件,不需要在项目里引用其他代码,就可以检测控制器是否被销毁.具体代码如下: UIViewController ...
- 8 iOS中KVO 的本质
前言本质 Automatic key-value observing is implemented using a technique called isa-swizzling 这计划的意思就是 自动 ...
- Runtime知识点整理
Runtime介绍 Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象.进行消息传递和转发. Runtime就是这个运行时系统. Runti ...
- 浅谈runtime运行时机制
由于OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法. 下面就介绍运行时一种很简单的使 ...
- KVO 从基本使用到原理剖析
文章目录 1. 简介 2. 基本使用 2.1 设置观察者 2.2 接收属性改变消息 2.3 移除观察者 2.4 KVO 使用实例 3. 原理剖析 3.1 KVO 的实现 3.2 NSKVONotify ...
- runtime实现的机制是什么,怎么用,一般用于干嘛. 你还能记得你所使用的相关的头文件或者某些方法的名称吗?...
运行时机制,runtime库里面包含了跟类.成员变量.方法相关的API,比如获取类里面的所有成员变量,为类动态添加成员变量,动态改变类的方法实现,为类动态添加新的方法等 需要导入<objc/me ...
- iOS的runtime运行时机制
本文转自http://www.cnblogs.com/guoxiao/p/3583432.html 最近一直在研究runtime运行时机制的问题,我想可能也有很多人不太清楚这个问题吧?在这里跟大家沟通 ...
最新文章
- IE和火狐都支持的方法(输入用户名和密码后按下 enter 键)
- MySQL基础:数据类型
- 前端开发代码架构相关想法
- scrapy从入门到放弃 学习项目2
- django-allauth定制模板(转载)
- spring mvc学习(45):springMVC的三大组件
- java 前台商品展示模块_SSH框架网上商城项目第10战之搭建商品类基本模块
- #pragma comment使用
- 如何在delphi里面控制Edit只能输入数字
- 技术总监和CTO的区别 浅谈CTO的作用----软件公司如何开源节流
- adventureworks mysql_AdventureWorks相关
- Error: The method ‘DioHttpHeaders.add‘ has fewer named arguments than those of overridden method
- matlab 非圆齿轮,非圆齿轮参数化设计关键技术研究
- iOS程序员为啥都爱用Mac电脑
- ActiveMQ学习笔记(二十三)Comsumer高级特性2
- 微信退款服务器系统失败怎么办,微信缴费失败怎么退款?能退回吗?
- win10服务器 稳定性,如何检测Win10系统稳定性?
- 宕机三个月、36亿打水漂,印度骄傲Infosys如何活成了全球笑话?
- (二)Redis数据类型和操作
- Unity初级工程师面试指导
热门文章
- 一些有用的Python问题
- webservice发送字符串
- 【2012天津区域赛】部分题解 hdu4431—4441
- .net之工作流工程展示及代码分享(二)工作流引擎
- flash模拟EEROM
- android各种权限及说明
- RHEL5(CentOS)下nginx+php+mysql+tomcat+memchached配置全过程(转)
- Rank() over()的用法
- valgrind——hisi平台valgrind
- OpenCV 【六】————youtu(图像)——旋转保存图片