OC學習篇之---KVC和KVO操做 分類: IOS 2014-12-13 22:12 2075人閱讀 評論(1) 收藏

前一篇文章咱們介紹了OC中最經常使用的文件操做:http://blog.csdn.net/jiangwei0910410003/article/details/41875015,那麼今天來看一下OC中的一個比較有特點的知識點:KVC和KVOjava


1、KVC操做數組

OC中的KVC操做就和Java中使用反射機制去訪問類的private權限的變量,很暴力的,這樣作就會破壞類的封裝性,原本類中的的private權限就是不但願外界去訪問的,可是咱們這樣去操做,就會反其道而行,可是咱們有時候真的須要去這樣作,哎。因此說有些事不是都是順其天然的,而是須要的時候天然就誕生了。函數

下面就來看一下這種技術的使用:測試

Dog.hspa

//
//  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.net

//
//  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;
}

@end
Person類中咱們定義了兩個屬性,可是這兩個屬性對外是不可訪問的,並且也沒有對應的get/set方法。咱們也實現了description方法,用於打印結果


看一下測試代碼code

main.mserver

//
//  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"];
        
        //設置基本數據類型
        //這裏須要將基本類型轉化成NSNumber
        //在設置值的時候,會有自動解包的過程,NSNumber會解包賦值給age
        [p setValue:@22 forKey:@"age"];
        
        NSLog(@"%@",p);
        
        return 0;
    }
    return 0;
}
這裏咱們生成一個Person對象,而後開始使用KVC技術了:


一、設置屬性值對象

//設置值
//這裏setValue方法:第一個參數是value,第二個參數是key(就是類的屬性名稱)
[p setValue:@"jiangwei" forKey:@"name"];
        
Dog *dog = [[Dog alloc] init];
[p setValue:dog forKey:@"dog"];
使用setValue方法,就能夠進行對屬性進行設置值操做了,同時須要傳遞這個屬性的名稱,這個和Java中使用反射機制真的很像。

注:KVC設置值時,若是屬性有set方法,則優先調用set方法,若是沒有則直接設置上去,get方法同樣blog

//設置基本數據類型
//這裏須要將基本類型轉化成NSNumber
//在設置值的時候,會有自動解包的過程,NSNumber會解包賦值給age
[p setValue:@22 forKey:@"age"];
還有一個須要注意的地方:當咱們在設置基本類型的時候,須要將其轉化成NSNumber類型的。

二、取屬性的值

//讀取值
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"];
        
        //基本數據類型會自動被包裝成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;
}


一、首先經過前面說到的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"];
添加了兩本書籍


二、下面就開始用到KVC中鍵值路徑了

1)獲取做者類中書籍數組中全部書籍的價格

//基本數據類型會自動被包裝成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類型的


2、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
看到了在這裏就開始進行監聽操做了


下面來具體看一下如何作到監聽的

一、添加監聽對象

咱們使用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"];

二、監聽方法

//觸發方法
- (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中就會有幾個鍵值對


三、銷燬方法

這個並不屬於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:監聽類中屬性值變化的

相關文章
相關標籤/搜索