Key-Value Coding Programming Guide

官方文檔點(diǎn)這里:Key-Value Coding Programming GuideNSKey?Value?Coding扣汪。


Getting Started


About Key-Value Coding


Key-value coding 允許間接訪問實(shí)現(xiàn)了 NSKeyValueCoding 非正式協(xié)議的對象的屬性匪蟀,間接訪問的功能是由直接訪問功能提供的典予。間接訪問是指通過字符串颖杏,直接訪問是指實(shí)例變量和訪問器,比如 getter盒使。

通過變量名訪問實(shí)例變量,或者通過訪問器訪問屬性七嫌,是比較直接的少办,但隨著屬性數(shù)量的增加或者修改,訪問這些屬性的代碼也要跟著修改诵原。與之相比英妓,kvc 通過字符串訪問屬性就簡潔多了挽放。


Using Key-Value Coding Compliant Objects

直接或間接繼承自 NSObject 的對象,都遵循 NSKeyValueCoding 協(xié)議蔓纠。后面的章節(jié)會(huì)詳細(xì)介紹辑畦,使用這些對象可以做下列事情:

  • Access object properties.
  • Manipulate collection properties.
  • Invoke collection operators on collection objects.
  • Access non-object properties.
  • Access properties by key path.


Adopting Key-Value Coding for an Object

自定義對象實(shí)現(xiàn) NSKeyValueCoding 協(xié)議就可以使用 kvc。 NSObject 的子類已經(jīng)實(shí)現(xiàn)該協(xié)議腿倚。為了使 kvc 能正常工作纯出,要保證訪問器和實(shí)例變量遵守一些定義好的規(guī)則(文檔并沒有說是哪些規(guī)則)。


Key-Value Coding with Swift

在 Swift 中猴誊,NSObject 的子類都能使用 kvc潦刃。和 OC 不一樣,Swift 的屬性自動(dòng)遵循 kvc 的一些規(guī)則懈叹。有些特點(diǎn)不適合 Swift念逞,比如 Swift 的屬性都是對象,kvc 中處理非對象屬性的規(guī)則就不適用于 Swift稀火。


Other Cocoa Technologies Rely on Key-Value Coding

kvc 是很多功能的基礎(chǔ)亭敢,詳情請看官方文檔:

  • Key-value observing.
  • Cocoa bindings.
  • Core Data.
  • AppleScript.


Key-Value Coding Fundamentals


Accessing Object Properties


對象頭文件中定義的屬性,屬于下列中的一種:

  • Attributes. These are simple values, such as a scalars, strings, or Boolean values. Value objects such as NSNumber and other immutable types such as as NSColor are also considered attributes.
  • To-one relationships. These are mutable objects with properties of their own. An object’s properties can change without the object itself changing. For example, a bank account object might have an owner property that is an instance of a Person object, which itself has an address property. The owner’s address may change without changing the owner reference held by the bank account. The bank account’s owner did not change. Only her address did.
  • To-many relationships. These are collection objects. You commonly use an instance of NSArray or NSSet to hold such a collection, although custom collection classes are also possible.
Listing 2-1Properties of the BankAccount object

@interface BankAccount : NSObject
 
@property (nonatomic) NSNumber* currentBalance;              // An attribute
@property (nonatomic) Person* owner;                         // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
 
@end


Identifying an Object’s Properties with Keys and Key Paths

key 是一個(gè)字符串墨状,和屬性同名卫漫。key 必須使用 ASCII 編碼,不能包含空格肾砂,通常是小寫字母開頭列赎。

// 這兩句效果是一樣的,使用 kvc 更靈活镐确。
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
[myAccount setCurrentBalance:@(100.0)];

key path 是一個(gè)用點(diǎn)和 keys 連接成的字符串包吝,比如 owner.address.street。


Getting Attribute Values Using Keys

