自己手动实现 KVO(来源:Glow 技术团队博客)

REST-meta-MDD Data总结 sMRI数据使用:nwc1、nwc2、nwc3(同样数据集相关论坛中有相关信息指向需要使用wc1、wc2、wc3)多方咨询与讨论后决定使用nwc数据,这里说明wc数据相对wc数据要大的多如wc1的大小为1.33GB,而nwc1的数据却有6.90GB。VBM文件:/REST-meta-MDD/Sharing/REST-meta-MDD-VBM- phase1 -Sharing.tar.gz。1、Excel中的表型信息:1-男,2-女;-9999或空白表示缺少数据。 阅读详情

本文是 Objective-C Runtime 系列文章的第三篇。如果你对 Objective-C Runtime 还不是很了解,可以先去看看前两篇文章:

Objective-C Runtime

Method Swizzling 和 AOP 实践

本篇会探究 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 的 Key-Value Observing Done Right,以及获得不少分享讨论的 KVO Considered Harmful 都把 KVO 拿出来吊打了一番。所以在实际开发中 KVO 使用的情景并不多,更多时候还是用 Delegate 或 NotificationCenter。

自己实现 KVO

如果没找到理想的,就自己动手做一个。既然我们对官方的 API 不太满意,又知道如何去实现一个 KVO,那就尝试自己动手写一个简易的 KVO 玩玩。

首先,我们创建 NSObject 的 Category,并在头文件中添加两个 API:

1
2
3
4
5
6
7
typedef void(^PGObservingBlock)(id observedObject, NSString *observedKey, id oldValue, id newValue);
@interface NSObject (KVO)
- (void)PG_addObserver:(NSObject *)observer
                 forKey:(NSString *)key
              withBlock:(PGObservingBlock)block;
- (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end

接下来,实现 PG_addObserver:forKey:withBlock: 方法。逻辑并不复杂:

  • 检查对象的类有没有相应的 setter 方法。如果没有抛出异常;

  • 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类;

  • 检查对象的 KVO 类重写过没有这个 setter 方法。如果没有,添加重写的 setter 方法;

  • 添加这个观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (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 指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
     NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
     Class clazz = NSClassFromString(kvoClazzName);
     if  (clazz) {
         return  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  kvoClazz;
}

动态创建新的类需要用 objc/runtime.h 中定义的 objc_allocateClassPair() 函数。传一个父类,类名,然后额外的空间(通常为 0),它返回给你一个类。然后就给这个类添加方法,也可以添加变量。这里,我们只重写了 class 方法。哈哈,跟 Apple 一样,这时候我们也企图隐藏这个子类的存在。最后 objc_registerClassPair() 告诉 Runtime 这个类的存在。

第三步,重写 setter 方法。新的 setter 在调用原 setter 方法后,通知每个观察者(调用之前传入的 block ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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_msgSendSuper;
     // 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 类里。

1
2
3
4
5
@interface PGObservationInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) PGObservingBlock block;
@end

就此,一个基本的 KVO 就可以 work 了。当然,这只是一个一天多做出来的小东西,会有 bug,也有很多可以优化完善的地方。但作为 demo 演示如何利用 Runtime 动态创建类、如何实现 KVO,足已。

完整的例子可以从这里下载:ImplementKVO

如果有任何问题或找到 bug,可以邮件我 peng@glowing.com 或者私信我的微博 @no_computer

谢谢观赏。

Reference

KVO Implementation

Creating Classes at Runtime in Objective-C

Key-Value Observing Done Right

By your command

Associated Objects

ComfyUI 提示Allocation on device 显存占满的解决方法及秋叶整合包的优化方法 最近在玩ComfyUI的时候,发现一个让人崩溃的问题——VRAM一直占满,最终报错: 简单来说,就是显卡显存溢出了,导致程序无法继续运行。尤其是一些大模型或者复杂工作流,很容易让显存飙升到极限。那怎么办?直接在启动命令后加上 ,例如: 这个参数的作用是啥? 默认情况下,ComfyUI会尽量让模型一直留在显存里,即使有些部分暂时用不到,导致显存迟早被填满。而 可以强制让ComfyUI把数据卸载到常规内存,减少显存占用。如果你的显存真的不够用,还可以再加 参数: 这个参数的意思就是开启低显存模式,进一步优化 阅读详情

相关推荐

【硬件】LT3763中文手册

1)精确控制输入和输出电流;2) 3000:1真彩色PWM™调光;3) ±1.5%电压调节精度;4) ±6%电流调节精度;5)6V至60 V输入电压范围;6)宽输出范围最高可达55V;7)<2µA关断电流;8)用于负载电流热控制的控制引脚;9)输入和输出电流监控和限制;10)开路、短路和C/10故障检测;11)适用于LED应用的PWM驱动器输出;12)热增强型28引脚FE封装。

