KVC方法詳解與實現原理

KVC提供了一種在運行時而非編譯時動態訪問對象屬性與成員變量的方式,該方法不需要調用get和set方法和變量實例就可以訪問對象,KVC默認的實現方法有NSOject提供,這種方法及支持對象也支持簡單數據類型。
第一、在OC中訪問變量的幾種方式:
1、設置爲public型,通過->直接訪問:
代碼爲:
@interface Book : NSObject
{
    @public
    NSString *name;
}
        Book *book=[[Bookalloc]init];
        book->name=@"hello";
        NSLog(@"val is %@",book->name);
2.利用屬性訪問
3.利用KVC,即使該屬性是private也可以訪問
@interface Book : NSObject
{
    @private
    NSString *name;
}
 Book *book=[[Book alloc]init];
 [book setValue:@"hello"forKey:@"name"];
 NSLog(@"val is %@",[bookvalueForKey:@"name"]);
第二、KVC路徑訪問
除了通過鍵設置值外,鍵/值編碼還支持指定路徑,像文件系統一樣,用“點”號隔開
[book valueForKeyPath:@"authorObj.name"]
sample:
      Book *book=[[Book alloc] init];
        [book setValue:@"hello" forKey:@"name"];
        NSLog(@"val is %@",[book valueForKey:@"name"]);
        
        author *authorObj=[[author alloc] init];
        [authorObj setValue:@"niudun" forKey:@"name"];
        [book setValue:authorObj forKey:@"authorObj"];
        
        NSLog(@"the author of book is%@",[book valueForKeyPath:@"authorObj.name"]);
第三、一對多
@interface Book : NSObject
{
    @private
    NSString *name;
    author *authorObj;
    NSArray *relativeBooks;
    
}   //kvc
        Book *book=[[Book alloc] init];
        [book setValue:@"hello" forKey:@"name"];
        NSLog(@"val is %@",[book valueForKey:@"name"]);
        
        //keypath
        author *authorObj=[[author alloc] init];
        [authorObj setValue:@"niudun" forKey:@"name"];
        [book setValue:authorObj forKey:@"authorObj"];
        NSLog(@"the author of book is%@",[book valueForKeyPath:@"authorObj.name"]);
        
        //一對多
        NSMutableArray *array=[NSMutableArray arrayWithCapacity:3];
        for (int i=0; i<3; i++) {
            Book *bookObj=[[Book alloc] init];
            NSString *name=[NSString stringWithFormat:@"job_%d",i];
            [bookObj setValue:name forKey:@"name"];
            [array addObject:bookObj];
            [bookObj release];
        }
        [book setValue:array forKey:@"relativeBooks"];
        NSArray *arr=[book valueForKeyPath:@"relativeBooks.name"];
        NSLog(@"arr is %@",arr);


第四、kvc支持簡單的預算如max、min、sum,其中運算的字段必須是基本數據類型或NSNumber類型




第五、KVC對數值和結構體類型的支持
一套機制如果不支持數值和結構體型的數據,那麼它的實用性就會大大折扣。幸運的是KVC中蘋果對這方面的支持做的很好。KVC可以自動的將數值或結構體型的數據打包或解包成NSNumber或NSValue對象,以達到適配的目的。
舉個例子,Person類有個個NSInteger類型的num屬性
①修改值
我們通過KVC技術使用如下方式設置age屬性的值: 
[_person setValue:[NSNumber numberWithInteger:5] forKey:@"num"];
我們賦給num的是一個NSNumber對象,KVC會自動的將NSNumber對象轉換成NSInteger對象,然後再調用相應的訪問器方法設置age的值。
②獲取值
同樣,以如下方式獲取age屬性值:
     [person valueForKey:@"num"];
這時,會以NSNumber的形式返回num的值。
第六、KVC實現原理的方法定義
在iOS中,通過KVC可以直接用字符串的名字(key)來訪問類屬性的機制。而不是通過調用Setter、Getter方法訪問。
KVC是KVO、Core Data、CocoaBindings的技術基礎,他們都是利用了OC的動態性。
NSKeyValueCodingprotocol
第七、setValue:forKey是如何訪問屬性值的
KVC方法的實現get、set方法及實例變量的訪問,KVC setValue方法和getValue方法按順序使用如下技術:
1. 檢查是否存在set<Key>:方法
如果成員用@property,@synthsize處理,因爲@synthsize告訴編譯器自動生成set<Key>:格式的set方法,所以這種情況下會直接搜索到。
2. 檢查名爲-_<key>、-_is<key>(只針對布爾值有效)、-_set<key>:方法;
那麼按_<key>,_is<Key>,<key>,is<key>的順序搜索成員名。
3. 直接訪問實例變量。實例變量可以是名爲:<key>或_<key>;
4. 調用方法setValue:forUndefinedKey
第八、valueForKey是如何訪問屬性值的
跟上面setValue執行順序類似,把set方法改成get方法,當所有都失效時調用方法valueForUndefinedKey。
第九、方法重寫
如果我們的類既沒有key或_key對應的set/get方法,也沒有key或_key對應的實例變量,但要使用setValue和getValue方法,必須重寫函數setValue:forUndefinedKey和valueForUndefinedKey。
在這兒我們可以利用OC的關聯機制
1.什麼是關聯機制
OC提供了兩種共享機制,一種是category,一種是associative,category只能擴展方法,associative可以擴展屬性。
關聯機制是基於關鍵字的,我們可以爲任何對象增加任意多的關聯,每個都使用不同的關鍵字即可。關聯是可以保證被關聯的對象在關聯對象的整個生命週期都是可用的(在垃圾自動回收環境下也不會導致資源不可回收)。使用關聯機制必須引入<objc/runtime.h>頭文件
2.objc_setAssociatedObject創建關聯
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
 objc_setAssociatedObject來把一個對象與另外一個對象進行關聯。該函數需要四個參數:源對象,關鍵字,關聯的對象和一個關聯策略
  參數說明:
  object:表示源對象
  key:關鍵詞
  value:表示關聯的對象
  policy:關聯策略,關聯策略表明了相關的對象是通過賦值,保留引用還是複製的方式進行關聯的;還有這種關聯是原子的還是非原子的。這裏的關聯策略和聲明屬性時的很類似。
  policy有四個值:
OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */




     3. objc_getAssociatedObject獲取關聯對象
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
4.objc_removeAssociatedObjects刪除該對象的所有關聯,通常情況下不建議使用這個函數,因爲他會斷開所有關聯。只有在需要把對象恢復到“原始狀態”的時候纔會使用這個函數。
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
   NSArray *arr=[NSArray arrayWithObjects:@"one",nil];
    objc_setAssociatedObject(arr, @"hello", @"world", OBJC_ASSOCIATION_RETAIN);
    
    NSString *ret=(NSString *)objc_getAssociatedObject(arr, @"hello");
    NSLog(@"the result is %@",ret);




5. objc_setAssociatedObject with nil 來斷開指定key得關聯
6. 重寫方法的實現
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if (value == nil) {
        value = [NSNull null];
    }
    objc_setAssociatedObject(self, (__bridge const void *)(key), value, OBJC_ASSOCIATION_RETAIN);
}


- (id)valueForUndefinedKey:(NSString *)key {
    id target = objc_getAssociatedObject(self, (__bridge const void *)(key));
    if ([target isKindOfClass:[NSNull class]])
        return nil;
    return target;
}











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章