骚阿咦电影是有这个吧kvo谁给我发一个看看kvo精灵鼠小弟国语版谢谢啦?

IOS开发之----KVO的使用
上篇我们讲到了KVC,这篇我们学习KVO,全名为:Key Value Observing,直译为:基于键值的观察者。
那它有什么用呢?KVO主要用于视图交互方面,比如界面的某些数据变化了,界面的显示也跟着需要变化,那就要建立数据和界面的关联。
ObjC中提供的KVO就是解决这种问题的。以下用显示页面观察学生的课程名称变化的例子来说明KVO的使用。
学生类命名为:Student,页面类是:PageView.
上图来自苹果官网,图中的BankObject好比PageView,PersonObject好比Student,
PageView观察Student的变化。
1、添加Student学生类。
类中有name,和课程名称courseName,添加一个可以改变课程名称的方法changeCourseName。一会用来做对比,看直接改变课程名称时会不会有回调。
实现文件.m
实现类把方法实现了。
2、页面类实现
init初始化时,向student实例添加观察者,在释放的时候移除观察者。
3、实现观察
在main函数中
新建一个student的实例,设置他的课程是数学课,然后创建页面类的时候,用student初始化。这是页面类已经观察着学生的课程了。
再给课程设置新的值为化学课。这时候运行打印结果:
16:29:21.561
objectiveC[]&初始值:数学课
16:29:21.565 objectiveC[] PageView课程被改变了
16:29:21.566 objectiveC[] PageView新课程是:化学课老课程是:数学课
可以看到Pageview类中的回调被调用,Pageview接收到学生课程数据更新的信息。
4、直接改变课程信息对比
直接调用changeCourseName方法改变课程,打印结果:
16:32:06.230
objectiveC[]&初始值:数学课
16:32:06.237 objectiveC[] PageView课程被改变了
16:32:06.238 objectiveC[] PageView新课程是:化学课老课程是:数学课
16:32:06.239
objectiveC[]&直接改变的课程为:英语课
可以看到,这时Pageview的回调没被调用到。说明只有通过键值编码(KVC)改变的值,才会回调观察者注册的方法。
这里是苹果官网的关于KVO的文档,英文好的朋友可以看看:
/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/-BCICJDHA
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。KVO底层实现原理
时间: 15:53:40
&&&& 阅读:876
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&KVO是实现Cocoa Bindings的基础,它提供了一种方法,当某个属性改变时,相对的objects会被通知到。在其他语言中,这种观察者模式通常需要单独实现,而在Objective-C中,通常无须增加额外代码即可使用,
这是怎么实现的呢?其实就是通过OC强大的运行时(runtime)实现的。当你第一次观察某个对象时,runtime会创建一个新的继承原先class的subclass。在这个新的class中,它重写了所有被观察的key,然后将OC的isa指针指向新创建的class(这个指针告诉oc运行时某个object到底是哪种类型的object)。所以object神奇的变成了新的子类的实例;
这些被重写的方法实现了如何通知观察者们。当改变一个key时,会触发setkey方法,但这个方法被重写了,并且在内部添加了发送通知机制。(当然也可以i不走set方法,比如直接修改ivar,但不推荐这么做)。
有意思的是:苹果不希望这个机制暴露在外部。除了setters,这个动态生成的子类同时也重写了-class方法,依旧返回原先的class!如果不仔细看的话,被KVO过的object看起来和原先的object没什么两样。
下面是笔者写了个程序来演示隐藏在KVO背后的机制。
我们从头到尾细细看来。
首先定义了一个TestClass的类,它有3个属性。
然后定义了一些方便调试的方法。ClassMethodNames使用Objective-C运行时方法来遍历一个class,得到方法列表。注意,这些方法不包括父类的方法。PrintDescription打印object的所有信息,包括class信息(包括-class和通过运行时得到的class),以及这个class实现的方法。
然后创建了4个TestClass实例,每一个都使用了不同的观察方式。x实例有一个观察者观察xkey,y, xy也类似。为了做比较,zkey没有观察者。最后control实例没有任何观察者。
然后打印出4个objects的description。
之后继续打印被重写的setter内存地址,以及未被重写的setter的内存地址做比较。这里做了两次,是因为-methodForSelector:没能得到重写的方法。KVO试图掩盖它实际上创建了一个新的subclass这个事实!但是使用运行时的方法就原形毕露了。
看看这段代码的输出
首先,它输出了controlobject,没有任何问题,它的class是TestClass,并且实现了6个set/get方法。
然后是3个被观察的objects。注意-class仍然显示的是TestClass,使用object_getClass显示了这个object的真面目:它是NSKVONotifying_TestClass的一个实例。这个NSKVONotifying_TestClass就是动态生成的subclass!
注意,它是如何实现这两个被观察的setters的。你会发现,它很聪明,没有重写-setZ:,虽然它也是个setter,因为它没有被观察。同时注意到,3个实例对应的是同一个class,也就是说两个setters都被重写了,尽管其中的两个实例只观察了一个属性。这会带来一点效率上的问题,因为即使没有被观察的property也会走被重写的setter,但苹果显然觉得这比分开生成动态的subclass更好,我也觉得这是个正确的选择。
你会看到3个其他的方法。有之前提到过的被重写的-class方法,假装自己还是原来的class。还有-dealloc方法处理一些收尾工作。还有一个_isKVOA方法,看起来像是一个私有方法。
接下来,我们输出-setX:的实现。使用-methodForSelector:返回的是相同的值。因为-setX:已经在子类被重写了,这也就意味着methodForSelector:在内部实现中使用了-class,于是得到了错误的结果。
最后我们通过运行时得到了不同的输出结果。
作为一个优秀的探索者,我们进入debugger来看看这第二个方法的实现到底是怎样的:
看起来是一个内部方法,对Foundation使用nm -a得到一个完整的私有方法列表:
这个列表也能发现一些有趣的东西。比如苹果为每一种primitive type都写了对应的实现。Objective-C的object会用到的其实只有__NSSetObjectValueAndNotify,但需要一整套来对应剩下的,而且看起来也没有实现完全,比如long dobule或_Bool都没有。甚至没有为通用指针类型(generic
pointer type)提供方法。所以,不在这个方法列表里的属性其实是不支持KVO的。
KVO是一个很强大的工具,有时候过于强大了,尤其是有了自动触发通知机制。现在你知道它内部是怎么实现的了,这些知识或许能帮助你更好地使用它,或在它出错时更方便调试。
标签:&&&&&&
&&国之画&&&& &&&&chrome插件
版权所有 京ICP备号-2
迷上了代码!如何自己动手实现 KVO
招聘信息:
本文是 Objective-C Runtime 系列文章的第三篇。如果你对 Objective-C Runtime 还不是很了解,可以先去看看前两篇文章:本篇会探究 KVO (Key-Value Observing) 实现机制,并去实践一番 - 利用 Runtime 自己动手去实现 KVO 。KVO (Key-Value Observing)KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。有意思的是,你不需要给被观察的对象添加任何额外代码,就能使用 KVO 。这是怎么做到的?KVO 实现机制KVO 的实现也依赖于 Objective-C 强大的 Runtime 。Apple 的文档有简单提到过 KVO 的实现:Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...Apple 的文档真是一笔带过,唯一有用的信息也就是:被观察对象的 isa 指针会指向一个中间类,而不是原来真正的类。看来,Apple 并不希望过多暴露 KVO 的实现细节。不过,要是你用 runtime 提供的方法去深入挖掘,所有被掩盖的细节都会原形毕露。Mike Ash 早在 2009 年就做了这么个探究。简单概述下 KVO 的实现:当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。原来,这个中间类,继承自原本的那个类。不仅如此,Apple 还重写了 -class 方法,企图欺骗我们这个类没有变,就是原本那个类。更具体的信息,去跑一下 Mike Ash 的那篇文章里的代码就能明白,这里就不再重复。KVO 缺陷KVO 很强大,没错。知道它内部实现,或许能帮助更好地使用它,或在它出错时更方便调试。但官方实现的 KVO 提供的 API 实在不怎么样。比如,你只能通过重写 -observeValueForKeyPath:ofObject:change:context: 方法来获得通知。想要提供自定义的 selector ,不行;想要传一个 block ,门都没有。而且你还要处理父类的情况 - 父类同样监听同一个对象的同一个属性。但有时候,你不知道父类是不是对这个消息有兴趣。虽然 context 这个参数就是干这个的,也可以解决这个问题 - 在 -addObserver:forKeyPath:options:context: 传进去一个父类不知道的 context。但总觉得框在这个 API 的设计下,代码写的很别扭。至少至少,也应该支持 block 吧。有不少人都觉得官方 KVO 不好使的。Mike Ash 的 ,以及获得不少分享讨论的
都把 KVO 拿出来吊打了一番。所以在实际开发中 KVO 使用的情景并不多,更多时候还是用 Delegate 或 NotificationCenter。自己实现 KVO如果没找到理想的,就自己动手做一个。既然我们对官方的 API 不太满意,又知道如何去实现一个 KVO,那就尝试自己动手写一个简易的 KVO 玩玩。首先,我们创建 NSObject 的 Category,并在头文件中添加两个 API:typedef&void(^PGObservingBlock)(id&observedObject,&NSString&*observedKey,&id&oldValue,&id&newValue);
@interface&NSObject&(KVO)
-&(void)PG_addObserver:(NSObject&*)observer
&&&&&&&&&&&&&&&&forKey:(NSString&*)key
&&&&&&&&&&&&&withBlock:(PGObservingBlock)
-&(void)PG_removeObserver:(NSObject&*)observer&forKey:(NSString&*)
@end接下来,实现 PG_addObserver:forKey:withBlock: 方法。逻辑并不复杂:检查对象的类有没有相应的 setter 方法。如果没有抛出异常;检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;添加这个观察者-&(void)PG_addObserver:(NSObject&*)observer
&&&&&&&&&&&&&&&&forKey:(NSString&*)key
&&&&&&&&&&&&&withBlock:(PGObservingBlock)block
&&&&//&Step&1:&Throw&exception&if&its&class&or&superclasses&doesn't&implement&the&setter
&&&&SEL&setterSelector&=&NSSelectorFromString(setterForGetter(key));
&&&&Method&setterMethod&=&class_getInstanceMethod([self&class],&setterSelector);
&&&&if&(!setterMethod)&{
&&&&&&&&//&throw&invalid&argument&exception
&&&&Class&clazz&=&object_getClass(self);
&&&&NSString&*clazzName&=&NSStringFromClass(clazz);
&&&&//&Step&2:&Make&KVO&class&if&this&is&first&time&adding&observer&and&
&&&&//&&&&&&&&&&its&class&is&not&an&KVO&class&yet
&&&&if&(![clazzName&hasPrefix:kPGKVOClassPrefix])&{
&&&&&&&&clazz&=&[self&makeKvoClassWithOriginalClassName:clazzName];
&&&&&&&&object_setClass(self,&clazz);
&&&&//&Step&3:&Add&our&kvo&setter&method&if&its&class&(not&superclasses)&
&&&&//&&&&&&&&&&hasn't&implemented&the&setter
&&&&if&(![self&hasSelector:setterSelector])&{
&&&&&&&&const&char&*types&=&method_getTypeEncoding(setterMethod);
&&&&&&&&class_addMethod(clazz,&setterSelector,&(IMP)kvo_setter,&types);
&&&&//&Step&4:&Add&this&observation&info&to&saved&observation&objects
&&&&PGObservationInfo&*info&=&[[PGObservationInfo&alloc]&initWithObserver:observer&Key:key&block:block];
&&&&NSMutableArray&*observers&=&objc_getAssociatedObject(self,&(__bridge&const&void&*)(kPGKVOAssociatedObservers));
&&&&if&(!observers)&{
&&&&&&&&observers&=&[NSMutableArray&array];
&&&&&&&&objc_setAssociatedObject(self,&(__bridge&const&void&*)(kPGKVOAssociatedObservers),&observers,&OBJC_ASSOCIATION_RETAIN_NONATOMIC);
&&&&[observers&addObject:info];
}再来一步一步细看。第一步里,先通过 setterForGetter() 方法获得相应的 setter 的名字(SEL)。也就是把 key 的首字母大写,然后前面加上 set 后面加上 :,这样 key 就变成了 setKey:。然后再用 class_getInstanceMethod 去获得 setKey: 的实现(Method)。如果没有,自然要抛出异常。第二步,我们先看类名有没有我们定义的前缀。如果没有,我们就去创建新的子类,并通过 object_setClass() 修改 isa 指针。-&(Class)makeKvoClassWithOriginalClassName:(NSString&*)originalClazzName
&&&&NSString&*kvoClazzName&=&[kPGKVOClassPrefix&stringByAppendingString:originalClazzName];
&&&&Class&clazz&=&NSClassFromString(kvoClazzName);
&&&&if&(clazz)&{
&&&&&&&&return&
&&&&//&class&doesn't&exist&yet,&make&it
&&&&Class&originalClazz&=&object_getClass(self);
&&&&Class&kvoClazz&=&objc_allocateClassPair(originalClazz,&kvoClazzName.UTF8String,&0);
&&&&//&grab&class&method's&signature&so&we&can&borrow&it
&&&&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&kvoC
}动态创建新的类需要用 objc/runtime.h 中定义的 objc_allocateClassPair() 函数。传一个父类,类名,然后额外的空间(通常为 0),它返回给你一个类。然后就给这个类添加方法,也可以添加变量。这里,我们只重写了 class 方法。哈哈,跟 Apple 一样,这时候我们也企图隐藏这个子类的存在。最后 objc_registerClassPair() 告诉 Runtime 这个类的存在。第三步,重写 setter 方法。新的 setter 在调用原 setter 方法后,通知每个观察者(调用之前传入的 block ):static&void&kvo_setter(id&self,&SEL&_cmd,&id&newValue)&&
&&&&NSString&*setterName&=&NSStringFromSelector(_cmd);
&&&&NSString&*getterName&=&getterForSetter(setterName);
&&&&if&(!getterName)&{
&&&&&&&&//&throw&invalid&argument&exception
&&&&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&complain
&&&&void&(*objc_msgSendSuperCasted)(void&*,&SEL,&id)&=&(void&*)objc_msgSendS
&&&&//&call&super's&setter,&which&is&original&class's&setter&method
&&&&objc_msgSendSuperCasted(&superclazz,&_cmd,&newValue);
&&&&//&look&up&observers&and&call&the&blocks
&&&&NSMutableArray&*observers&=&objc_getAssociatedObject(self,&(__bridge&const&void&*)(kPGKVOAssociatedObservers));
&&&&for&(PGObservationInfo&*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);
&&&&&&&&&&&&});
}细心的同学会发现我们对 objc_msgSendSuper 进行类型转换。在 Xcode 6 里,新的 LLVM 会对 objc_msgSendSuper 以及 objc_msgSend 做严格的类型检查,如果不做类型转换。Xcode 会抱怨有 too many arguments 的错误。(在 WWDC 2014 的视频 What new in LLVM 中有提到过这个问题。)最后一步,把这个观察的相关信息存在 associatedObject 里。观察的相关信息(观察者,被观察的 key, 和传入的 block )封装在 PGObservationInfo 类里。@interface&PGObservationInfo&:&NSObject
@property&(nonatomic,&weak)&NSObject&*
@property&(nonatomic,&copy)&NSString&*
@property&(nonatomic,&copy)&PGObservingBlock&
@end就此,一个基本的 KVO 就可以 work 了。当然,这只是一个一天多做出来的小东西,会有 bug,也有很多可以优化完善的地方。但作为 demo 演示如何利用 Runtime 动态创建类、如何实现 KVO,足已。完整的例子可以从这里下载:如果有任何问题或找到 bug,可以邮件我
或者私信我的微博 。谢谢观赏。Reference
微信扫一扫
订阅每日移动开发及APP推广热点资讯公众号:CocoaChina
您还没有登录!请或
点击量9470点击量6606点击量6134点击量3802点击量3566点击量3552点击量2917点击量2621点击量2377
&2016 Chukong Technologies,Inc.
京公网安备89(转)如何自己动手实现 KVO - 简书
(转)如何自己动手实现 KVO
转自:本文是 Objective-C Runtime 系列文章的第三篇。如果你对 Objective-C Runtime 还不是很了解,可以先去看看前两篇文章:
本篇会探究 KVO (Key-Value Observing) 实现机制,并去实践一番 - 利用 Runtime 自己动手去实现 KVO 。KVO (Key-Value Observing)KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。有意思的是,你不需要给被观察的对象添加任何额外代码,就能使用 KVO 。这是怎么做到的?KVO 实现机制KVO 的实现也依赖于 Objective-C 强大的 Runtime 。Apple 的文档有简单提到过 KVO 的:Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...
Apple 的文档真是一笔带过,唯一有用的信息也就是:被观察对象的 isa 指针会指向一个中间类,而不是原来真正的类。看来,Apple 并不希望过多暴露 KVO 的实现细节。不过,要是你用 runtime 提供的方法去深入挖掘,所有被掩盖的细节都会原形毕露。Mike Ash 早在 2009 年就做了这么个。简单概述下 KVO 的实现:当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。原来,这个中间类,继承自原本的那个类。不仅如此,Apple 还重写了-class 方法,企图欺骗我们这个类没有变,就是原本那个类。更具体的信息,去跑一下 Mike Ash 的那篇文章里的代码就能明白,这里就不再重复。KVO 缺陷KVO 很强大,没错。知道它内部实现,或许能帮助更好地使用它,或在它出错时更方便调试。但官方实现的 KVO 提供的 API 实在不怎么样。比如,你只能通过重写 -observeValueForKeyPath:ofObject:change:context: 方法来获得通知。想要提供自定义的 selector ,不行;想要传一个 block ,门都没有。而且你还要处理父类的情况 - 父类同样监听同一个对象的同一个属性。但有时候,你不知道父类是不是对这个消息有兴趣。虽然 context 这个参数就是干这个的,也可以解决这个问题 - 在 -addObserver:forKeyPath:options:context: 传进去一个父类不知道的 context。但总觉得框在这个 API 的设计下,代码写的很别扭。至少至少,也应该支持 block 吧。有不少人都觉得官方 KVO 不好使的。Mike Ash 的 ,以及获得不少分享讨论的
都把 KVO 拿出来吊打了一番。所以在实际开发中 KVO 使用的情景并不多,更多时候还是用 Delegate 或 NotificationCenter。自己实现 KVO如果没找到理想的,就自己动手做一个。既然我们对官方的 API 不太满意,又知道如何去实现一个 KVO,那就尝试自己动手写一个简易的 KVO 玩玩。首先,我们创建 NSObject 的 Category,并在头文件中添加两个 API:typedef void(^PGObservingBlock)(id observedObject, NSString observedKey, id oldValue, id newValue);@interface NSObject (KVO)- (void)PG_addObserver:(NSObject )observer forKey:(NSString )key withBlock:(PGObservingBlock)- (void)PG_removeObserver:(NSObject )observer forKey:(NSString *)@end
接下来,实现 PG_addObserver:forKey:withBlock: 方法。逻辑并不复杂:检查对象的类有没有相应的 setter 方法。如果没有抛出异常;检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;添加这个观察者
(void)PG_addObserver:(NSObject )observer forKey:(NSString )key withBlock:(PGObservingBlock)block{ // Step 1: Throw exception if its class or superclasses doesn't implement the setter SEL setterSelector = NSSelectorFromString(setterForGetter(key)); Method setterMethod = class_getInstanceMethod([self class], setterSelector); if (!setterMethod) { // throw invalid argument exception } Class clazz = object_getClass(self); NSString clazzName = NSStringFromClass(clazz); // Step 2: Make KVO class if this is first time adding observer and
// its class is not an KVO class yet if (![clazzName hasPrefix:kPGKVOClassPrefix]) { clazz = [self makeKvoClassWithOriginalClassName:clazzName]; object_setClass(self, clazz); } // Step 3: Add our kvo setter method if its class (not superclasses)
// hasn't implemented the setter if (![self hasSelector:setterSelector]) { const char types = method_getTypeEncoding(setterMethod); class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types); } // Step 4: Add this observation info to saved observation objects PGObservationInfo info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block]; NSMutableArray observers = objc_getAssociatedObject(self, (bridge const void *)(kPGKVOAssociatedObservers)); if (!observers) { observers = [NSMutableArray array]; objc_setAssociatedObject(self, (bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } [observers addObject:info];}
再来一步一步细看。第一步里,先通过 setterForGetter() 方法获得相应的 setter 的名字(SEL)。也就是把 key 的首字母大写,然后前面加上 set 后面加上 :,这样 key 就变成了 setKey:。然后再用 class_getInstanceMethod 去获得 setKey: 的实现(Method)。如果没有,自然要抛出异常。第二步,我们先看类名有没有我们定义的前缀。如果没有,我们就去创建新的子类,并通过 object_setClass() 修改 isa 指针。
(Class)makeKvoClassWithOriginalClassName:(NSString )originalClazzName{ NSString kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName]; Class clazz = NSClassFromString(kvoClazzName); if (clazz) { } // class doesn't exist yet, make it Class originalClazz = object_getClass(self); Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0); // grab class method's signature so we can borrow it 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 kvoC}
动态创建新的类需要用 objc/runtime.h 中定义的 objc_allocateClassPair() 函数。传一个父类,类名,然后额外的空间(通常为 0),它返回给你一个类。然后就给这个类添加方法,也可以添加变量。这里,我们只重写了 class 方法。哈哈,跟 Apple 一样,这时候我们也企图隐藏这个子类的存在。最后 objc_registerClassPair() 告诉 Runtime 这个类的存在。第三步,重写 setter 方法。新的 setter 在调用原 setter 方法后,通知每个观察者(调用之前传入的 block ):static void kvo_setter(id self, SEL _cmd, id newValue) { NSString setterName = NSStringFromSelector(_cmd); NSString getterName = getterForSetter(setterName); if (!getterName) { // throw invalid argument exception } 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 complain void (objc_msgSendSuperCasted)(void , SEL, id) = (void )objc_msgSendS // call super's setter, which is original class's setter method objc_msgSendSuperCasted(&superclazz, _cmd, newValue); // look up observers and call the blocks NSMutableArray observers = objc_getAssociatedObject(self, (__bridge const void )(kPGKVOAssociatedObservers)); for (PGObservationInfo 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); }); } }}
细心的同学会发现我们对 objc_msgSendSuper 进行类型转换。在 Xcode 6 里,新的 LLVM 会对 objc_msgSendSuper 以及 objc_msgSend 做严格的类型检查,如果不做类型转换。Xcode 会抱怨有 too many arguments 的错误。(在 WWDC 2014 的视频 What new in LLVM 中有提到过这个问题。)最后一步,把这个观察的相关信息存在 associatedObject 里。观察的相关信息(观察者,被观察的 key, 和传入的 block )封装在 PGObservationInfo 类里。@interface PGObservationInfo : NSObject@property (nonatomic, weak) NSObject @property (nonatomic, copy) NSString @property (nonatomic, copy) PGObservingB@end
就此,一个基本的 KVO 就可以 work 了。当然,这只是一个一天多做出来的小东西,会有 bug,也有很多可以优化完善的地方。但作为 demo 演示如何利用 Runtime 动态创建类、如何实现 KVO,足已。完整的例子可以从这里下载:如果有任何问题或找到 bug,可以邮件我
或者私信我的微博 。谢谢观赏。Reference}

我要回帖

更多关于 红楼之林家小弟 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信