浅谈kvo

关于kvo即Key-Value Observing ,下面记下读官方文档https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html的个人总结,一方面希望能够加深对kvo的了解的,另一方面能够和大家一起讨论,如有错误,请留言指正,万分感谢。

一、什么是kvo

kvo是一个对象用来观察另一个对象(可以是自己)属性的变化,并作出处理的一种机制。当被观察对象的属性变化时,系统会发出一个通知,来通知观察对象。kvo实现机制的前提是对象遵守kvc非正式协议,所有继承于NSObject的对象,并且按一般命名规则定义属性的,都遵守kvc机制。

假设有一个Person对象,他有一个Account属性。他需要当账户发生变动时接收到一个通知,首先account要注册person为其观察者addObserver:forKeyPath: options:context:,person对象为了收到通知,需实现observeValueForKeyPath:ofObject:change:context:方法,如果不想监听账户的变化了,那么需要在person 对象dealloc调用之前,调用removeObserver:forKeyPath: 移除对账户特定属性的监听.

Options

addObserver:forKeyPath: options:context:,参数options 传入按位或运算的常量,不但影响到通知的内容,还影响到通知产生的方式,通过NSKeyValueObservingOptionOld.来获取属性变化之前的值,通过NSKeyValueObservingOptionNew获取属性变化之后的值,通过或运算 | 来同时获取变化之前和变化之后的值即传入NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew。也可传入NSKeyValueObservingOptionInitial来让被观察对象在addObserver:forKeyPath: options:context:return之前发出一个一次通知。 可以用来在observeValueForKeyPath:ofObject:change:context:初始化观察者对象属性的值。options的值传入NSKeyValueObservingOptionPrior可以在被观察对象属性的值发生变化之前,由被观察对象发出一个通知.

Context 可以包含任意的数据,并被传参到OberserveValueForKeyPath:ofObject:change:context:方法中,你可以传入NULL,但是当观察对象的父类对象同时对该KeyPath注册了观察者的时候就会产生问题,因此安全的做法是可以使用context来区分观察者是子类还是父类。你可以为所有的类定义一个context,而用keyPath来区分观察的属性,也可以为所有要观察的对象的属性来定义不同的context,然后通过不同的context来区分观察的属性。记住在找不到对应的属性时一定要调用【super observeValueForKeyPath:ofObject:change:context:]方法,因为有可能是父类的监听

举例:

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
 
     
     
- (void)registerAsObserverForAccount:(Account*)account {
    [account addObserver:self
              forKeyPath:@"balance"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];
 
    [account addObserver:self
              forKeyPath:@"interestRate"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}
需要注意的是这个方法中,对于观察者,被观察者,context都是弱引用。
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
    if (context == PersonAccountBalanceContext) {
        // Do something with the balance…
 
    } else if (context == PersonAccountInterestRateContext) {
        // Do something with the interest rate…
 
    } else {
        // Any unrecognized context must belong to super
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                               context:context];
    }
}
change Dictionary key NSKeyValueChangeKindKey包含了变化的类型,如果被观察对象的值发生了变化,那么对NSKeyValueChangeKindKey取值会获得 NSKeyValueChangeSetting.值 依赖于注册观察者时options传入的参数, NSKeyValueChangeOldKey 和 NSKeyValueChangeNewKey,分别返回变化之前的值和改变之后的值,如果被观察的对象属性是一个对象,那么直接返回该对象的值,如果是标量或者是c的结构体,那么返回NSNumber对象或者,NSValue对象。
如果被观察的对象的属性是一个to-many 关系,那么NSKeyValueChangeKindKey通过返回NSKeyValueChangeInsertionNSKeyValueChangeRemoval, or NSKeyValueChangeReplacement来表示这个对象属性所做改变的方式是插入,删除,或者是替换,而NSKeyValueChangeIndexesKey则返回一个包含所改变的索引的NSSet对象,NSKyeValueCHangeOldKey和NSKeyValueChangeNewKey则返回改变之前的集合对象和该变之后的集合对象。

