OC學習篇之---KVC和KVO操作,oc---kvckvo
前一篇文章我們介紹了OC中最常用的檔案操作:http://blog.csdn.net/jiangwei0910410003/article/details/41875015,那麼今天來看一下OC中的一個比較有特色的知識點:KVC和KVO
一、KVC操作
OC中的KVC操作就和Java中使用反射機制去訪問類的private許可權的變數,很暴力的,這樣做就會破壞類的封裝性,本來類中的的private許可權就是不希望外界去訪問的,但是我們這樣去操作,就會反其道而行,但是我們有時候真的需要去這樣做,哎。所以說有些事不是都是順其自然的,而是需要的時候自然就誕生了。
下面就來看一下這種技術的使用:
Dog.h
//// Dog.h// 42_KVC//// Created by jiangwei on 14-10-14.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>@interface Dog : NSObject@end
Dog.m
//// Dog.m// 42_KVC//// Created by jiangwei on 14-10-14.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import "Dog.h"@implementation Dog@end
定義了Dog這個類,但是什麼都沒有,他只是一個中間類,沒什麼作用,在這個demo中。
Person.h
//// Person.h// 42_KVC//// Created by jiangwei on 14-10-14.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>#import "Dog.h"@interface Person : NSObject{@private NSString *_name; NSDog *_dog; NSInteger *age;}@end
Person.m
//// Person.m// 42_KVC//// Created by jiangwei on 14-10-14.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import "Person.h"@implementation Person- (NSString *)description{ NSLog(@"%@",_name); return _name;}@endPerson類中我們定義了兩個屬性,但是這兩個屬性對外是不可訪問的,而且也沒有對應的get/set方法。我們也實現了description方法,用於列印結果
看一下測試代碼
main.m
//// main.m// 42_KVC//// Created by jiangwei on 14-10-14.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>#import "Person.h"#import "Dog.h"//KVC:很暴力,及時一個類的屬性是私人的,而且也沒有get/set方法,同樣可以讀寫//相當於Java中的反射,破壞類的封裝性int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init]; //設定值 //這裡setValue方法:第一個參數是value,第二個參數是key(就是類的屬性名稱) [p setValue:@"jiangwei" forKey:@"name"]; Dog *dog = [[Dog alloc] init]; [p setValue:dog forKey:@"dog"]; //KVC設定值時,如果屬性有set方法,則優先調用set方法,如果沒有則直接設定上去,get方法類似 //讀取值 NSString *name = [p valueForKey:@"name"]; //設定基礎資料型別 (Elementary Data Type) //這裡需要將基本類型轉化成NSNumber //在設定值的時候,會有自動解包的過程,NSNumber會解包賦值給age [p setValue:@22 forKey:@"age"]; NSLog(@"%@",p); return 0; } return 0;}這裡我們產生一個Person對象,然後開始使用KVC技術了:
1、設定屬性值
//設定值//這裡setValue方法:第一個參數是value,第二個參數是key(就是類的屬性名稱)[p setValue:@"jiangwei" forKey:@"name"]; Dog *dog = [[Dog alloc] init];[p setValue:dog forKey:@"dog"];
使用setValue方法,就可以進行對屬性進行設定值操作了,同時需要傳遞這個屬性的名稱,這個和Java中使用反射機制真的很像。
註:KVC設定值時,如果屬性有set方法,則優先調用set方法,如果沒有則直接設定上去,get方法一樣
//設定基礎資料型別 (Elementary Data Type)//這裡需要將基本類型轉化成NSNumber//在設定值的時候,會有自動解包的過程,NSNumber會解包賦值給age[p setValue:@22 forKey:@"age"];
還有一個需要注意的地方:當我們在設定基本類型的時候,需要將其轉化成NSNumber類型的。
2、取屬性的值
//讀取值NSString *name = [p valueForKey:@"name"];
取值就簡單了
下面再來看一下KVC中強大的功能:索引值路徑
索引值路徑是對於一個類中有數組對象的屬性進行便捷操作。
看個情境:
一個作者有多本書
Author.h
//// Author.h// 43_KeyValuePath//// Created by jiangwei on 14-10-15.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>@interface Author : NSObject{ NSString *_name; //作者出版的書,一個作者對應多個書籍對象 NSArray *_issueBook;}@end
作者類中定義了名字和一個書籍數組
Author.m
//// Author.m// 43_KeyValuePath//// Created by jiangwei on 14-10-15.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import "Author.h"@implementation Author@end
Book.h
//// Book.h// 43_KeyValuePath//// Created by jiangwei on 14-10-15.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>#import "Author.h"@interface Book : NSObject{ Author *_author;}@property NSString *name;@property float *price;@end定義了一個作者屬性,書的名字,價格
Book.m
//// Book.m// 43_KeyValuePath//// Created by jiangwei on 14-10-15.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import "Book.h"@implementation Book@end
看一下測試代碼
main.m
//// main.m// 43_KeyValuePath//// Created by jiangwei on 14-10-15.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>#import "Book.h"#import "Author.h"int main(int argc, const char * argv[]) { @autoreleasepool { //------------------KVC索引值路徑 /* Book *book = [[Book alloc] init]; Author *author = [[Author alloc] init]; //設定作者 [book setValue:author forKey:@"author"]; //設定作者的名字 //路徑為:author.name,中間用點號進行串連 [book setValue:@"jiangwei" forKeyPath:@"author.name"]; NSString *name = [author valueForKey:@"name"]; NSLog(@"name is %@",name); */ //--------------------KVC的運算 Author *author = [[Author alloc] init]; [author setValue:@"莫言" forKeyPath:@"name"]; Book *book1 = [[Book alloc] init]; book1.name = @"紅高粱"; book1.price = 9; Book *book2 = [[Book alloc] init]; book2.name = @"蛙"; book2.price = 10; NSArray *array = [NSArray arrayWithObjects:book1,book2, nil]; [author setValue:array forKeyPath:@"issueBook"]; //基礎資料型別 (Elementary Data Type)會自動被封裝成NSNumber,裝到數組中 //得到所有書籍的價格 NSArray *priceArray = [author valueForKeyPath:@"issueBook.price"]; NSLog(@"%@",priceArray); //擷取數組的大小 NSNumber *count = [author valueForKeyPath:@"issueBook.@count"]; NSLog(@"count=%@",count); //擷取書籍價格的總和 NSNumber *sum = [author valueForKeyPath:@"issueBook.@sum.price"]; NSLog(@"%@",sum); //擷取書籍的平均值 NSNumber *avg = [author valueForKeyPath:@"issueBook.@avg.price"]; NSLog(@"%@",avg); //擷取書籍的價格最大值和最小值 NSNumber *max = [author valueForKeyPath:@"issueBook.@max.price"]; NSNumber *min = [author valueForKeyPath:@"issueBook.@min.price"]; } return 0;}
1、首先通過前面說到的KVC設定作者的書籍數組
//--------------------KVC的運算Author *author = [[Author alloc] init];[author setValue:@"莫言" forKeyPath:@"name"];Book *book1 = [[Book alloc] init];book1.name = @"紅高粱";book1.price = 9;Book *book2 = [[Book alloc] init];book2.name = @"蛙";book2.price = 10;NSArray *array = [NSArray arrayWithObjects:book1,book2, nil];[author setValue:array forKeyPath:@"issueBook"];
添加了兩本書籍
2、下面就開始用到KVC中索引值路徑了
1)擷取作者類中書籍數組中所有書籍的價格
//基礎資料型別 (Elementary Data Type)會自動被封裝成NSNumber,裝到數組中//得到所有書籍的價格NSArray *priceArray = [author valueForKeyPath:@"issueBook.price"];NSLog(@"%@",priceArray);
看到了:@"issueBook.price" 這就是索引值路徑的使用,issueBook是作者類中的書籍數組屬性名稱,price是書籍類的屬性,中間用點號進行串連,這樣我們就可以擷取到了所有書籍的價格了,如果在Java中,我們需要用一個迴圈操作。但是OC中多麼方便。
2)擷取作者類中書籍數組的大小
//擷取數組的大小NSNumber *count = [author valueForKeyPath:@"issueBook.@count"];NSLog(@"count=%@",count);
使用 @"issueBook.@count" 索引值路徑擷取書籍數組的大小,issueBook是作者類中的書籍數組屬性名稱,@count是特定一個寫法,可以把它想象成一個方法,中間任然用點號進行串連
3)擷取作者類中書籍數組的價格總和
//擷取書籍價格的總和NSNumber *sum = [author valueForKeyPath:@"issueBook.@sum.price"];NSLog(@"%@",sum);
使用 @"issueBook.@sum.price" 索引值路徑擷取書籍數組中的價格總和,issueBook是作者類中的書籍數組屬性名稱,@sum是特性寫法,可以把它想象成一個方法,price是書籍的價格屬性名稱,可以把它看成是@sum的一個參數,中間用點號進行串連
如果在java中,這個需要用一個迴圈來計算總和,OC中很方便的
4)擷取作者類中書籍數組的價格平均值、最小值、最大值
//擷取書籍的平均值NSNumber *avg = [author valueForKeyPath:@"issueBook.@avg.price"];NSLog(@"%@",avg);//擷取書籍的價格最大值和最小值NSNumber *max = [author valueForKeyPath:@"issueBook.@max.price"];NSNumber *min = [author valueForKeyPath:@"issueBook.@min.price"];
操作和上面類似,這裡就不解釋了
我們看到上面返回來的資料都是NSNumber類型的
二、KVO操作
KVO操作在OC中也是經常會用到的,而且這種機制在java中不存在的。
它的作用就是用來監聽類中屬性值的變化,實現原理是觀察者模式,當然我們也可以使用觀察者模式在Java中實現這樣的機制
看一下具體的例子:現在有一個小孩類,他有兩個屬性:開心值,饑餓值,然後還有一個護士類,用來監聽孩子類的這兩個屬性值的
Chidren.h
//// Children.h// 44_KVO//// Created by jiangwei on 14-10-16.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>@interface Children : NSObject@property NSInteger *hapyValue;@property NSInteger *hurryValue;@end
Children.m
//// Children.m// 44_KVO//// Created by jiangwei on 14-10-16.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import "Children.h"@implementation Children- (id) init{ self = [super init]; if(self != nil){ //啟動定時器 [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES]; self.hapyValue= 100; } return self;}- (void) timerAction:(NSTimer *) timer{ //使用set方法修改屬性值,才能觸發KVO int value = _hapyValue; [self setHapyValue:--value]; int values = _hurryValue; [self setHurryValue:--values];}@end在初始化方法中,我們啟動一個定時器,然後隔1s就去修改孩子類的值
Nure.h
//// Nure.h// 44_KVO//// Created by jiangwei on 14-10-16.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>@class Children;@interface Nure : NSObject{ Children *_children;}- (id) initWithChildren:(Children *)children;@end定義一個孩子屬性
Nure.m
//// Nure.m// 44_KVO//// Created by jiangwei on 14-10-16.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import "Nure.h"#import "Children.h"@implementation Nure- (id) initWithChildren:(Children *)children{ self = [super init]; if(self != nil){ _children = children; //觀察小孩的hapyValue //使用KVO為_children對象添加一個觀察者,用於觀察監聽hapyValue屬性值是否被修改 [_children addObserver:self forKeyPath:@"hapyValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"]; //觀察小孩的hurryValue [_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"]; } return self;}//觸發方法- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ NSLog(@"%@",change); //通過列印change,我們可以看到對應的key //通過keyPath來判斷不同屬性的觀察者 if([keyPath isEqualToString:@"hapyValue"]){ //這裡change中有old和new的值是因為我們在調用addObserver方法時,用到了 //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一個就用哪一個 //[change objectForKey:@"old"]是修改前的值 NSNumber *hapyValue = [change objectForKey:@"new"];//修改之後的最新值 NSInteger *value = [hapyValue integerValue]; if(value < 90){ //do something... } }else if([keyPath isEqualToString:@"hurryValue"]){ //這裡change中有old和new的值是因為我們在調用addObserver方法時,用到了 //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一個就用哪一個 //[change objectForKey:@"old"]是修改前的值 NSNumber *hurryValue = [change objectForKey:@"new"];//修改之後的最新值 NSInteger *value = [hurryValue integerValue]; if(value < 90){ //do something... } } NSLog(@"%@",context);//列印的就是addObserver方法的context參數 //使用KVC去修改屬性的值,也會觸發事件}- (void)dealloc{ //移除觀察者 [_children removeObserver:self forKeyPath:@"hapyValue"]; [_children removeObserver:self forKeyPath:@"hurryValue"]; }@end看到了在這裡就開始進行監聽操作了
下面來具體看一下如何做到監聽的
1、添加監聽對象
我們使用addObserver方法給孩子添加監聽對象
第一個參數:監聽者,這裡是Nure,所以可以直接傳遞self
第二個參數:監聽對象的屬性名稱
第三個參數:監聽這個屬性的狀態:這裡可以使用|進行多種組合操作,屬性的新值和舊值
第四個參數:傳遞內容給監聽方法
//觀察小孩的hapyValue//使用KVO為_children對象添加一個觀察者,用於觀察監聽hapyValue屬性值是否被修改[_children addObserver:self forKeyPath:@"hapyValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"];//觀察小孩的hurryValue[_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"];
2、監聽方法
//觸發方法- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ NSLog(@"%@",change); //通過列印change,我們可以看到對應的key //通過keyPath來判斷不同屬性的觀察者 if([keyPath isEqualToString:@"hapyValue"]){ //這裡change中有old和new的值是因為我們在調用addObserver方法時,用到了 //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一個就用哪一個 //[change objectForKey:@"old"]是修改前的值 NSNumber *hapyValue = [change objectForKey:@"new"];//修改之後的最新值 NSInteger *value = [hapyValue integerValue]; if(value < 90){ //do something... } }else if([keyPath isEqualToString:@"hurryValue"]){ //這裡change中有old和new的值是因為我們在調用addObserver方法時,用到了 //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一個就用哪一個 //[change objectForKey:@"old"]是修改前的值 NSNumber *hurryValue = [change objectForKey:@"new"];//修改之後的最新值 NSInteger *value = [hurryValue integerValue]; if(value < 90){ //do something... } } NSLog(@"%@",context);//列印的就是addObserver方法的context參數 //使用KVC去修改屬性的值,也會觸發事件}我們上面傳遞的第一個參數是監聽者,這個方法也是在監聽者中實現的,當屬性值發生變化的時候,這個方法會被回調
這個方法的參數:
第一個參數:索引值路徑
第二個參數:監聽對象
第三個參數:變化的值
第四個參數:傳遞的內容
我們看到代碼中有一個特殊的參數:第三個參數:NSDirctionary類型的
其實我們如果不知道是幹什麼的,我們可以列印一下他的結果看一下,很簡單,這裡就不說明了
我們會發現他有兩個索引值對
key是:new和old
他們就是分別代表這個屬性值變化的前後值,同時他們的得到也和之前我們添加監聽對象時設定的第三個參數有關:
NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld
那個地方設定了幾種狀態,這裡的NSDirctionary中就會有幾個索引值對
3、銷毀方法
這個並不屬於KVO的內容了,只是在這裡用到了就順便說一下
- (void)dealloc{ //移除觀察者 [_children removeObserver:self forKeyPath:@"hapyValue"]; [_children removeObserver:self forKeyPath:@"hurryValue"]; }我們在建立一個對象的時候會調用alloc方法,當對象被銷毀的時候會調用dealloc這個方法,這個和C++中的解構函式一樣,Java中有記憶體回收行程,所以沒有此類的方法,但是有一個finalize方法,其實這個方法就是在記憶體回收行程回收對象的時候會調用,和這個功能差不多,但是在Java中,我們並不提倡使用這個方法。因為會造成GC的回收發生錯誤。
我們在銷毀方法中需要移除監聽者
總結
這一篇就介紹了OC中比較有特色的兩個機制:KVC和KVO
KVC:就是可以暴力的去get/set類的私人屬性,同時還有強大的索引值路徑對數群組類型的屬性進行操作
KVO:監聽類中屬性值變化的