Objective-C中的KVC與KVO是兩種比較重要的技術,這裏簡要介紹一下這兩者的使用方法。
一、KVC
《iOS程序開發方法與實踐》中介紹了KVC(Key–Value Coding,鍵值編碼)的基本內容。
簡要來說,KVC提供了一種在運行時而非編譯時動態訪問對象屬性與成員變量的方式,也就是說,我們可以用字符串的內容作爲屬性名稱或者成員變量名稱進行訪問。這種特性有些類似於其他高級編程語言中的反射。
舉個例子,例如我們創建了一個類ClassA,其中定義了3個屬性p1、p2和p3,以及1個實例方法getValueByPropertyName:,代碼如下:
@interface ClassA : NSObject
@property (nonatomic) int p1;
@property (nonatomic) int p2;
@property (nonatomic) int p3;
- (int)getValueByPropertyName:(NSString*)pName;
@end
@implementation ClassA
@synthesize p1 = _p1, p2 = _p2, p3 = _p3;
...
- (int)getValueByPropertyName:(NSString*)pName
{
if([pName isEqualToString:@"p1"])
{
return self.p1;
}
else if([pName isEqualToString:@"p2"])
{
return self.p2;
}
else if([pName isEqualToString:@"p3"])
{
return self.p3;
}
return 0;
}
@end
方法getValueByPropertyName:用於通過字符串來訪問對應的屬性值。如果我們想要向ClassA添加幾個屬性,那麼這個方法就還需要添加幾個else if語句。而如果使用KVC就會方便許多,代碼如下:
- (int)getValueByPropertyName:(NSString*)pName
{
NSNumber* pNumber = [self valueForKey:pName];
return [pNumber intValue];
}
這裏有幾點需要注意,首先這裏的valueForKey:方法用於以字符串調用對象的get屬性方法,或者讀取成員變量的值;與之相對的是setValue:forKey:,它用於以字符串調用對象的set屬性方法,或者修改成員變量的值。第二點需要注意的是,對於基本數據類型,KVC方法會對基本數據類型進行封裝(基本數據類型封裝爲NSNumber,其他結構體類型封裝爲NSValue)。這裏的p1、p2、p3定義爲int型,而valueForKey:方法返回的是NSNumber對象,需要再調用intValue取出其中的值。setValue:forKey:方法與之類似,接收NSNumber參數。第三,在使用KVC時,如果找不到字符串對應的屬性和成員變量時會怎麼樣?此時會調用valueForUndefinedKey:或者setValue:forUndefinedKey:這兩個方法,默認情況下會拋出異常。第四,默認情況下KVC方法能夠直接訪問類的私有成員變量,如果我們不想這樣,可以重寫accessInstanceVariablesDirectly方法,並令其返回NO(默認是返回YES)。KVC方法定義在NSKeyValueCoding類別中,該類別附加於NSObject類上,所以所有對象都具有這些方法。第五,在一些特殊的類的對象上調用KVC方法會有特別的效果。對於數組NSArray、集合NSSet,調用valueForKey:會對每個數組和集合成員調用valueForKey:,並返回新的數組或者集合。
在KVC中還有一種常用技術,稱爲鍵值鏈(Key Path)。鍵值鏈是用點將若干鍵相連的字符串,例如“manufacturer.product.name”。通過在對象上調用valueForKeyPath:或者setValue:forKeyPath:,我們就可以在一條語句中接連調用制定的屬性。請看下面的例子:
@interface Product : NSObject
@property NSString* name;
@end
@implementation Product
@synthesize name = _name;
- (void)dealloc
{
self.name = nil;
[super dealloc];
}
@end
@interface Manufacturer : NSObject
@property Product* product;
@end
@implementation Manufacturer
@synthesize product = _product;
-(void)dealloc
{
self.product = nil;
[super dealloc];
}
@end
int main(int argc, const char * argv[])
{
@autoreleasepool
{
Manufacturer* apple = [[[Manufacturer alloc] init] autorelease];
Product* iPhone4S = [[[Product alloc] init] autorelease];
iPhone4S.name = @"iPhone 4S";
apple.product = iPhone4S;
Manufacturer* micro$oft = [[[Manufacturer alloc] init] autorelease];
Product* win8 = [[[Product alloc] init] autorelease];
win8.name = @"Windows 8";
micro$oft.product = win8;
NSArray* manufacturerArray = [NSArray arrayWithObjects:apple, micro$oft, nil];
NSArray* productNameArray = [manufacturerArray valueForKeyPath:@"product.name"];
NSLog(@"productNameArray:\n%@", productNameArray);
}
return 0;
}
首先程序定義了兩個類Product與Manufacturer,分別定義商品與製造商。Manufacturer類定義了一個product屬性,Product類定義了name屬性。在main函數中,我們創建了兩個製造商蘋果和微軟,以及兩個商品iPhone4S和Win8,並設置好他們之間的關係。然後程序將兩個製造商放入數組中,在數組上調用valueForKeyPath:方法並傳入鍵值鏈@"product.name"。於是數組首先執行valueForKey:@"product",它會在每個數組成員上調用product屬性,獲取每個製造商的商品,然後再在每個商品上調用valueForKey:@"name",獲取每個製造商的每個商品的名稱,並將結果放到另一個數組中。上述程序的輸出結果爲:productNameArray:
(
"iPhone 4S",
"Windows 8"
)
使用KVC的好處是非常靈活,但同時也丟失了編譯時檢查。
在下一篇中,我將會介紹KVO的使用方法。