遵循 NSKeyValueCoding 協(xié)議的對象可以使用 kvc 源葫。NSObject 已經(jīng)實(shí)現(xiàn)了該協(xié)議诗越,因此它的子類都可以使用 kvc。至少可以使用下列函數(shù):

  • valueForKey: 息堂,通過字符串獲取屬性值嚷狞。如果找不到屬性,會(huì)調(diào)用 valueForUndefinedKey: 函數(shù)荣堰,該函數(shù)默認(rèn)實(shí)現(xiàn)是拋出 NSUndefinedKeyException 異常床未。
  • valueForKeyPath:,默認(rèn)調(diào)用 valueForKey:振坚。
  • dictionaryWithValuesForKeys:薇搁,傳入一個(gè)字符串?dāng)?shù)組,返回一個(gè)包含所有 key 和對應(yīng) value 的字典屡拨。默認(rèn)調(diào)用 valueForKey:只酥。

注意
NSArray, NSSet, and NSDictionary 不能包含 nil褥实,使用 NSNull 對象代替。 NSNull 提供單一實(shí)例裂允,代表 nil 對象损离。dictionaryWithValuesForKeys: and the related setValuesForKeysWithDictionary:會(huì)自動(dòng)轉(zhuǎn)換 nil 和 NSNull。

NSArray *array = @[@123, [NSNull null], @456];
// nil, Nil, NULL, NSNull, [NSNull null] 的區(qū)別绝编?
// nil 用于對象僻澎,Nil 用于類,NULL 用于非對象類型的變量十饥,[NSNull null] 用于數(shù)組和字典窟勃。

如果 key path 訪問的是 to-many relationship,那么返回值是一個(gè) collection逗堵,包含 key 對應(yīng)的所有的值秉氧。比如 transactions.payee,返回一個(gè)數(shù)組蜒秤,包含每一個(gè) transaction 的 payee汁咏。這個(gè)對多層數(shù)組也適用,比如 accounts.transactions.payee作媚,返回一個(gè)數(shù)組攘滩,包含所有的 account 的 transaction 的 payee。


Setting Attribute Values Using Keys

NSObject 實(shí)現(xiàn)了 NSKeyValueCoding 協(xié)議的幾個(gè) setter 方法:

  • setValue:forKey:纸泡,自動(dòng)對 NSValue 和 NSNumber 拆箱漂问,然后給 標(biāo)量和結(jié)構(gòu)體屬性賦值。詳情請看 Representing Non-Object Values 章節(jié)女揭。如果找不到 key 對應(yīng)的屬性蚤假,會(huì)調(diào)用 setValue:forUndefinedKey:,該函數(shù)默認(rèn)拋出 NSUndefinedKeyException 異常田绑。
  • setValue:forKeyPath:勤哗,默認(rèn)調(diào)用 setValue:forKey:抡爹。
  • setValuesForKeysWithDictionary: 掩驱,默認(rèn)調(diào)用 setValue:forKey:,使用 NSNull 對象代替 nil冬竟。

對非對象屬性設(shè)置 nil 值欧穴,系統(tǒng)會(huì)調(diào)用 setNilValueForKey:,該函數(shù)默認(rèn)拋出 NSInvalidArgumentException 異常泵殴,詳情請看 Handling Non-Object Values 章節(jié)涮帘。


Using Keys to Simplify Object Access

使用 kvc 精簡代碼的例子:在 macOS 中,NSTableView 和 NSOutlineView 的每一列都有一個(gè) identifier 字符串相對應(yīng)笑诅。在數(shù)據(jù)源方法中调缨,根據(jù) identifier 判斷是給哪一列賦值疮鲫,然后獲取數(shù)據(jù)模型對應(yīng)的屬性值,代碼如 Listing 2-2 所示弦叶。

Listing 2-2 Implementation of data source method without key-value coding

- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
    id result = nil;
    Person *person = [self.people objectAtIndex:row];
 
    if ([[column identifier] isEqualToString:@"name"]) {
        result = [person name];
    } else if ([[column identifier] isEqualToString:@"age"]) {
        result = @([person age]);  // Wrap age, a scalar, as an NSNumber
    } else if ([[column identifier] isEqualToString:@"favoriteColor"]) {
        result = [person favoriteColor];
    } // And so on...
 
    return result;
}

