KVO
KVO是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化,是一对多的关系,一个值的变化会通知所有的观察者
Key-Value Observing (KVO) 建立在 KVC 之上,它能够观察一个对象的 KVC key path 值的变化
//KVO
主要用于
model
类
,
当
Model
类的一个属性值发生变化的时候
,
会触发事件
//
在设计模式中
有一种设计模式是观察者模式
//kvo 是观察者模式的实现方式
//kvo 是观察者模式的实现方式
//kvo里面涉及的几个概念
//1.
被观察者
一般是我们的
Model
类
//2. 观察者 一般是 controller
//3.key 一般是 model 类的属性
//2. 观察者 一般是 controller
//3.key 一般是 model 类的属性
//4.value 一般是属性对应的value值
1.被观察者
@interface
Person :
NSObject
@property
(
nonatomic
,
copy
)
NSString
*name;
@property
(
nonatomic
,
copy
)
NSString
*gender;
@end
//使用步骤
//1.创建一个被观察者
_p
= [[
Person
alloc
]
init
];
_p . name = @ “ a " ;
_p . name = @ “ a " ;
_p.gender = @
“
b
"
;
//2.
为被观察者指定观察者
// 观察者 属性 key new/old/inital/Prior 参数
// 观察者 属性 key new/old/inital/Prior 参数
[_p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
//4.
改变属性的
value
值
_p . name = @“c" ;
_p . name = @“d" ;
_p . name = @“c" ;
_p . name = @“d" ;
_p
.
gender
=
@“e"
;
// whenever an observed key path changes, this method will be called
//3. 实现回调方法
//3. 实现回调方法
- (
void
)observeValueForKeyPath:(
NSString
*)keyPath ofObject:(
id
)object change:(
NSDictionary
*)change context:(
void
*)context{
NSLog
(
@"keyPath:%@"
,keyPath);
NSLog ( @"object:%@" ,object);
NSLog ( @"change:%@" ,change);
NSLog ( @"object:%@" ,object);
NSLog ( @"change:%@" ,change);
}
//5.
在
dealloc
方法里面移除观察者
- ( void )dealloc{
[ _p removeObserver : self forKeyPath : @"name" ];
- ( void )dealloc{
[ _p removeObserver : self forKeyPath : @"name" ];
}
KVO的实现分析
使用观察者模式需要被观察者的配合,当被观察者的状态发生变化的时候通过事先定义好的接口(协议)通知观察者.在KVO的使用中我们并不需要向被观察者添加额外的代码,就能在被观察者的属性变化的时候得到通知,这个功能是如何实现的?同KVC一样依赖于强大的Runtime机制
系统实现KVO有以下几个步骤:
- 当类A的对象第一次被观察的时候,系统会在运行期动态创建类A的派生类。我们称为B。
- 在派生类B中重写类A的setter方法,B类在被重写的setter方法中实现通知机制。
- 类B重写会 class方法,将自己伪装成类A。类B还会重写dealloc方法释放资源。
- 系统将所有指向类A对象的isa指针指向类B的对象。
KVO同KVC一样,通过isa-swizzling技术来实现.当观察者被注册为一个对象的属性的观察对象的isa指针被修改,指向一个中间类,而不是在真实的类.其结果是,isa指针的值并不一定反映实例的实际类
所以不能依靠isa指针来确定对象是否是一个类的成员,应该使用class方法来确定对象实例的类
这就是
KVO
的作用,它通过
key path
观察对象的值,当值发生变化的时候会收到通知
NSNotification
NSNotificaiion是通知,也是一对多的使用场景.在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方.NSNotification的特点,就是需要观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但其优点是监听不局限于属性的变化,还可以对多种多样的状态化进行jiant,监听范围广,使用也更灵活
- (
BOOL
)application:(
UIApplication
*)application didFinishLaunchingWithOptions:(
NSDictionary
*)launchOptions {
// Override point for customization after application launch.
// 创建一个本地通知
UILocalNotification *localNot = [[ UILocalNotification alloc ] init ];
// 先做一下 iOS 版本的判断 , 因为 iOS8.0 以后注册发生了一些变化 , 必须设置 UIUserNotificationSettings
if ([[ UIDevice currentDevice ]. systemVersion floatValue ] >= 8.0 ) {
UIUserNotificationSettings *settings = [ UIUserNotificationSettings settingsForTypes : UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories : nil ];
// 注册这些 settings
[[ UIApplication sharedApplication ] registerUserNotificationSettings :settings];
}
// 设置本地通知的一些属性
// 设置通知的开始时间
NSDate *fireDate = [ NSDate dateWithTimeIntervalSinceNow : 10 ];
localNot. fireDate = fireDate;
// 设置时区
localNot. timeZone = [ NSTimeZone defaultTimeZone ];
// 设置重复频率 ( 现在为一分钟 )
localNot. repeatInterval = NSCalendarUnitMinute ;
// 推送显示的内容
// Override point for customization after application launch.
// 创建一个本地通知
UILocalNotification *localNot = [[ UILocalNotification alloc ] init ];
// 先做一下 iOS 版本的判断 , 因为 iOS8.0 以后注册发生了一些变化 , 必须设置 UIUserNotificationSettings
if ([[ UIDevice currentDevice ]. systemVersion floatValue ] >= 8.0 ) {
UIUserNotificationSettings *settings = [ UIUserNotificationSettings settingsForTypes : UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories : nil ];
// 注册这些 settings
[[ UIApplication sharedApplication ] registerUserNotificationSettings :settings];
}
// 设置本地通知的一些属性
// 设置通知的开始时间
NSDate *fireDate = [ NSDate dateWithTimeIntervalSinceNow : 10 ];
localNot. fireDate = fireDate;
// 设置时区
localNot. timeZone = [ NSTimeZone defaultTimeZone ];
// 设置重复频率 ( 现在为一分钟 )
localNot. repeatInterval = NSCalendarUnitMinute ;
// 推送显示的内容
localNot.alertBody
=
@"12
点了
"
;
// 标题
localNot. alertTitle = @" 提醒 " ;
// 设置声音
localNot. soundName = UILocalNotificationDefaultSoundName ;
//localNot.soundName = @"XiaoPingGuo.aiff";
// 设置角标个数
localNot. applicationIconBadgeNumber = 2 ;
// 设置 userInfo
NSDictionary *dic = [ NSDictionary dictionaryWithObject : @"banji" forKey : @"class" ];
localNot. userInfo = dic;
// 执行通知
[application scheduleLocalNotification :localNot];
return YES ;
}
- ( void )application:( UIApplication *)application didReceiveLocalNotification:( UILocalNotification *)notification{
[application setApplicationIconBadgeNumber : 0 ]; // 进入前台
// 先得到所有的本地通知
NSArray *localNots = [application scheduledLocalNotifications ];
// 遍历所有的本地通知 找到对应的本地通知
for ( UILocalNotification *local in localNots) {
// 根据 userInfo 找到对应的本地通知
NSDictionary *dic = local. userInfo ;
if ([[dic objectForKey : @"class" ] isEqualToString : @"banji" ]) {
// 取消对应的本地通知
[application cancelLocalNotification :local];
}
}
// 取消全部本地通知
[application cancelAllLocalNotifications ];
}
Delegate
delegate是代理,就是我不想做的事情交给别人做.比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭,盛饭,倒水,这些操作这些狗都不需要关心,只需要调用delegate(代理人)就可以了,有其他类完成所需要的操作.所以delegate是一对一关系
Block
block是delegate的另一种形式,是函数式编程的一种形式,使用 场景跟delegete一样,相比delegate更灵活,而且代理的实习更直观
KVO一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式).
delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate.
Notification一般是进行全局通知,经纪人也不要知道自己的顾客.Notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息
KVC
kvc是Key Value Coding简称,是一种可以他那个过字符串的名字(key)来访问类属性的机制,而不是通过调用Setter,Getter方法访问
关键方法定义在NSKeyValueCodingProtocol
KVC支持类对象的内 建基本数据类型
使用
-
获取值
valueForKey: 传入NSString属性的名字。
valueForKeyPath: 属性的路径,xx.xx
valueForUndefinedKey 默认实现是抛出异常,可重写这个函数做错误处理 - 修改值
setValue:forKey:
setValue:forKeyPath:
setValue:forUnderfinedKey:
setNilValueForKey: 对非类对象属性设置nil时调用,默认抛出异常。
KVC键值查找
搜索单值成员
setValue:forKey:搜索方式
1,首先搜索setKey:方法.(key指成员变量名,首字母大写)
2,上面的setter方法没找到,如果类方法axxessInstanceVariablesDirectly返回YES.那么按key,is Key,key,isKey的顺序搜索成员名. (NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)
3,如果没有找到成员变量,调用setValue:forUnderfinedKey:
valueForKey:的搜索方式
1.首先按getKey,key,isKey的顺序查找getter方法,找到直接调用.如果是BOOL,int等内建值类型,会做NSNumber的转换
2.上面的getter没找到,查找countOfKey,objectInKeyAtIndex,KeyAtIndexes格式的方法,如果countOfKey和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合的NSArray消息方法
3.还没找到,查找countOfKey,enumeratorOfKey,memberOfKey格式的方法.如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合
4.还是没有找到,如果类方法accessInstanceVariablesDirectly返回YES,那么按key,isKey,key,isKey的顺序搜索成员名
5,再没有找到,调用valueForUndefinedKey
KVC实现分析
KVC运用了isa-swizzing技术,isa-swizzing就是类型混合指针机制,KVC通过isa-swizzing实现其内部查找定位.isa指针(is kind of)指向维护分发表的对象的类,该分发表实际上包含了指向现实类中的方法的指针和其他数据
[site setValue:@
"sitename"
forKey:@
"name"
]; //会被编译器处理成
SEL
sel = sel_get_uid(setValue:forKey);
IMP
method
= objc_msg_loopup(site->isa,sel);
method
(site,sel,@
"sitename"
,@
"name"
);
每一个类都由一张方法表,是一个hash表,值是还书指针IMP,SEL的名称就是查表时所有的键
SEL数据类型:查找方法表时所用的键.定义成char*,实质上可以理解成int值
IMP数据类型:他其实就是一个编译器内部实现时候的函数指针,当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型
KVC的内部机制:
一个对象在调用setValue的时候进行了:
1.根据方法名找到运行方法的时候需要的环境参数
2.他会从自己的isa指针结合环境参数,找到具体的方法实现接口
3.再直接查找得来的具体的实现方法