字典轉模型 1> 什麼是字典轉模型? 字典數據/數組(可以是 plist 文件中的數據也可以是網路後臺的數據等)轉化為模型對象/數組. 2> 註意 模型要提供可以傳入字典參數的構造方法.(一個對象方法和一個類方法) - (instancetype)initWithDict:(NSDictionary ...
字典轉模型
1> 什麼是字典轉模型?
字典數據/數組(可以是 plist 文件中的數據也可以是網路後臺的數據等)轉化為模型對象/數組.
2> 註意
模型要提供可以傳入字典參數的構造方法.(一個對象方法和一個類方法)
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)xxxWithDict:(NSDictionary *)dict;
3> 提示:在模型中儘量的使用只讀屬性可以進一步降低代碼的耦合性
4> 優點:
- 將字典中的數據封裝到一個模型類中,有 MVC 框架思想的優點,可以降低耦合性
- 所有的字典轉模型可以寫在一處,便於管理,同時也可以降低代碼的出錯率
- 字典轉模型後,直接把字典中(後臺)的數據轉移到模型的屬性中,外界可以通過直接調用模型的屬性來獲取數據,從而可以提高代碼的編碼效率
- 由於模型是一個單獨的類中進行的,外界不用關心類中的細節,只要使用就可以,更好的體現了面向對象的思想.
5> 使用步驟(簡單舉例)
- 在模型的.h 文件中聲明所需要的所有屬性
- 在模型的.h實例化兩個方法
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)appInfoWithDict:(NSDictionary *)dict;
- 在模型的.m 文件中,通過實現兩個方法獲取數據
- (instancetype)initWithDict:(NSDictionary *)dict{
self = [super init];
if (self) {//給聲明的屬性賦值
self.name = dict[@"name"];
self.icon = dict[@"icon"];
}
return self;
}
+ (instancetype)appInfoWithDict:(NSDictionary *)dict{
return [[self alloc] initWithDict:dict];
}
- 聲明空控制項的屬性,併在懶載入中直接實例化賦值
- 在 VC 的. m 文件中聲明一個目標控制項的屬性
- 懶載入控制項:在懶載入的判斷為空的時候,通過 bundle à path à 通過 path 路徑把數據轉到模型(數組)中 à 創建一個臨時可變數組(用於盛放數據) à 遍歷通過路徑獲取的數據 à 給臨時數組賦值 à 將臨時數組賦值給模型對象
6 KVC 下的字典轉模型
> 什麼是 KVC?
KVC -- key value coding鍵值編碼 (註:KVO key value observe 鍵值觀察 監聽某個模型對象的屬性,當屬性改變時會及時通知你)
> KVC 是作用?
KVC 可以理解為cocoa 的大招,是指在程式運行過程中,可以動態的給屬性進行賦值,包括只讀屬性和私有屬性.(可以理解為,只要對象有屬性,就能給屬性賦值)
> KVC 的使用機理
> 使用 KVC 修改對象的屬性時, KVC 會自動判斷對象的屬性,並完成類型轉換.
> KVC 按照鍵值路徑對象取值的時候,如果對象不包含對應的鍵值,會進入對象內部查找對象屬性.
> KVC 取嵌套很深的路徑的時候,只要一個路徑就能把想要的對象取出來,能幫我們很方便的編碼.
> 使用前提:
使用了KVC,如果有訪問器方法,則運行時會在訪問器方法中調用will/didChangeValueForKey:方法;
如果沒用訪問器方法,運行時會在setValue:forKey方法中調用will/didChangeValueForKey:方法
> 使用場景:
> 字典轉模型: setValuesForKeysWithDictionary
> 模型轉字典: dictionaryWithValuesForKeys
補充:>>>>>>>>>>>>>>>>>>>以下為轉載大神文章<<<<<<<<<<<<<<<<<<<<<<
>>>> 基本概念
1. 它是一種可以直接通過字元串的名字(key)來訪問類屬性的機制。而不是通過調用Setter、Getter方法訪問。
2. 在應用程式中實現鍵-值編碼相容性是一項重要的設計原則。存取方法可以加強合適的數據封裝,而鍵-值編碼方法在多數情況下可簡化程式代碼。
3. 鍵-值編碼支持帶有對象值的屬性,同時也支持純數值類型和結構。非對象參數和返回類型會被識別並自動封裝/解封。
使用 KVC 為對象賦值或者取值時,需要知道準確的鍵值, 相比較點語法,KVC 是一種間接的傳遞方式,這種方式有利於
對象解耦,讓對象彼此之間的耦合度不會太高。
>>>> 設置和訪問
1.鍵/值編碼中的基本調用包括 -valueForKey: 和 -setValue:forkey: 這兩個方法,它們以字元串的形式向對象發送消息,字元串為屬性名.
2.是否存在 setter、getter 方法, 若存在優先調用相應方法;若不存在,它將在內部查找名為 _key 或 key 的實例變數。
3.通過 KVC 設置對象,此對象會 retain。
4.通過 setValue:forKey: 設置對象的值,或通過 valueForKey 來獲取對象的值時,如若對象的實例變數為基本數據類型時 ( char,int,float,BOOL ) ,我們需要對數據進行封裝。
5.賦值語句 setValue:forKey: 是給對象當前的屬性賦值,而 setValue:forKeyPath: 是按照對象的層級關係為其中的屬性賦值。 forKeyPath可以替代forKey,但是forKey不能替代forKeyPath。
>>>> KVC 中常用的方法
> 獲取值
valueForKey:,傳入NSString屬性的名字。
valueForKeyPath:,傳入NSString屬性的路徑,xx.xx形式。
valueForUndefinedKey它的預設實現是拋出異常,可以重寫這個函數做錯誤處理。
> 修改值
setValue:forKey:
setValue:forKeyPath: m
setValue:forUndefinedKey:
setNilValueForKey: 當對非類對象屬性設置nil時,調用,預設拋出異常。
> 一對多關係成員的情況
mutableArrayValueForKey:有序一對多關係成員 NSArray
mutableSetValueForKey:無序一對多關係成員 NSSet
>>>> KVC的實現細節
搜索Setter、Getter方法,這一部分比較重要,能讓你瞭解到KVC調用之後,到底是怎樣獲取和設置類成員值的。
> 搜索簡單的成員 如:基本類型成員,單個對象類型成員:NSInteger,NSString*成員。
a. setValue:forKey的搜索方式:
1. 首先搜索set<Key>:方法
如果成員用@property,@synthsize處理,因為@synthsize告訴編譯器自動生成set<Key>:格式的setter方法,
所以這種情況下會直接搜索到。註意:這裡的<Key>是指成員名,而且首字母大寫。下同。
2. 上面的setter方法沒有找到,那麼類方法accessInstanceVariablesDirectly返回YES(註:這是NSKeyValueCodingCatogery中實現的類方法,預設實現為返回YES)。
那麼按_<key>,_is<Key>,<key>,is<key>的順序搜索成員名。
3. 如果找到就去設置成員的值,如果沒有就去調用setValue:forUndefinedKey:。
b. valueForKey:的搜索方式:
1. 首先按get<Key>、<key>、is<Key>的順序查找getter方法,找到直接調用。如果是bool、int等內建值類型,會做NSNumber的轉換。
2. 上面的getter沒有找到,查找countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法。
如果countOf<Key>和另外兩個方法中的一個找到,那麼就會返回一個可以響應NSArray所有方法的代理集合(collection proxy object)。發送給這個代理集合(collection proxy object)的NSArray消息方法,就會以countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes這幾個方法組合的形式調用。還有一個可選的get<Key>:range:方法。
3. 還沒查到,那麼查找countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法。
如果這三個方法都找到,那麼就返回一個可以響應NSSet所有方法的代理集合(collection proxy object)。發送給這個代理集合(collection proxy object)的NSSet消息方法,就會以countOf<Key>、enumeratorOf<Key>、memberOf<Key>:組合的形式調用。
4. 還是沒查到,那麼如果類方法accessInstanceVariablesDirectly返回YES,那麼按_<key>,_is<Key>,<key>,is<key>的順序直接搜索成員名。
5. 再沒查到,調用valueForUndefinedKey:。
> 查找有序集合成員,比如NSMutableArray
mutableArrayValueForKey:搜索方式如下:
1. 搜索insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:或者insert<Key>:atIndexes、remove<Key>AtIndexes:格式的方法。
如果至少一個insert方法和至少一個remove方法找到,那麼同樣返回一個可以響應NSMutableArray所有方法的代理集合。那麼發送給這個代理集合的NSMutableArray消息方法,以insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:、insert<Key>:atIndexes、remove<Key>AtIndexes:組合的形式調用。還有兩個可選實現的介面:replaceObjectIn<Key>AtIndex:withObject:、replace<Key>AtIndexes:with<Key>:。
2. 否則,搜索set<Key>:格式的方法,如果找到,那麼發送給代理集合的NSMutableArray最終都會調用set<Key>:方法。
也就是說,mutableArrayValueForKey取出的代理集合修改後,用set<Key>:重新賦值回去。這樣做效率會差很多,所以推薦實現上面的方法。
3. 否則,那麼如果類方法accessInstanceVariablesDirectly返回YES,那麼按_<key>,<key>的順序直接搜索成員名。如果找到,那麼發送的NSMutableArray消息方法直接轉交給這個成員處理。
4. 再找不到,調用setValue:forUndefinedKey:。
> 搜索無序集合成員,如:NSSet。
mutableSetValueForKey:搜索方式如下:
1. 搜索add<Key>Object:、remove<Key>Object:或者add<Key>:、remove<Key>:格式的方法,如果至少一個insert方法和至少一個remove方法找到,那麼返回一個可以響應NSMutableSet所有方法的代理集合。那麼發送給這個代理集合的NSMutableSet消息方法,以add<Key>Object:、remove<Key>Object:、add<Key>:、remove<Key>:組合的形式調用。還有兩個可選實現的介面:intersect<Key>、set<Key>:。
2. 如果reciever是ManagedObejct,那麼就不會繼續搜索了。
3. 否則,搜索set<Key>:格式的方法,如果找到,那麼發送給代理集合的NSMutableSet最終都會調用set<Key>:方法。也就是說,mutableSetValueForKey取出的代理集合修改後,用set<Key>:重新賦值回去。這樣做效率會差很多,所以推薦實現上面的方法。
4. 否則,那麼如果類方法accessInstanceVariablesDirectly返回YES,那麼按_<key>,<key>的順序直接搜索成員名。如果找到,那麼發送的NSMutableSet消息方法直接轉交給這個成員處理。
5. 再找不到,調用setValue:forUndefinedKey:。
>>>> KVC還提供了下麵的功能
> 值的正確性核查
KVC提供屬性值確認的API,它可以用來檢查set的值是否正確、為不正確的值做一個替換值或者拒絕設置新值並返回錯誤原因。
實現核查方法為如下格式:validate<Key>:error:如:
[cpp] view plain copy
在CODE上查看代碼片派生到我的代碼片
-(BOOL)validateName:(id *)ioValue error:(NSError **)outError
{
// The name must not be nil, and must be at least two characters long.
if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2]) {
if (outError != NULL) {
NSString *errorString = NSLocalizedStringFromTable(
@"A Person's name must be at least two characters long", @"Person",
@"validation: too short name error");
NSDictionary *userInfoDict =
[NSDictionary dictionaryWithObject:errorString
forKey:NSLocalizedDescriptionKey];
*outError = [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN
code:PERSON_INVALID_NAME_CODE
userInfo:userInfoDict] autorelease];
}
return NO;
}
return YES;
}
調用核查方法:
validateValue:forKey:error:,預設實現會搜索 validate<Key>:error:格式的核查方法,找到則調用,未找到預設返回YES。
註意其中的記憶體管理問題。
>>>> 集合操作
集合操作通過對valueForKeyPath:傳遞參數來使用,一定要用在集合(如:array)上,否則產生運行時刻錯誤。其格式如下:
Left keypath部分:需要操作對象路徑。
Collectionoperator部分:通過@符號確定使用的集合操作。
Rightkey path部分:需要進行集合操作的屬性。
>>>> 數據操作
@avg:平均值
@count:總數
@max:最大
@min:最小
@sum:總數
確保操作的屬性為數字類型,否則運行時刻錯誤。
5.2 對象操作
針對數組的情況
@distinctUnionOfObjects:返回指定屬性去重後的值的數組
@unionOfObjects:返回指定屬性的值的數組,不去重
屬性的值不能為空,否則產生異常。
> 數組操作
針對數組的數組情況
@distinctUnionOfArrays:返回指定屬性去重後的值的數組
@unionOfArrays:返回指定屬性的值的數組,不去重
@distinctUnionOfSets:同上,只是返回值為NSSet
>>>> 效率問題
相比直接訪問KVC的效率會稍低一點,所以只有當你非常需要它提供的可擴展性時才使用它。
>>>>代碼實現以上知識:
1)直接賦值
使用KVC 可以對對象的某個屬性進行賦值。如下麵的代碼:
假定現在我們有一個Person 類,類中包含兩個屬性:一個是只讀的name 屬性,一個是Number類型的age屬性。
@interface Person : NSObject
@property(nonatomic,copy,readonly)NSString* name;
@property(nonatomic,assign)NSNumber *age;
@end
當我們定義了屬性的時候,系統就為我們自動的生成了setter 和getter 方法。我們可以通過setter 和getter方法,或讀取或寫入數值。當然我們也可以用KVC 的方式進行讀寫數據。
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
Person *person=[[Person alloc] init];
[person setValue:@"20" forKey:@"age"];
[person setValue:@"張依依" forKey:@"name"];
NSLog(@"person 的名字是%@",person.name);
NSLog(@"person 的年領是%@",[person valueForKey:@"age"]);
}
@end
總結: 只讀的屬性怎麼可以賦值? 還有age屬性明明是NSNumber類型的,怎麼可以把字元串賦給它?!沒錯,這就是我想說的,KVC 不但能夠賦值,而且還能破壞只讀的特性。當然這隻是我們需要註意的一個細節,更重要的是KVC 有自動裝箱
(自動類型轉換)的功能,我們不需要去轉換類型了。由於開發過程中數據領域是字元串的天下,所以這個自動裝箱的功能的確是極好的。
2)支持鍵值路徑
什麼叫支持鍵值路徑?說白了就是支持嵌套。假如現在有一個書籍類,類中包含了書籍的名稱name。
書籍可以被Person所擁有(就是可以作為person的屬性)
@interface Book : NSObject
@property(nonatomic,copy)NSString* name;
@end
那麼我們就可以這樣來用
Person *person=[[Person alloc] init];
Book *myBook=[[Book alloc] init];
person.book=myBook;
[person setValue:@"程式員攤煎餅指南" forKeyPath:@"book.name"];
NSLog(@"%@",[person valueForKeyPath:@"book.name"]);
這裡的key直接使用點局分開就好了,註意一下:這裡使用的時keyPath,
當然在 “ 1)屬性賦值” 中我們也可以使用keyPath,只不過再不必要的情況下使用keyPath會浪費性能而已。
1.路徑
除了通過鍵設值或取值外, 鍵/值編碼還支持指定路徑設值或取值,像文件系統一樣, 用“ . ”號隔開:
[book setValue:@"比爾" forKeyPath:@"author.name"];
NSNumber *price=[book valueForKeyPath:@"relativeBooks.price"]
2.數組的整體操作
如果向 NSArray 請求一個鍵值,它實際上會查詢數組中的每個對象來查找這個鍵值, 然後將查詢結果打包到另一
個數組中並返回給你:
// 獲取 Student 中所有 Book 的 name
NSArray *names = [student.books valueForKeyPath:@"name"]; 或者
NSArray *names = [student valueForKeyPath:@"books.name"];
//註意:不能在鍵路徑中為數組添加索引,比如 @"books[0].name"
3)支持操作符
KVC的簡單運算
//count
NSString *count = [book valueForKeyPath:@"relativeBooks.@count"];
NSLog(@"count : %@", count);
//sum
NSString *sum = [book valueForKeyPath:@"relativeBooks.@sum._price"];
NSLog(@"sum : %@", sum);
//avg
NSString *avg = [book valueForKeyPath:@"relativeBooks.@avg._price"];
NSLog(@"avg : %@", avg);
//min
NSString *min = [book valueForKeyPath:@"relativeBooks.@min._price"];
NSLog(@"min : %@", min);
//max
NSString *max = [book valueForKeyPath:@"relativeBooks.@max._price"];
NSLog(@"max : %@", max);
4)錯誤攔截
對於我們前端程式員來說,後端程式員有時也是一個troubleMaker。他總是給你傳遞一些很奇怪的東西。比如給你傳遞一個id 屬性,或者什麼都不給你傳。如果有這樣一個json文件 {“id”:"1"}。這是逼著我們把id作為數據模型的一個屬性的節奏啊!!老夫不願意啊!儘管作為屬性也不會報錯。屈服?還是抗爭?這是一個問題。但是好在前輩們已經給了我們答案。假如我們有一個Model類,類中的whoCare屬性就是本應命名為id 的屬性。我們還寫了一個字典轉模型的初始化方法。
@interface Model : NSObject
@property(nonatomic,strong)id whoCare;
-(instancetype)initWithDict:(NSDictionary *)dict;
@end
那麼我們可以在.m文件中重寫 -(void)setValue:(id)value forUndefinedKey:(NSString *)key 方法。這個方法會在字典轉模型時,系統找不到同名的屬性時調用。所以我們可以再這個方法中進行錯誤攔截,併進行賦值操作,這樣就不會報錯了。