在代碼 Listing 2-3 中俊犯,省略了對 identifier 的判斷,直接用 kvc 獲取 identifier 對應(yīng)的屬性值伤哺。更重要的是燕侠,以后增加了列名和數(shù)據(jù)模型屬性,這里的代碼也不用修改了立莉,因?yàn)槭峭ㄟ^ identifier 來獲取對應(yīng)的屬性值绢彤。

Listing 2-3Implementation of data source method with key-value coding

- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{
    return [[self.people objectAtIndex:row] valueForKey:[column identifier]];
}


Accessing Collection Properties


對于數(shù)組和集合,也可以使用 valueForKey: 和 setValue:forKey: 方法獲取和設(shè)置蜓耻。但如果要修改其中的元素茫舶,使用代理方法更加高效。

三種代理方法:

  • mutableArrayValueForKey: 和 mutableArrayValueForKeyPath:
    返回一個(gè)代理對象刹淌,行為類似 NSMutableArray object奇适。
  • mutableSetValueForKey: 和 mutableSetValueForKeyPath:
    返回一個(gè)代理對象,行為類似 NSMutableSet object芦鳍。
  • mutableOrderedSetValueForKey: 和 mutableOrderedSetValueForKeyPath:
    返回一個(gè)代理對象嚷往,行為類似 NSMutableOrderedSet object。

假設(shè)有一個(gè)屬性柠衅,不可變的數(shù)組對象:

@property (nonatomic, strong) NSArray *array;

// 初始化數(shù)組
self.array = @[@1,@2,@3];

使用 kvc 修改數(shù)組元素皮仁,比如增刪改,方法一:

NSArray *array = [self valueForKey:@"array"];
// 用 array 的值創(chuàng)建一個(gè)新的數(shù)組
array = ...
[self setValue:array forKey:@"array"];

方法一比較麻煩菲宴,要先獲取原來的數(shù)組贷祈,然后創(chuàng)建新的數(shù)組,最后再通過 kvc 賦值喝峦。

使用代理就簡單很多势誊,方法二:

NSMutableArray *ma = [self mutableArrayValueForKey:@"array"];
ma[0] = @998; // 變化類型是 NSKeyValueChangeReplacement

這種方法代碼簡單,效率更高谣蠢,而且有額外的 kvo 好處粟耻,也就是當(dāng)數(shù)組發(fā)生增刪改時(shí),觀察者可以收到通知:

// 注冊觀察者
[self addObserver:self forKeyPath:@"array" options: NSKeyValueObservingOptionNew context:NULL];

// 觀察者處理通知
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    // 輸出 new = ( 998 )
    NSLog(@"new = %@", change[NSKeyValueChangeNewKey]);
}

對可變和不可變的數(shù)組眉踱、集合都有用挤忙,有時(shí)候比可變數(shù)組或集合自帶的增刪改方法都要高效。當(dāng)數(shù)組元素發(fā)生改變想收到通知時(shí)谈喳,可以使用這些代理方法册烈。


Using Collection Operators


在使用 valueForKeyPath:時(shí),可以在 key path 中加一個(gè)@和集合操作符婿禽。

如圖 4-1所示赏僧,中間是操作符大猛,左邊是目標(biāo)集合,右邊是集合中的元素的屬性淀零。如果是給集合對象發(fā)送消息 valueForKeyPath:胎署,比如數(shù)組,左邊可以省略窑滞。

Figure 4-1Operator key path format
// demo
// @avg 是求平均值琼牧。對數(shù)組 transactions 中的元素的 amount 屬性求平均值。
NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];


Sample Data

BankAccount 類:

Listing 2-1Properties of the BankAccount object

@interface BankAccount : NSObject

@property (nonatomic) NSNumber* currentBalance;              // An attribute
@property (nonatomic) Person* owner;                         // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation

@end

Transaction 類:

Listing 4-1Interface declaration for the Transaction object

@interface Transaction : NSObject
 
@property (nonatomic) NSString* payee;   // To whom
@property (nonatomic) NSNumber* amount;  // How much
@property (nonatomic) NSDate* date;      // When
 
