1、KVO简介
KVO是键值观察Key-Value Observing的简称,在iOS开发中,可使用KVO来监听一个对象属性的变化。
2、KVO的使用
2.1对某个类添加监听
//content后面填NULL不要填nil,官方介绍的,OC是C的超集
[self.person addObserver:self forKeyPath:@"nick" options:(NSKeyValueObservingOptionNew) context:NULL];
1.self.person是LGPerson的实例对象
2.给LGPerson的实例对象self.person添加观察者self,监听self.person的属性nick的新值变化(NSKeyValueObservingOptionNew)
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];
}
- (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];
}
}
2.2观察者的回调方法。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
}
当观察者的属性变化时,会来到这个回调方法,keyPath是被监听的属性,object是被观察的对象,change是数据的变化。
2.3移除观察者
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"nick"];
}
2.4数组类型的属性监听。
self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
// 5: 可变数组 KVO --
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
// KVO 建立在 KVC
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];
对象的属性-数组,需要使用KVC的形式赋值才能监听
2.5多个因素综合影响
// 4: 多个因素影响 - 下载进度 = 当前下载量 / 总量
[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];
#import "LGPerson.h"
@implementation LGPerson
// 下载进度 -- writtenData/totalData
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"downloadProgress"]) {
NSArray *affectingKeys = @[@"totalData", @"writtenData"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
- (NSString *)downloadProgress{
if (self.writtenData == 0) {
self.writtenData = 10;
}
if (self.totalData == 0) {
self.totalData = 100;
}
return [[NSString alloc] initWithFormat:@"%f",1.0f*self.writtenData/self.totalData];
}
2.6KVO开关
// 自动开关
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
return YES;
}
//单个属性设置关闭
+ (BOOL) automaticallyNotifiesObserversForNick:(NSString *)nick{
return YES;
}
- (void)setNick:(NSString *)nick{
[self willChangeValueForKey:@"nick"];
_nick = nick;
[self didChangeValueForKey:@"nick"];
}
当上面的方法返回NO,整个类的监听都被关掉,反之,返回YES,就被打开。
2.7使用集合类型字典等
/* Possible values in the NSKeyValueChangeKindKey entry in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information.
*/
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4,
};
/* Possible kinds of set mutation for use with -willChangeValueForKey:withSetMutation:usingObjects: and -didChangeValueForKey:withSetMutation:usingObjects:. Their semantics correspond exactly to NSMutableSet's -unionSet:, -minusSet:, -intersectSet:, and -setSet: method, respectively.
*/
typedef NS_ENUM(NSUInteger, NSKeyValueSetMutationKind) {
NSKeyValueUnionSetMutation = 1,
NSKeyValueMinusSetMutation = 2,
NSKeyValueIntersectSetMutation = 3,
NSKeyValueSetSetMutation = 4
};
3、KVO的底层原理
3.1添加监听的处理
实现以下方法打印类及其子类
#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
// 注册类的总数
int count = objc_getClassList(NULL, 0);
// 创建一个数组, 其中包含给定对象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
// 获取所有已注册的类
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i<count; i++) {
if (cls == class_getSuperclass(classes[i])) {
[mArray addObject:classes[I]];
}
}
free(classes);
NSLog(@"classes = %@", mArray);
}
实现以下方法打印类的方法列表
#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
NSLog(@"*********************");
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[I];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
image.png
通过监听前后对LGPerson及其子类的列表的打印,我们发现,当对,LGPerson的对象监听后,系统自动生成了一个LGPerson的子类NSKVONotifying_LGPerson。再来看看NSKVONotifying_LGPerson的方法列表。
image.png从上图可以得知,NSKVONotifying_LGPerson重写了父类的setNickName方法。
note:注意只有监听属性才有生成set方法,监听成员变量是不会生成的,比如监听name.
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject{
@public
NSString *name;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
- (void)sayLove;
@end
再来看看self.person 的isa指向。
image.png
可以看到self.person的isa指向,有LGPerson变成了NSKVONotifying_LGPerson.
通过上图可以分析得出:当对一个对象的属性进行监听的时候,会生成对前对象所属类的子类NSKVONotifying_XXX,并将这个对象的isa指向这个派生类。在这个派生类中,重写当前属性的setter方法。还重写了class方法,[self.person class];返回的是LGPerson。还实现了一个isKVO的标记,标记当前类实现监听的派生类。
3.2监听的销毁
image.png1、从上图可知,销毁监听后,派生类NSKVONotifying_LGPerson依然存在,这样下次监听不需要重复创建了。
2、销毁后,对象的isa指针又指回了LGPerson.
对象销毁后,isa指回来,保证不出现混乱。
谨记:一定要移除观察,在使用RAC的时候要手动接受(dispose)并在dealloc中释放掉。
如果观察没有移除会出现野指针的情况。
网友评论