当你不需要监听被观察对象属性变化的时候一定要记得调用 removeObserver: forKeyPath: context方法,并且在调用这个方法之前,一定要确认 已经注册了观察者,否者会抛出NSRangeException异常,当然可以在try-catch block里来调用捕获异常。观察对象并不会自动移除观察模式,即使它已经被dealloc掉了,因此它会继续接受被观察者对象发出的通知,而对一个被销毁的对象接受通知会导致内存异常。协议并没有提供是否是观察者或者是被观察者的方法,因此需要合理的组织代码,一般在初始化或者viewDidLoad里注册观察者,在dealloc里移除观察者。

- (void)unregisterAsObserverForAccount:(Account*)account {
    [account removeObserver:self
                 forKeyPath:@"balance"
                    context:PersonAccountBalanceContext];
 
    [account removeObserver:self
                 forKeyPath:@"interestRate"
                    context:PersonAccountInterestRateContext];
}

	

二、如何手动控制kvo的过程

那么什么样的类的属性是遵守kvo的呢?1、首先这个类的属性必须是遵守kvc的2这个类为这个属性的变化发送kvo通知3、所依赖的key要被正确注册

什么情况下改变属性会发送kvo通知呢?1.使用通用的set 方法,2.使用kvc中的setValueForKey:和setValueForKeyPath:都会使类自动发送kvo通知。

手动发送kvo通知:在有些情况下,你想控制发送通知的过程,来减少不必要的通知,或者是将一组数据的变化通知,改成一个通知。想控制通知的发送过程需要重写automaticallyNotifiesObserversForKey:类方法。对于手动管理的通知的属性返回NO, 对于不手动控制通知过程的属性,用super调用该方法。举例如下:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
 
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"balance"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}

手动控制通知的发送需要调用willChangeValueForKey:和didChangeValueForKey:方法 

- (void)setBalance:(double)theBalance {
    if (theBalance != _balance) {
        [self willChangeValueForKey:@"balance"];
        _balance = theBalance;
        [self didChangeValueForKey:@"balance"];
    }
如果是多个属性,那么需要进行嵌套:

- (void)setBalance:(double)theBalance {
    [self willChangeValueForKey:@"balance"];
    [self willChangeValueForKey:@"itemChanged"];
    _balance = theBalance;
    _itemChanged = _itemChanged+1;
    [self didChangeValueForKey:@"itemChanged"];
    [self didChangeValueForKey:@"balance"];
}
对于一个有序的to-many关系的属性,当它变化时,你不但要指出它的key,还要指出变化的类型以及变化元素的下标

- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
    [self willChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];
 
    // Remove the transaction objects at the specified indexes.
 
    [self didChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];
}

三、关于有依赖关系的Keys kvo的处理

在许多情况下一个对象的属性值,依赖于其他对象的多个属性的值,因此当依赖对象的一个属性值发生变化时,需要收到通知。例如一个人的名字包含姓和名,获取全名的getter方法如下

- (NSString *)fullName {
    return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}
当一个对象监听fullName的变化,它需要在firstName改变和LastName属性改变的时候都接收到通知,那么可以有两种方法来注册这种依赖关系1.重写

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key方法

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
 
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
 
    if ([key isEqualToString:@"fullName"]) {
        NSArray *affectingKeys = @[@"lastName", @"firstName"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}
2.实现方法,keyPahtsForValuesAffecting<Key>

+ (NSSet *)keyPathsForValuesAffectingFullName {
    return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}
但是对于集合类的属性这两个方法都不行,假设有一个Department对象,有一个 to-many relationship的属性employees(对于Employee对象),而Employee有一个salary属性。而Department对象有一个属性totalSalary,依赖于emplyees中所有Employee对象的salary属性。那么可以Department对象注册所有employees中Employee对象属性salary的监听,并负责移除监听

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
 
    if (context == totalSalaryContext) {
        [self updateTotalSalary];
    }
    else
    // deal with other observations and/or invoke super...
}
 