@end

數(shù)組 transactions 數(shù)據(jù):

Table 4-1Example data for the Transactions objects


Aggregation Operators

用于 array 和 set哀卫。

@avg

求平均值巨坊。讀取集合中的每一個(gè)元素,轉(zhuǎn)換成 double 類型(用 0 代替 nil 值)此改,求平均值后返回一個(gè) NSNumber 對象趾撵。

// @avg 是操作符,amount 是屬性名共啃。
NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];

用 Table 4-1的數(shù)據(jù)算的結(jié)果是 $456.54占调。


@count

求個(gè)數(shù),返回一個(gè) NSNumber 對象移剪。

NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];

結(jié)果是 13究珊。


@max

求最大值。比較的過程使用 compare: 函數(shù)纵苛,忽略 nil 值剿涮。

NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];

結(jié)果是 Jul 15, 2016。


@min

求最小值攻人。比較的過程使用 compare: 函數(shù)取试,忽略 nil 值。

NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];

結(jié)果是 Dec 1, 2015怀吻。


@sum

求和瞬浓。讀取集合中的每一個(gè)元素,轉(zhuǎn)換成 double 類型(用 0 代替 nil 值)蓬坡,求和后返回一個(gè) NSNumber 對象猿棉。

NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];

結(jié)果是 $5,935.00。


Array Operators

創(chuàng)建并返回一個(gè)數(shù)組渣窜。注意铺根,key path 中有 nil 值會(huì)引起異常宪躯。


@distinctUnionOfObjects

創(chuàng)建并返回一個(gè)數(shù)組乔宿,包含 right key path 指定的屬性。會(huì)去掉重復(fù)的屬性访雪。

NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];

數(shù)組 distinctPayees 里面都是 payee详瑞,分別是 Car Loan, General Cable, Animal Hospital, Green Power, Mortgage掂林。


@unionOfObjects

創(chuàng)建并返回一個(gè)數(shù)組,包含 right key path 指定的屬性坝橡。保留重復(fù)的屬性泻帮。

NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];

數(shù)組 payees 里面都是 payee,分別是 Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital计寇。


Nesting Operators

用于嵌套集合(集合的元素也是集合)锣杂。注意,key path 中有 nil 值會(huì)引起異常番宁。

使用 Table 4-2 的數(shù)據(jù)再創(chuàng)建一個(gè)數(shù)組 moreTransactions:

NSArray* moreTransactions = @[< transaction data >]; // 右邊是偽代碼
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];
Table 4-2 Hypothetical Transaction data in the moreTransactions array


@distinctUnionOfArrays

創(chuàng)建并返回一個(gè)數(shù)組元莫,數(shù)組包含所有數(shù)組的元素的屬性。會(huì)去掉重復(fù)的屬性蝶押。

NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];

數(shù)組 arrayOfArrays 有兩個(gè)元素踱蠢,分別是數(shù)組 self.transactions 和 moreTransactions。結(jié)果 collectedDistinctPayees 包含上述兩個(gè)數(shù)組的所有 payee:Hobby Shop, Mortgage, Animal Hospital, Second Mortgage, Car Loan, General Cable - Cottage, General Cable, Green Power棋电。


@unionOfArrays

創(chuàng)建并返回一個(gè)數(shù)組茎截,數(shù)組包含所有數(shù)組的元素的屬性。保留重復(fù)的屬性赶盔。

NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];

結(jié)果 collectedDistinctPayees 包含上述兩個(gè)數(shù)組的所有 payee:Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital, General Cable - Cottage, General Cable - Cottage, General Cable - Cottage, Second Mortgage, Second Mortgage, Second Mortgage, Hobby Shop企锌。


@distinctUnionOfSets

創(chuàng)建并返回一個(gè)set,包含所有set的元素的屬性于未。和 @distinctUnionOfArrays 是類似的霎俩,只不過是對 NSSet 中的 NSSet 進(jìn)行操作,并且返回的是 NSSet沉眶。

假設(shè)用的數(shù)據(jù)和數(shù)組的一樣打却,那么結(jié)果和 @distinctUnionOfArrays 是一樣的。