qq_39200110的博客 459

手动实现KVO

    前言 KVO(Key-Value Observing, 键值观察), KVO实现也依赖于runtime. 当你对一个对象进行观察时, 系统会动态创建一个类继承自原类, 然后重写被观察属性的setter方法. 然后重写的setter方法会负责在调用原setter方法前后通知观察者. KVO还会修改原对象的isa指针指向这个新类. 我们知道, 对象是通过isa指针去查找自己是属于哪个类, ...

weixin_33696106的博客 120

ZYNQ系列PS侧和PL侧DDR3或DDR4在AD(Altium Designer 25)中的layout布线规则约束

DDR3或DDR4的PCB布线规则通常有相对固定的要求,对应的,在PCB设计时有固定的约束设置。

FPGA艺术家的博客 745

关于KVO的一些总结------已完结

关于KVO的一些总结

ACERROR 2644

KVO用法总结

一.概述:KVO全称KeyValue Observing,是苹果提供的一套事件通知机制。 作用:允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。 注意:由于KVO实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。 使用KVO只需要两个步骤: (1)注册Observer; (2)接收通知。   (3)   当观察者不需要监听时,可以...

Null959_的博客 3795

kvo实现原理_KVO实现原理

有关KVO (Key-Value Observing)大家一定不会觉得陌生,常常被用来监听某个对象属性值的改变。那么有关底层实现原理是需要来探讨的,今天就来说说KVO的基本使用以及实现原理。什么是KVOKVO (Key-Value Observing) 是 Objective-C 对观察者模式(Observer Pattern)实现。当被观察者对象的某个被观察属性发生变化时,观察者对象会获得通知...

weixin_30753213的博客 586

KVO-理解与简单使用

KVO 就是key value observing (键值监听 /观察者模式)/是一种回调机制 观察者模式 : 一个目标对象管理所有依赖于他的观察者对象 /并在它自身的状态改变时主动通知观察者对象 /这个制动通知通常是通过调用各观察者对象所提供的接口方法来实现的 /观察者模式比较完美的将目标对象和观察者对象解耦 简单来说 : 在某个对象注册监听者后/在被监听的对象发生改...

aixiwan6424的博客 130

如何自动动手实现KVO

本篇会探究 KVO (Key-Value Observing) 实现机制,并去实践一番 - 利用 Runtime 自己动手去实现 KVOKVO (Key-Value Observing) KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。

慢跑蜗牛的专栏 534

如何自己动手实现 KVO

原文地址:点击打开链接 本文是 Objective-C Runtime 系列文章的第三篇。如果你对 Objective-C Runtime 还不是很了解,可以先去看看前两篇文章: Objective-C RuntimeMethod Swizzling 和 AOP 实践 本篇会探究 KVO (Key-Value Observing) 实现机制,并去实践一番 - 利用 Runtime 自己动

563

iOS_模拟KVO的底层实现手动实现KVO(附源码)

转载至:http://www.jianshu.com/p/5b43a527ac73 以前出去面试,会遇到被问到KVO和runtime的面试题,很多朋友基本上都能秒杀,滔滔不绝的说什么是KVO,什么是runtime,甚至还会说出他们能干什么。但是最近发现出去面试回来的朋友都反映会被问到KVO的底层实现,好吧也是能秒杀的,就是动态的创建一个派生类,在派生类里实现监听。 然而今天

YLGWHYH的专栏 1005

iOS开发-黑科技防止多次添加删除KVO出现的问题