- (void)updateTotalSalary {
    [self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];
}
 
- (void)setTotalSalary:(NSNumber *)newTotalSalary {
 
    if (totalSalary != newTotalSalary) {
        [self willChangeValueForKey:@"totalSalary"];
        _totalSalary = newTotalSalary;
        [self didChangeValueForKey:@"totalSalary"];
    }
}
 
- (NSNumber *)totalSalary {
    return _totalSalary;
}


CSDN海神之光上传的代码均可运行,亲测可用,直接替换数据即可,适合小白; 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2019b或2023b;若运行有误,根据提示修改;若不会,私信博主; 3、运行操作步骤 步骤一:将所有文件放到Matlab的当前文件夹中; 步骤二:双击打开main.m文件; 步骤三:点击运行,等程序运行完得到结果; 4、仿真咨询 如需其他服务,可私信博主或扫描博客文章底部QQ名片; 4.1 博客或资源的完整代码提供 4.2 期刊或参考文献复现 4.3 Matlab程序定制 4.4 科研合作 功率谱估计: 故障诊断分析: 雷达通信:雷达LFM、MIMO、成像、定位、干扰、检测、信号分析、脉冲压缩 滤波估计:SOC估计 目标定位:WSN定位、滤波跟踪、目标定位 生物电信号:肌电信号EMG、脑电信号EEG、心电信号ECG 通信系统:DOA估计、编码译码、变分模态分解、管道泄漏、滤波器、数字信号处理+传输+分析+去噪(CEEMDAN)、数字信号调制、误码率、信号估计、DTMF、信号检测识别融合、LEACH协议、信号检测、水声通信 1. EMD(经验模态分解,Empirical Mode Decomposition) 2. TVF-EMD(时变滤波的经验模态分解,Time-Varying Filtered Empirical Mode Decomposition) 3. EEMD(集成经验模态分解,Ensemble Empirical Mode Decomposition) 4. VMD(变分模态分解,Variational Mode Decomposition) 5. CEEMDAN(完全自适应噪声集合经验模态分解,Complementary Ensemble Empirical Mode Decomposition with Adaptive Noise) 6. LMD(局部均值分解,Local Mean Decomposition) 7. RLMD(鲁棒局部均值分解, Robust Local Mean Decomposition) 8. ITD(固有时间尺度分解,Intrinsic Time Decomposition) 9. SVMD(逐次变分模态分解,Sequential Variational Mode Decomposition) 10. ICEEMDAN(改进的完全自适应噪声集合经验模态分解,Improved Complementary Ensemble Empirical Mode Decomposition with Adaptive Noise) 11. FMD(特征模式分解,Feature Mode Decomposition) 12. REMD(鲁棒经验模态分解,Robust Empirical Mode Decomposition) 13. SGMD(辛几何模态分解,Spectral-Grouping-based Mode Decomposition) 14. RLMD(鲁棒局部均值分解,Robust Intrinsic Time Decomposition) 15. ESMD(极点对称模态分解, extreme-point symmetric mode decomposition) 16. CEEMD(互补集合经验模态分解,Complementary Ensemble Empirical Mode Decomposition) 17. SSA(奇异谱分析,Singular Spectrum Analysis) 18. SWD(群分解,Swarm Decomposition) 19. RPSEMD(再生相移正弦辅助经验模态分解,Regenerated Phase-shifted Sinusoids assisted Empirical Mode Decomposition) 20. EWT(经验小波变换,Empirical Wavelet Transform) 21. DWT(离散小波变换,Discraete wavelet transform) 22. TDD(时域分解,Time Domain Decomposition) 23. MODWT(最大重叠离散小波变换,Maximal Overlap Discrete Wavelet Transform) 24. MEMD(多元经验模态分解,Multivariate Empirical Mode Decomposition) 25. MVMD(多元变分模态分解,Multivariate Variational Mode Decomposition)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值