Representing Non-Object Values

由于 Swift 所有的屬性都是對象谎倔,所以本章節(jié)只適用于 Objective-C properties柳击。

kvc 自動(dòng)轉(zhuǎn)換對象和非對象屬性。非對象屬性比如 int片习、float 等標(biāo)量捌肴,以及結(jié)構(gòu)體。

kvc 默認(rèn)是調(diào)用屬性的訪問器藕咏。當(dāng) getter 發(fā)現(xiàn)返回值不是對象時(shí)状知,會(huì)先轉(zhuǎn)換成 NSNumber 對象(對于標(biāo)量,比如 int) 或者 NSValue 對象(對于結(jié)構(gòu)體)孽查,然后再返回饥悴。類似的,當(dāng) setter 發(fā)現(xiàn)屬性不是對象時(shí),會(huì)先調(diào)用 <type>Value 方法(比如 intValue)拆箱西设,再給非對象屬性進(jìn)行賦值瓣铣。

注意
kvc 給非對象屬性賦值 nil 時(shí),會(huì)調(diào)用 setNilValueForKey: 贷揽,該函數(shù)默認(rèn)拋出 NSInvalidArgumentException 異常棠笑。子類可以重寫該函數(shù)。詳情請看 Handling Non-Object Values 章節(jié)禽绪。


Wrapping and Unwrapping Scalar Types

標(biāo)量的裝箱和拆箱蓖救。裝箱是指封裝成對象,拆箱是指把對象拆成標(biāo)量印屁。對于標(biāo)量藻糖,kvc 使用 NSNumber 進(jìn)行轉(zhuǎn)換。Table 5-1 是標(biāo)量與 NSNumber 轉(zhuǎn)換表库车。Data type 是標(biāo)量類型巨柒,Creation method 是創(chuàng)建 NSNumber 對象的方法,Accessor method 是獲取 NSNumber 對象的標(biāo)量值柠衍。

// demo 
int age = 18;
// 裝箱
NSNumber *ageNumber = [NSNumber numberWithInt:age];
// 也可以使用語法糖
ageNumber = @(age);
ageNumber = @(18);
// 拆箱
age = ageNumber.intValue;
Table 5-1 Scalar types as wrapped in NSNumber objects

注意
在 macOS洋满,對 BOOL 變量調(diào)用 setValue:forKey:時(shí),不要傳 @“true” 或 @“YES”珍坊,而是 @(1) 或 @(YES)牺勾。因?yàn)樵诮o BOOL 變量賦值前,會(huì)調(diào)用 charValue 進(jìn)行拆箱阵漏,但是 NSString 沒有實(shí)現(xiàn)這個(gè)函數(shù)驻民,會(huì)引起運(yùn)行時(shí)異常。會(huì)調(diào)用 charValue 進(jìn)行拆箱是因?yàn)闅v史原因履怯,BOOL 類型定義為 signed char回还。總之叹洲,給 BOOL 變量賦值時(shí)柠硕,傳 NSNumber 對象就行,不要傳 NSString 類型运提。
在 iOS 就沒有這個(gè)問題蝗柔。在 iOS 中,kvc 給 BOOl 變量賦值前民泵,會(huì)調(diào)用 boolValue 進(jìn)行拆箱癣丧,NSString 和 NSNumber 都實(shí)現(xiàn)了 boolValue 函數(shù)。在 macOS 中栈妆,kvc 給 BOOl 變量賦值前胁编,會(huì)調(diào)用 charValue 進(jìn)行拆箱厢钧,NSString 沒有實(shí)現(xiàn)這個(gè)函數(shù),NSNumber 實(shí)現(xiàn)了掏呼。


Wrapping and Unwrapping Structures

對于結(jié)構(gòu)體坏快,kvc 使用 NSValue 進(jìn)行轉(zhuǎn)換铅檩。Table 5-2 是 NSPoint, NSRange, NSRect, NSSize 結(jié)構(gòu)體與 NSValue 轉(zhuǎn)換表憎夷。Data type 是結(jié)構(gòu)體類型,Creation method 是創(chuàng)建 NSValue 的方法昧旨,Accessor method 是轉(zhuǎn)換成結(jié)構(gòu)體的方法拾给。