一、使用场景 有时候我们会忘记添加多次KVO监听或者,不小心删除如果KVO监听,如果添加多次KVO监听这个时候我们就会接受到多次监听。 如果删除多次kvo程序就会造成catch,如下图 这时候我们就可以想一些方案来防止这种情况的发生。 二、使用技术 核心 : 利用runtime实现方法交换,进行拦截add和remove进行操作。 方案一 :利用 @try @ca

klabcxy36897的专栏 9684

kvo的observationInfo

观察者信息的注册: <NSKeyValueObservationInfo 0x600000708d60> ( <NSKeyValueObservance 0x6000009143f0: Observer: 0x7f9a0b508ef0, Key path: tpStr, Options: <New: YES, Old: YES, Prior: NO&gt...

diaoju3333的博客 448

iOS底层探索之KVO()—自定义KVO

回顾 在上篇博客已经自定义了KVO,但是还没有完善,还有些问题需要解决,这么本篇博客就把自定义KVO进行完善。 1. 观察者信息保存问题 在上一篇的博客中,自定义KVO的简单逻辑是已经实现了,但是这里还是存在一个大的问题,就是如果我们要观察多个属性的时候,以及新值和旧值,都要观察以及传递了context的情况下就无效了。 解决的办法就是,我们需要保存观察者相关的信息,那么就创建一个新类JPKVOInfo保存,代码的实现如下: typedef NS_OPTIONS(NSUInteger, JPKeyValue

俊而不逊的博客 511

知名技术团队博客

国内技术团队博客 1  http://tech.meituan.com/ 美团点评技术团队 2  http://fe.meituan.com/   美团前端技术团队 3  http://tech.uc.cn/       UC技术团队 4  http://tencentdba.com/   腾讯游戏DBA团队 5  http://noops.me/         小米运维部非官方博客

2万+

KVO底层实现--写一个自己的KVO

KVO底层原理   Person * p =[[Person alloc] init]; [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; p这个对象一旦添加观察者后,系统会将这个Person这个类的isa指针修改为NSKVONotifying_Pe

petyou123的博客 1279

iOS 手动实现KVO / iOS KVO底层原理

手动实现KVO git demo地址 首先将一下Apple KVO的底层实现吧 内部实现原理 KVO是基于机制实现的,运用了一个技术.就是类型混合指针机制, 将2个对象的isa指针互相调换, 就是俗称的黑魔法. 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制 如果原类为MyPerson,那么生成的派生类名为NSKVONotifying_MyPerson 每个类对象

风起云涌的博客 1026

iOS自己实现KVO

KVO是什么 一下是摘自苹果官网的一段话 Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects KVO其实就是可以让开发人员观察到对象特定属性变化的一种机制 重要一点:为了能理解KVO,首先要理解KVC。...

weixin_34321977的博客 368

ios-手动实现KVO简单版

知道KVO实现原理的应该都知道,其实在我们去设置监听对象的属性的时候,对象的类会在运行时被改变,改成系统生成的一个类,会在这个子类中重写被观察属性的方法,当然也会去调用原本属于那个类中的set方法。关于KVO实现可以详细请看http://blog.csdn.net/zcmuczx/article/details/78620680 所以我们当然也可以利用runtime来进行模拟这个过程,下

rv0p111 1364

[iOS进阶]自己动手写KVO总结

KVO原理: 当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

shawjan的专栏 1388

Runtime实践

Runtime实践基础总结:-对类操作:class_继承体系struct objc_class { Class isa OBJC_ISA_AVAILABILITY; };struct objc_object { Class isa OBJC_ISA_AVAILABILITY; };## Method Swizzling,面向切面编程(另一种实现方式:继承) - Meth

shenhualxt的专栏 1009

KVO实现原理和具体应用

一、什么是KVOKVO(key-value observing)是Objective-C对观察者设计模式的一种实现。【另一种是:通知机制(notification),详情参考:iOS 趣谈设计模式——通知】;KVO提供一种机制,指定一个被观察的对象(A类),当对象某个属性(A中的属性name)发生更改时,对象会获得通知,并作出相应处理;【且不需要给被观察的对象添加任何额外代码,就能使用KVO机制...

陈荣科的博客 8865
上一篇: iOS App提交指南-协议、税务和银行业务
下一篇: 迭代器 ----- 设计模式
FirstBloodFB
博客等级 码龄12年 7粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值