KeyValue Observing 键值监测.docx
- 文档编号:18490713
- 上传时间:2023-08-18
- 格式:DOCX
- 页数:22
- 大小:44.90KB
KeyValue Observing 键值监测.docx
《KeyValue Observing 键值监测.docx》由会员分享,可在线阅读,更多相关《KeyValue Observing 键值监测.docx(22页珍藏版)》请在冰点文库上搜索。
KeyValueObserving键值监测
Key-ValueObserving(键值监测)
简介
KVO是一套当目标对象的属性值改变时观察者对象能够接受到通知的机制。
必须先理解KVC才能更好的理解KVO,前者是后者的实现基础。
这样的通信机制在MVC设计模式很是常见
实现过程简单来说分为3步:
1、添加观察这和监测对象
2、监测对象改变
3、收到值改变通知,处理后续逻辑
举个生活中的例子就是给银行卡开通短信通知的业务,总体也是分3步“
1、去银行办理短信业务
2、账号财产变动
3、收到短信通知
KVO是框架级别的服务,无需自己发送通知,使用方便,基本不需要添加额外代码即可使用。
详情
为了使用KVO,必须满足以下3步
1、目标对象的属性,必须支持KVO
2、注册观察者与被观察者addObserver:
forKeyPath:
options:
context:
3、观察者必须实现observeValueForKeyPath:
ofObject:
change:
context:
方法
第一步、确保目标支持KVO
被监测的目标对象的属性支持KVO必须满足以下条件:
1、目标对象的属性必须支持KVC,对于1对1属性简单来说就是实现set和get方法。
详情和1对多请阅读官方说明。
系统已有类及子类自动支持,放心使用。
2、自动和手动属性通知
目标对象必须能发出属性变化通知。
系统默认支持,也可自定义。
系统默认支持,且支持的很好,一般无需自定义。
//如果需要自定义,需要重新此方法,默认返回YES
+(BOOL)automaticallyNotifiesObserversForKey:
(NSString*)key;
//在set方法中手动调用,变化类型只是针对NSKeyValueChangeSetting
-(void)willChangeValueForKey:
(NSString*)key;
-(void)didChangeValueForKey:
(NSString*)key;
例如
//假设有属性
@property(nonatomic,copy)NSString*name;
+(BOOL)automaticallyNotifiesObserversForKey:
(NSString*)theKey{
BOOLautomatic=NO;
/*只自定义指定属性,其它仍然自动发送通知*/
if([theKeyisEqualToString:
@"name"])
{
//在set方法中手动调用相关方法
automatic=NO;
}
else
{
//此方法默认返回YES
automatic=[superautomaticallyNotifiesObserversForKey:
theKey];
}
returnautomatic;
}
-(void)setName:
(NSString*)name
{
//即将变化
[selfwillChangeValueForKey:
@"name"];
_name=name;
//已经变化
[selfdidChangeValueForKey:
@"name"];
}
//如果说只有值不相等时才发送通知,提升性能
-(void)setName:
(NSString*)name
{
if(!
[nameisEqualToString:
_name])
{
[selfwillChangeValueForKey:
@"name"];
_name=name;
[selfdidChangeValueForKey:
@"name"];
}
}
如果涉及1对多的容器类,需要自己实现NSKeyValueChangeInsertion,NSKeyValueChangeRemoval,NSKeyValueChangeReplacement三种操作对应的方法,例如
//Keys为属性名称
-(void)removeKeysAtIndexes:
(NSIndexSet*)indexes{
[selfwillChange:
NSKeyValueChangeRemoval
valuesAtIndexes:
indexesforKey:
@"keys"];
//Removethetransactionobjectsatthespecifiedindexes.
[selfdidChange:
NSKeyValueChangeRemoval
valuesAtIndexes:
indexesforKey:
@"keys"];
}
3、属性依赖
如果目标对象属性存在依赖关系,注册合适的依赖Keys。
核心方法为
第一种、
+(NSSet
(NSString*)keyNS_AVAILABLE(10_5,2_0);
说明:
1、返回目标属性依赖属性的KeyPath的Set。
当对象注册后,KVO自动监测该对象所有的KeyPaths。
2、其默认实现从对象所属类的方法列表中匹配方法:
+keyPathsForValuesAffecting
triggerChangeNotificationsForDependentKey:
3、可以用来替换手动调用-willChangeValueForKey:
/-didChangeValueForKey:
来实现属性依赖的解决方案
4、不能在已有类的Category中使用,在Category禁止重写此方法,可以使用+keyPathsForValuesAffecting
第二种、
或者重写此格式+keyPathsForValuesAffecting
比如说,姓名=姓+名;当二者任一变动时更新姓名
@property(nonatomic,copy)NSString*name;//姓名
@property(nonatomic,copy)NSString*firstName;//姓
@property(nonatomic,copy)NSString*lastName;//名
//重新get方法,表明字段组成
-(NSString*)name
{
return[NSStringstringWithFormat:
@"%@%@",_firstName,_lastName];
}
//通过此方法
+(NSSet*)keyPathsForValuesAffectingValueForKey:
(NSString*)key
{
NSSet*keyPaths=[superkeyPathsForValuesAffectingValueForKey:
key];
if([keyisEqualToString:
@"name"])
{
NSArray*affectingKeys=@[@"lastName",@"firstName"];
keyPaths=[keyPathssetByAddingObjectsFromArray:
affectingKeys];
}
returnkeyPaths;
}
或者
+(NSSet*)keyPathsForValuesAffectingName
{
return[NSSetsetWithObjects:
@"lastName",@"firstName",nil];
}
//改变值
-(void)viewDidLoad{
[superviewDidLoad];
[selfsetValue:
@"张"forKey:
@"firstName"];
[selfsetValue:
@"三"forKey:
@"lastName"];
[selfaddObserver:
selfforKeyPath:
@"name"options:
NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNewcontext:
nil];
//名称改变,刷新姓名
[selfsetValue:
@"龙"forKey:
@"lastName"];
}
-(void)observeValueForKeyPath:
(NSString*)keyPathofObject:
(id)objectchange:
(NSDictionary
(void*)context
{
NSLog(@"NSKeyValueChangeOldKey:
%@",change[NSKeyValueChangeOldKey]);
NSLog(@"NSKeyValueChangeNewKey:
%@",change[NSKeyValueChangeNewKey]);
}
//输出结果
2016-09-0617:
05:
01.904KVC[4192:
307124]NSKeyValueChangeOldKey:
张三
2016-09-0617:
05:
01.904KVC[4192:
307124]NSKeyValueChangeNewKey:
张龙
注意:
以上关于属性依赖的处理方法不支持一对多的关系。
比如说ViewController对象有一个totalNumber表示总数和属性datas数组,数组中Data的对象,对象含有number属性。
/*Data对象*/
@interfaceData:
NSObject
@property(nonatomic,assign)NSIntegernumber;
@end
/*ViewController对象*/
@interfaceViewController()
@property(nonatomic,assign)NSIntegertotalNumber;
@property(nonatomic,strong)NSArray*datas;
@end
可以采用以下方法解决
1、注册每个Data对象的年龄属性为监测属性,ViewController对象为观察者,data.number变化时,使用集合运算符求和,然后设置ViewController的totalNumber属性值
-(void)viewDidLoad{
[superviewDidLoad];
/*创建Data对象*/
Data*data1=[[Dataalloc]init];
data1.number=0;
Data*data2=[[Dataalloc]init];
data2.number=0;
Data*data3=[[Dataalloc]init];
data3.number=0;
/*self.datas属性赋值*/
[selfsetValue:
@[data1,data2,data3]forKey:
@"datas"];
/*监测self.datas中每个data对象的number属性*/
//(0,3)中0表示index从0开始,0表示长度3,也就是index(0、1、2);但是不能使得self.datas数组越界
NSIndexSet*set=[NSIndexSetindexSetWithIndexesInRange:
NSMakeRange(0,3)];
[self.datasaddObserver:
selftoObjectsAtIndexes:
setforKeyPath:
@"number"options:
NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNewcontext:
nil];
/*监测totalNumber属性*/
[selfaddObserver:
selfforKeyPath:
@"totalNumber"options:
NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNewcontext:
nil];
/*改变值,查看输出结果*/
[data1setValue:
@"1"forKey:
@"number"];
[data2setValue:
@"1"forKey:
@"number"];
[data3setValue:
@"1"forKey:
@"number"];
}
-(void)observeValueForKeyPath:
(NSString*)keyPathofObject:
(id)objectchange:
(NSDictionary
(void*)context
{
if([keyPathisEqualToString:
@"number"])
{
[selfupdateTotalNumber];
}
elseif([keyPathisEqualToString:
@"totalNumber"])
{
NSLog(@"%@--%@",keyPath,change);
}
}
//更新总数
-(void)updateTotalNumber
{
NSNumber*total=[selfvalueForKeyPath:
@"datas.@sum.number"];
[selfsetValue:
totalforKey:
@"totalNumber"];
}
//输出结果
2016-09-0714:
10:
10.694KVC[3034:
165515]totalNumber--{
kind=1;
new=1;
old=0;
}
2016-09-0714:
10:
10.695KVC[3034:
165515]totalNumber--{
kind=1;
new=2;
old=1;
}
2016-09-0714:
10:
10.695KVC[3034:
165515]totalNumber--{
kind=1;
new=3;
old=2;
}
以上代码中不涉及移除,根据需要添加代码,对象delloc之前,必须移除。
2、CoreData,自动实现类似的功能。
第二步、注册
1、注册通知
为了能够获取目标属性值改变的通知,需要注册观察者和观察对象属性
-(void)addObserver:
(NSObject*)observerforKeyPath:
(NSString*)keyPathoptions:
(NSKeyValueObservingOptions)optionscontext:
(nullablevoid*)context;
参数说明:
observer:
观察者对象,就是想收到变动通知的对象
keyPath:
监测的目标属性的路径
options:
决定了通知中内容和发送时间
context:
C指针或者对象,传递参数,一般不用传NULL
例如在新建的工程的ViewController中
@property(nonatomic,copy)NSString*name;
-(void)registerAsObserver
{
[selfaddObserver:
selfforKeyPath:
@"name"options:
NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNewcontext:
nil];
}
注意:
此方法不持有观察者对象、被观察对象、context,管理好其生命周期。
2、接受通知
当监测的目标对象的属性变化时,观察者将调用observeValueForKeyPath:
ofObject:
change:
context:
message,所有的观察者都必须实现此方法
-(void)observeValueForKeyPath:
(nullableNSString*)keyPathofObject:
(nullableid)objectchange:
(nullableNSDictionary
(nullablevoid*)context;
keyPath:
监测的目标属性的路径
object:
观察者对象
change:
变化内容
context:
C指针或者对象,传递参数,一般不用传NULL
3、移除通知
当不再使用时,需要通过以下方法移除通知。
-(void)removeObserver:
(NSObject*)observerforKeyPath:
(NSString*)keyPathcontext:
(nullablevoid*)contextNS_AVAILABLE(10_7,5_0);
-(void)removeObserver:
(NSObject*)observerforKeyPath:
(NSString*)keyPath;
keyPath:
监测的目标属性的路径
observer:
观察者对象
context:
C指针或者对象,传递参数,一般不用传NULL
以上两个方法,根据需要选择使用。
特别注意:
NSArray、NSOrderedSet、NSSet不支持以上三个方法,调用会抛出异常。
第三步、属性变化
使用KVC方法,或者能够触发KVC方法使得监测的目标对象属性变化。
第四步、接收变化
当监测的目标对象的属性变化时,观察者将调用observeValueForKeyPath:
ofObject:
change:
context:
message,所有的观察者都必须实现此方法。
在此方法中处理变化
以上第二、三、四步组成一次完整的KVO使用过程,下边关于一些参数的用法说明
参数说明
关于NSKeyValueObservingOptions
-(void)addObserver:
(NSObject*)observerforKeyPath:
(NSString*)keyPathoptions:
(NSKeyValueObservingOptions)optionscontext:
(nullablevoid*)context;
options:
决定了通知中内容和发送时间
1
2
NSKeyValueObservingOptions是一个枚举类型
typedefNS_OPTIONS(NSUInteger,NSKeyValueObservingOptions){
/*通知dic中是否包含新值*/
NSKeyValueObservingOptionNew=0x01,
/*通知dic中是否包含新值*/
NSKeyValueObservingOptionOld=0x02,
/*添加此操作,通知dic中是否包含注册通知前的初始值;如果目标属性是容器类,每个元素都会触发通知发送*/
NSKeyValueObservingOptionInitialNS_ENUM_AVAILABLE(10_5,2_0)=0x04,
/*添加此操作,每次值变化,将触发两次:
1、变化前(dic中包含NSKeyValueChangeNotificationIsPriorKey,值为1,NSNumber类型)
2、变化后
*/
NSKeyValueObservingOptionPriorNS_ENUM_AVAILABLE(10_5,2_0)=0x08
};
关于ChangeDictionary
-(void)observeValueForKeyPath:
(nullableNSString*)keyPathofObject:
(nullableid)objectchange:
(nullableNSDnary
(nullablevoid*)context;
change:
变化内容
1
2
其包含以下几种内容,可以使用以下字段取值
//值变化类型
FOUNDATION_EXPORTNSString*constNSKeyValueChangeKindKey;
//新值
FOUNDATION_EXPORTNSString*constNSKeyValueChangeNewKey;
//旧值
FOUNDATION_EXPORTNSString*constNSKeyValueChangeOldKey;
//容器类中,变化值所在位置,NSIndexSet类型
FOUNDATION_EXPORTNSString*constNSKeyValueChangeIndexesKey;
//是否值变化前,NSNumber类型
FOUNDATION_EXPORTNSString*constNSKeyValueChangeNotificationIsPriorKeyNS_AVAILABLE(10_5,2_0);
其中NSKeyValueChangeKindKey有以下几种类型
typedefNS_ENUM(NSUInteger,NSKeyValueChange)
{
NSKeyValueChangeSetting=1,//值变化
NSKeyValueChangeInsertion=2,//插入
NSKeyValueChangeRemoval=3,//移除
NSKeyValueChangeReplacement=4,//替换
};
上边的NSKeyValueChangeKindKey2、3、4分别对应着有序集合比如NSArray中增、删、改操作。
参数说明-代码示例
1、NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
@property(nonatomic,copy)NSString*name;
//注册
-(void)viewDidLoad{
[superviewDidLoad];
[selfsetValue:
@"zwq"forKey:
@"name"];
[selfaddObserver:
selfforKeyPath:
@"name"options:
NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOldcontext:
nil];
[selfsetValue:
@"zwq2"forKey:
@"
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- KeyValue Observing 键值监测 键值 监测