// demo
CGPoint point = CGPointMake(10, 20);
NSValue *pointValue = [NSValue valueWithCGPoint:point];
point = pointValue.CGPointValue;
Table 5-2 Common struct types as wrapped using NSValue

自定義的結(jié)構(gòu)體如何轉(zhuǎn)換 NSValue 呢?Listing 5-1A 定義了一個(gè)結(jié)構(gòu)體和一個(gè)類兔沃。

Listing 5-1A sample class using a custom structure

typedef struct {
    float x, y, z;
} ThreeFloats;
 
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end

myClass 是類 MyClass 的對象蒋得。setValue:forKey: 先調(diào)用 NSValue 的 getValue: 方法拆箱,然后調(diào)用 setThreeFloats: 賦值乒疏。

// 結(jié)構(gòu)體轉(zhuǎn) NSValue
ThreeFloats floats = {1, 2, 3};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];

valueForKey: 先調(diào)用 threeFloats getter额衙,然后裝箱成一個(gè) NSValue 對象再返回。

// NSValue 轉(zhuǎn)結(jié)構(gòu)體
NSValue* result = [myClass valueForKey:@"threeFloats"];
ThreeFloats floats2;
[result getValue:&floats2];


Validating Properties


kvc 可以通過 key 或者 key path 訪問屬性怕吴,也可以驗(yàn)證屬性窍侧。調(diào)用 validateValue:forKey:error: (或 validateValue:forKeyPath:error:) 時(shí),默認(rèn)實(shí)現(xiàn)是查找被執(zhí)行 kvc 的對象转绷,是否有類似 validate<Key>:error: 的函數(shù)(比如 validateName:error:)伟件,如果沒找到,默認(rèn)驗(yàn)證是成功的议经,并且默認(rèn)返回 YES斧账。如果找到了,就用調(diào)用 validate<Key>:error: 函數(shù)煞肾,并且返回它的返回值咧织。

注意
驗(yàn)證屬性值只適用于 Objective-C。Swift 使用自帶的 willSet 和 didSet 屬性可以驗(yàn)證任何的 run-time API籍救。

先看一下函數(shù)的定義:

- (BOOL)validateValue:(inout id  _Nullable *)ioValue 
               forKey:(NSString *)inKey 
                error:(out NSError * _Nullable *)outError;
  • 參數(shù) ioValue 是一個(gè)指針拯爽,指針,指針钧忽。如果驗(yàn)證無效毯炮,可能會(huì)讓它指向一個(gè)新創(chuàng)建的值。
  • 參數(shù) outError 如果不為 NULL耸黑,驗(yàn)證無效的時(shí)候可能會(huì)讓它指向一個(gè) NSError 對象桃煎。

驗(yàn)證有3種結(jié)果:

  • 值有效,返回 YES大刊,不修改 error 和 value为迈。
  • 值無效三椿,但是選擇不修改值。這種情況下葫辐,返回 NO搜锰,并且如果 error 不為 NULL,修改 error 指向一個(gè) NSError 對象耿战,里面包含無效的原因蛋叼。
  • 值無效,但是創(chuàng)建一個(gè)新的有效值剂陡。這種情況下狈涮,返回 YES,并且不修改 error鸭栖。在返回之前歌馍,會(huì)修改 value 指向新創(chuàng)建的有效值。一般是讓 value 指向新創(chuàng)建的值晕鹊,而不是修改原來的值松却,即使它是可變的。

Listing 6-1 演示驗(yàn)證的例子:

Listing 6-1 Validation of the name property

Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
    NSLog(@"%@",error);
}

示例中的 validateValue:forKey:error: 函數(shù)溅话,會(huì)查找 validateName:error: 函數(shù)晓锻,實(shí)現(xiàn)可能是這樣的:

- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
    if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2)) {
        if (outError != NULL) {
            *outError = [NSError errorWithDomain:PersonErrorDomain
                                            code:PersonInvalidNameCode
                                        userInfo:@{ NSLocalizedDescriptionKey
                                                    : @"Name too short" }];
        }
        return NO;
    }
    return YES;
}

另一個(gè)驗(yàn)證標(biāo)量的函數(shù):

- (BOOL)validateAge:(id *)ioValue error:(NSError * __autoreleasing *)outError {
    if (*ioValue == nil) {
        // Value is nil: Might also handle in setNilValueForKey
        *ioValue = @(0);
    } else if ([*ioValue floatValue] < 0.0) {
        if (outError != NULL) {
            *outError = [NSError errorWithDomain:PersonErrorDomain
                                            code:PersonInvalidAgeCode
                                        userInfo:@{ NSLocalizedDescriptionKey
                                                    : @"Age cannot be negative" }];
        }
        return NO;
    }
    return YES;
}

這里年齡無效,修改為新的值后返回一個(gè) YES公荧,或者不修改為新值带射,error 不為 NULL,就創(chuàng)建一個(gè) NSError 對象循狰,然后返回 NO窟社。

詳情請看這里:Adding Validation


Automatic Validation

kvc 一般情況下不會(huì)自動(dòng)驗(yàn)證绪钥,如果 app 需要灿里,可以手動(dòng)驗(yàn)證。

In general, neither the key-value coding protocol nor its default implementation define any mechanism to perform validation automatically. Instead, you make use of the validation methods when appropriate for your app.

有些 Cocoa 技術(shù)在某些情況會(huì)自動(dòng)驗(yàn)證程腹,比如 Core Data 當(dāng) 保存 managed object context 時(shí)會(huì)自動(dòng)驗(yàn)證(see Core Data Programming Guide)匣吊。


Accessor Search Patterns


kvc 協(xié)議方法在尋找 accessors, instance variables, 和 related methods 時(shí),是按一定的規(guī)則去尋找的寸潦。了解規(guī)則對跟蹤 kvc 的行為或者讓自定義對象實(shí)現(xiàn) kvc 都有好處色鸳。

注意
<key> 是一個(gè)占位符。For example, for the getters <key> and is<Key>, the property named hidden maps to hidden and isHidden.


Search Pattern for the Basic Getter

  1. 首先按順序查找訪問器 get<Key>, <key>, is<Key>, 或者 _<key>见转,找到了跳到第5步命雀,否則轉(zhuǎn)第2步。
  2. 查找 countOf<Key> 和 objectIn<Key>AtIndex: (corresponding to the primitive methods defined by the NSArray class) 和 <key>AtIndexes: (corresponding to the NSArray method objectsAtIndexes:)斩箫。
    如果找到第一個(gè)和后兩個(gè)中的至少一個(gè)吏砂,創(chuàng)建和返回一個(gè)能響應(yīng)所有 NSArray 方法的 collection proxy 對象撵儿。否則轉(zhuǎn)第3步。
    剩下這段還是看原文吧:The proxy object subsequently converts any NSArray messages it receives to some combination of countOf<Key>, objectIn<Key>AtIndex:, and <key>AtIndexes: messages to the key-value coding compliant object that created it. If the original object also implements an optional method with a name like get<Key>:range:, the proxy object uses that as well, when appropriate. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were an NSArray, even if it is not.
  3. 查找 countOf<Key>, enumeratorOf<Key>, 和 memberOf<Key>: (corresponding to the primitive methods defined by the NSSet class)狐血。
    如果3個(gè)方法都找到了淀歇,創(chuàng)建并返回一個(gè)能響應(yīng)所有 NSSet 方法的 collection proxy 對象。
    剩下這段看原文:This proxy object subsequently converts any NSSet message it receives into some combination of countOf<Key>, enumeratorOf<Key>, and memberOf<Key>: messages to the object that created it. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were an NSSet, even if it is not.
  4. 如果接收者的類方法 accessInstanceVariablesDirectly 返回 YES匈织,按順序查找實(shí)例變量 _<key>, _is<Key>, <key>, 或者 is<Key>浪默。如果找到了,直接獲取實(shí)例變量的值并跳轉(zhuǎn)第5步报亩,否則跳轉(zhuǎn)第6步浴鸿。
  5. 如果取得的屬性值是一個(gè)對象指針井氢,直接返回弦追。如果值是標(biāo)量類型,轉(zhuǎn)換為 NSNumber 對象再返回花竞。如果是結(jié)構(gòu)體劲件,轉(zhuǎn)換為 NSValue 對象再返回。
  6. 如果都失敗了约急,調(diào)用 valueForUndefinedKey: 方法零远,該方法默認(rèn)拋出一個(gè)異常。


