1.什么是KVO
- KVO == (Key-Value Observing)
- 當(dāng)指定的對象中某個指定的屬性的值改變的時候,此對象接收到通知的機制;
2.KVO的簡單案例
//我們自定義一個模型類 "Person.h"
//在.h文件中我們可以聲明幾個屬性
@interface Person : NSObject
/** 年齡 */
@property (nonatomic, assign) int age;
/** 成績 */
@property (nonatomic, assign) int performance;
/**姓名 */
@property (strong, nonatomic) NSString *name;
@end
//隨便在一個ViewController里面應(yīng)用此類
@interface PRFLabelController : UIViewController
/*顯示消息的label*/
@property (nonatomic,strong) UILabel *personImformationLabel;
/**被顯示的人物對象*/
@property (nonatomic,strong) Person *person;
@end
#import "PRFLabelController.h"
@interface PRFLabelController ()
/**最新的成績*/
@property (nonatomic,assign) int newPerformance;
/**更新成績的按鈕*/
@property (strong, nonatomic) UIButton *updateBtn;
@end
//控制器的.m文件
@implementation PRFLabelController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
//初始化模型對象
self. person = [[Pserson alloc] init];
self.person.age = 18; //初始化屬性值
self.person.name = @"prf";
self.person.performaance = 0;
//指定一個新成績
self. newPerformance = 60;
/*
options:
NSKeyValueObservingOptionPrior -> 改變之前/后都發(fā)一次通知
NSKeyValueObservingOptionInitial -> 注冊監(jiān)聽的有值變化的時候發(fā)通知
NSKeyValueObservingOptionNew -> 返回新的值
NSKeyValueObservingOptionOld -> 返回舊的值
*/
//添加kvo監(jiān)聽
[self. person addObserver:self forKeyPath:@"performance"
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
//options參數(shù)是用于指定在值修改以后,反饋回來的是新值還是舊值
//options是一個位移枚舉,可以用 | 來表示同時監(jiān)聽
//初始化更新按鈕 (不具體寫了)
//self.updateBtn 添加一個 updateperformance 的點擊事件
}
//點擊按鈕時更改對象成績
-(void) updateperformance{
//更新對象的成績
[self.person setValue:@(self.newperformance) forKey:@"performance"];
//修改新成績 <這不是必要的>
self.newperformance +=2;
}
//Person對象監(jiān)聽到屬性值變化后,自動執(zhí)行以下方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@",change);//改變的數(shù)據(jù)(包含該改變前的和改變后的數(shù)據(jù))
//判斷修改的屬性是否為performance才執(zhí)行對應(yīng)的時間
if([keyPath isEqualToString:@"performance"]){
//此處寫監(jiān)聽到鍵值內(nèi)容改變后想要做的對應(yīng)的事情
}
}
#pragma mark 控制器銷毀的時候記得移除監(jiān)聽
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"price"];
}
@end
3.KVO的某些特定使用場景
3.1場景一
- 假設(shè)在某一個控制器內(nèi),有兩個方法method1 & method2 ;在方法的內(nèi)部都改變了person.age屬性的值,但是只需要監(jiān)聽method1方法觸發(fā)的改變時應(yīng)該如何實現(xiàn)?
在Person.m里面重寫下面這個方法:
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
return NO; //此方法默認(rèn)是返回YES
}
實現(xiàn)了上面這個方法以后,就會發(fā)現(xiàn)修改person.的屬性的時候,沒辦法監(jiān)聽到值的變化;如果想要監(jiān)聽值的變化,就需要再改變值的時候像下面這么寫:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//用修改age的值作為案例
static int a = 1;
a++;
[self.p willChangeValueForKey:@"age"];
self.p.age = a;
[self.p didChangeValueForKey:@"age"];
//上面這個寫法 是手動觸發(fā)KVO的
}
3.1場景二
*如果對象中嵌套了別的對象,然后想要監(jiān)聽嵌套對象的屬性時,應(yīng)該如何操作?
方法1:屬性依賴寫法
//假設(shè)person對象中有一個dog對象,dog對象有一個age屬性
//我們想要監(jiān)聽person對象中的dog屬性中的age屬性時可以用屬性依賴的寫法
//注意:在初始化person對象的同事,它的屬性dog也必須初始化,不然無法監(jiān)聽
[self.person addObserver:self forKeyPath:@"dog.age" options:NSKeyValueObservingOptionNew context:nil];
方法2:直接監(jiān)聽嵌套屬性的屬性
//可以直接用嵌套屬性person.dog 注冊監(jiān)聽,監(jiān)聽dog的age值
[self.person.dog addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
方法3:+(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key;
//可以在Person.m文件中重寫下面這個方法
+(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"dog"] ) {
//此處如果dog是有很多屬性的,還可以實用runtime的方式獲取屬性數(shù)組;
NSArray *arr = @[@"_dog.level",@"_dog.age"];
keyPaths = [keyPaths setByAddingObjectsFromArray:arr];
}
return keyPaths;
}
4.KVO的底層實現(xiàn)
- KV0實際監(jiān)聽的是屬性的set方法
//假設(shè)person中有一個成員變量 sex,是無法監(jiān)聽的,因為其沒有set方法
@interface Person : NSObject
{
@public
NSString *sex;
}
- 對象添加了kvo監(jiān)聽以后,在運行時創(chuàng)建了一個子類:
NSNotfyfing_Class.h
在這個運行時創(chuàng)建的子類中重寫了下面這個方法:
-(void)setName:(NSString *)name{
[self willChangeValueForKey:@"name"];
[super setName:name];
[self didChangeValueForKey:@"name"];
}
5.補充代碼:
碼云代碼地址