標籤:
一:KVC和KVO的學習
#import "StatusItem.h"/* 1:總結:KVC賦值:1:setValuesForKeysWithDictionary實現原理:遍曆字典,得到所有的key,value值,再利用kvc, setVaue forkey來為value賦值 2: [item setValue:@"來自即刻筆記" forKey:@"source"],內部的底層實現, 1.首先去模型中尋找有沒有setSource,找到,直接調用賦值 [self setSource:@"來自即刻筆記"] 2.去模型中尋找有沒有source屬性,有,直接存取屬性賦值 source = value 3.去模型中尋找有沒有_source屬性,有,直接存取屬性賦值 _source = value 4.找不到,就會直接報錯 setValue:forUndefinedKey:報找不到的錯誤 當系統找不到就會調用這個方法,報錯- (void)setValue:(id)value forUndefinedKey:(NSString *)key可以重寫此方法更改key值 3:KVC四個方法:利用kvc可以訪問成員變數和屬性,setValue,value為屬性值,forKey,key為屬性名稱,forKeyPath為索引值路徑,例如在model中有如下屬性定義: @property (nonatomic, strong) BankAccount *account; keyPath: [zhangSan setValue:@150 forKeyPath:@"account.balance"]; - (id)valueForKey:(NSString *)key; - (id)valueForKeyPath:(NSString *)keyPath; - (void)setValue:(id)value forKey:(NSString *)key; - (void)setValue:(id)value forKeyPath:(NSString *)keyPath; 4:KVO:索引值觀察機制,用於對屬性的value值的改變做監聽:用法: @interface BankAccount : NSObject @property (nonatomic, assign) NSInteger balance; @end @interface Person : NSObject @property (nonatomic, strong) BankAccount *account; @end @implementation Person - (instancetype)init { ... // 註冊Observer: [self.account addObserver:self forKeyPath:@"balance" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; ... } - (void)dealloc { // 不要忘了removeObserver [self.account removeObserver:self forKeyPath:@"balance"]; } // 屬性變化的回調方法: - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"balance"]) { NSLog(@"Balance was %@.", change[NSKeyValueChangeOldKey]); NSLog(@"Balance is %@ now.", change[NSKeyValueChangeNewKey]); } } @end - (void)testKVO { Person *zhangSan = [[Person alloc] initWithName:@"ZhangSan" andBalance:20]; // 無論是用點文法還是KVC的方法都會觸發回調: zhangSan.account.balance = 150; [zhangSan setValue:@250 forKeyPath:@"account.balance"]; } */@interface StatusItem ()@property (nonatomic,copy)NSString *hello;@end@implementation StatusItem// 模型只儲存最重要的資料,導致模型的屬性和字典不能一一對應+ (instancetype)itemWithDict:(NSDictionary *)dict{ StatusItem *item = [[self alloc] init]; // KVC:把字典中所有值給模型的屬性賦值 [item setValuesForKeysWithDictionary:dict]; // 拿到每一個模型屬性,去字典中取出對應的值,給模型賦值 // 從字典中取值,不一定要全部取出來 // MJExtension:字典轉模型 runtime:可以把一個模型中所有屬性遍曆出來 // MJExtension:封裝了很多層// item.pic_urls = dict[@"pic_urls"];// item.created_at = dict[@"created_at"]; // KVC原理: // 1.遍曆字典中所有key,去模型中尋找有沒有對應的屬性 [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) { // 2.去模型中尋找有沒有對應屬性 KVC // key:source value:來自即刻筆記 // [item setValue:@"來自即刻筆記" forKey:@"source"] [item setValue:value forKey:key]; }]; return item;}// 重寫系統方法? 1.想給系統方法添加額外功能 2.不想要系統方法實現// 系統找不到就會調用這個方法,報錯- (void)setValue:(id)value forUndefinedKey:(NSString *)key{ }@end
二:利用runtime實現字典轉模型
#import "ViewController.h"#import "NSDictionary+Property.h"#import "StatusItem.h"#import "NSObject+Model.h"@interface ViewController ()@end/* 總結:1:項目中的檔案都儲存在mainBundle裡,讀取項目中的本地資訊:[[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil]; 得到本地路徑path,再看項目中的檔案根節點是字典還是數組,再從中讀取本地路徑filer:dictionaryWithContentsOfFile讀取 。若是擷取網路端的路徑:dictionaryWithContentsOfUrl */@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // 擷取檔案全路徑 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil]; // 檔案全路徑 NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath]; // 設計模型,建立屬性代碼 => dict// [dict[@"user"] createPropertyCode]; // 字典轉模型:KVC,MJExtension StatusItem *item = [StatusItem modelWithDict:dict]; }@end
#import <Foundation/Foundation.h>// 字典轉模型@interface NSObject (Model)+ (instancetype)modelWithDict:(NSDictionary *)dict;@end
#import "NSObject+Model.h"#import <objc/message.h>/* 總結:MJExtension字典轉模型的底層核心實現:runtime實現字典轉模型。 1:因為模型model都繼承NSObject,所以可以給系統類別寫分類進行拓展,子類繼承NSObject,也就是繼承了擴充的方法。所以模型轉字典的方法考慮給NSObject寫一個分類進行方法的拓展 2:在分類中若是對象方法,self指的是調用該方法的對象,類方法中self指的是調用該方法的類。方法的設計:類方法簡單粗暴,直接用類去調用,字典轉模型方法獲得所轉換的模型,不用建立對象,並且會將調用方法的類作為參數傳進方法中(對象方法也如此) 3:原理:runtime:根據模型中屬性,去字典中取出對應的value給模型屬性賦值 1:先擷取模型中所有成員變數 key 參數意義: // 擷取哪個類的成員變數 // count:成員變數個數 int *類型 unsigned int count = 0; // 擷取成員變數數組 Ivar *ivarList = class_copyIvarList(self, &count); 注意:1:int *count ,此count的類型為int *類型,當作為參數的時候,需要傳入一個int *類型的指標,指標裡存放的都是記憶體位址,也就是將地址作為參數傳遞,當方法執行完畢後,系統會拿到*count 進行賦值 int a = 2; int b = 3; int c = 4; int arr[] = {a,b,c}; int *p = arr; p[0]; NSLog(@"%d %d",p[0],p[1]); 2: 擷取類裡面屬性 class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) 擷取類裡面所有方法 class_copyMethodList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)// 本質:建立誰的對象 3:擷取模型中所有成員變數 key,Ivar:成員變數 以底線開頭,相當於一個數組 // 擷取哪個類的成員變數 // count:成員變數個數 unsigned int count = 0; // 擷取成員變數數組 Ivar *ivarList = class_copyIvarList(self, &count); 4:具體實現:擷取成員變數名字:ivar_getName(ivar),屬於c語言字串,所以要進行UTF8編碼 擷取成員變數類型:ivar_getTypeEncoding(ivar) // 擷取成員變數名字 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 擷取成員變數類型 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; 注意:1:字串替換:stringByReplacingOccurrencesOfString 2:字串截取:substringFromIndex:包括該index substringToIndex:不包括該index 3:前尾碼:hasPrefix首碼,hasSuffix:尾碼 4:是否包含某個字串:containString 5:字串轉換為Class:NSClassFromString: // 擷取類 Class modelClass = NSClassFromString(ivarType); value = [modelClass modelWithDict:value]; 6:一般某個方法接受傳遞進來的參數的時候,要判斷參數是否為空白,為nil或是為空白值,給某個值賦值的時候,也要判斷該值是否存在: // 給模型中屬性賦值 if (value) { [objc setValue:value forKey:key]; } */@implementation NSObject (Model)// Ivar:成員變數 以底線開頭// Property:屬性+ (instancetype)modelWithDict:(NSDictionary *)dict{ id objc = [[self alloc] init]; // runtime:根據模型中屬性,去字典中取出對應的value給模型屬性賦值 // 1.擷取模型中所有成員變數 key // 擷取哪個類的成員變數 // count:成員變數個數 unsigned int count = 0; // 擷取成員變數數組 Ivar *ivarList = class_copyIvarList(self, &count); // 遍曆所有成員變數 for (int i = 0; i < count; i++) { // 擷取成員變數 Ivar ivar = ivarList[i]; // 擷取成員變數名字 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 擷取成員變數類型 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // @\"User\" -> User ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""]; ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""]; // 擷取key NSString *key = [ivarName substringFromIndex:1]; // 去字典中尋找對應value // key:user value:NSDictionary id value = dict[key]; // 二級轉換:判斷下value是否是字典,如果是,字典轉換層對應的模型 // 並且是自訂對象才需要轉換 if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) { // 字典轉換成模型 userDict => User模型 // 轉換成哪個模型 // 擷取類 Class modelClass = NSClassFromString(ivarType); value = [modelClass modelWithDict:value]; } // 給模型中屬性賦值 if (value) { [objc setValue:value forKey:key]; } } return objc;}void test(int *count){ *count = 3;}@end
ios開發runtime學習五:KVC以及KVO,利用runtime實現字典轉模型