Search Pattern for the Basic Setter

  1. 按順序查找訪問器 set<Key>: 或者 _set<Key>厌蔽,如果找到了就調(diào)用(按需要自動(dòng)拆箱)牵辣,結(jié)束。
  2. 如果類方法 accessInstanceVariablesDirectly 返回 YES奴饮,按順序查找實(shí)例變量 _<key>, _is<Key>, <key>, 或者 is<Key>纬向。如果找到了就直接賦值(按需要自動(dòng)拆箱),結(jié)束戴卜。
  3. 如果都找不到逾条,調(diào)用 setValue:forUndefinedKey: 方法。該方法默認(rèn)拋出一個(gè)異常投剥。


剩下的小節(jié)有 Search Pattern for Mutable Arrays师脂、Search Pattern for Mutable Ordered Sets、Search Pattern for Mutable Sets江锨,感興趣的點(diǎn)這里:Accessor Search Patterns吃警。


Adopting Key-Value Coding


NSObject 的子類都遵從 kvc,除非要自己定義一個(gè)類才要實(shí)現(xiàn) kvc 協(xié)議啄育,這種情況比較少就不記錄了酌心。看了一下還是有收獲的灸撰,感興趣的點(diǎn)這里:Adopting Key-Value Coding谒府。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拼坎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子完疫,更是在濱河造成了極大的恐慌泰鸡,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壳鹤,死亡現(xiàn)場離奇詭異盛龄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)芳誓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門余舶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锹淌,你說我怎么就攤上這事匿值。” “怎么了赂摆?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵挟憔,是天一觀的道長。 經(jīng)常有香客問我烟号,道長绊谭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任汪拥,我火速辦了婚禮达传,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘迫筑。我一直安慰自己宪赶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布铣焊。 她就那樣靜靜地躺著逊朽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪曲伊。 梳的紋絲不亂的頭發(fā)上叽讳,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音坟募,去河邊找鬼岛蚤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛懈糯,可吹牛的內(nèi)容都是我干的涤妒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼赚哗,長吁一口氣:“原來是場噩夢啊……” “哼她紫!你這毒婦竟也來了硅堆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤贿讹,失蹤者是張志新(化名)和其女友劉穎渐逃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體民褂,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茄菊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赊堪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片面殖。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖哭廉,靈堂內(nèi)的尸體忽然破棺而出脊僚,到底是詐尸還是另有隱情,我是刑警寧澤群叶,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布吃挑,位于F島的核電站钝荡,受9級特大地震影響街立,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜埠通,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一赎离、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧端辱,春花似錦梁剔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渗柿,卻和暖如春个盆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背朵栖。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工颊亮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陨溅。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓终惑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親门扇。 傳聞我的和親對象是個(gè)殘疾皇子雹有,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • KVC 需要遵從協(xié)議 NSKeyValueCoding , NSObject 遵從此協(xié)議偿渡,實(shí)現(xiàn)協(xié)議方法,所以大多數(shù)...
    花與少年_閱讀 440評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理霸奕,服務(wù)發(fā)現(xiàn)卸察,斷路器,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉铅祸,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評論 0 9
  • 官方文檔點(diǎn)這里:Key-Value Observing Programming Guide坑质、NSKey?Value...
    阿斯蘭iOS閱讀 533評論 0 1
  • About key-value coding(關(guān)于KVC) 關(guān)鍵值編碼是一種機(jī)制,使非正式協(xié)議NSKeyValue...
    edison0428閱讀 937評論 0 0