OC底層原理(12)-KVC

KVC初探

1.通常setter方法

代碼:數組

ViewController.mbash

person.name      = @"LG_Cooci";
 person.age       = 18;
 person->myName   = @"cooci";
 NSLog(@"%@ - %d - %@",person.name,person.age,person->myName);
複製代碼

打印結果:markdown

Janice - 18 - ty
複製代碼

2.Key-Value Coding (KVC) : 基本類型賦值

代碼:atom

ViewController.mspa

[person setValue:@"Janice" forKey:@"name"];
[person setValue:@19 forKey:@"age"];
[person setValue:@"ty" forKey:@"myName"];
NSLog(@"%@ - %@ - %@",[person valueForKey:@"name"],[person valueForKey:@"age"],[person valueForKey:@"myName"]);
複製代碼

打印結果:指針

Janice - 19 - ty
複製代碼

3.KVC:集合類型賦值

代碼:code

ViewController.morm

//不可變數組

person.array = @[@"1",@"2",@"3"];
NSArray *array = [person valueForKey:@"array"];
// 用 array 的值建立一個新的數組
array = @[@"100",@"2",@"3"];
[person setValue:array forKey:@"array"];
NSLog(@"%@",[person valueForKey:@"array"]);

//可變數組
NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
ma[0] = @"200";
NSLog(@"%@",[person valueForKey:@"array"]);
複製代碼

打印結果:對象

(
    100,
    2,
    3
)

(
    200,
    2,
    3
)
複製代碼

4.KVC - 訪問非對象屬性

代碼:three

LGPerson.h

#import <Foundation/Foundation.h>
#import "LGStudent.h"

NS_ASSUME_NONNULL_BEGIN

typedef struct {
    float x, y, z;
} ThreeFloats;

@interface LGPerson

@property (nonatomic) ThreeFloats threeFloats;

@end
複製代碼

ViewController.m

ThreeFloats floats = {1., 2., 3.};
    NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
    [person setValue:value forKey:@"threeFloats"];
    NSValue *reslut = [person valueForKey:@"threeFloats"];
    NSLog(@"%@",reslut);
    
    ThreeFloats th;
    [reslut getValue:&th] ;
    NSLog(@"%f - %f - %f",th.x,th.y,th.z);
複製代碼

打印結果:

{length = 12, bytes = 0x0000803f0000004000004040}
1.000000 - 2.000000 - 3.000000
複製代碼

5.KVC - 層層訪問

LGStudent *student = [[LGStudent alloc] init];
    student.subject    = @"語文";
    person.student     = student;
    [person setValue:@"數學" forKeyPath:@"student.subject"];
    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
複製代碼

打印結果:

數學
複製代碼

KVC原理設值過程

這個過程當中主要用成員變量來探索,爲何要使用成員變量呢,由於屬性在賦值的過程當中原本就會生成getter 和 setter 方法了,因此再用屬性探索KVC賦值,會很差分辯。

根據官方文檔可知 KVC setter 過程當中查找賦值的方法以下:

一、set<Key>: 或則 _set<key>

二、若是找不到第一步,查看 accessInstanceVariablesDirectly 返回是否爲YES,若是返回YES,就繼續查找一下方法:_<key>, _is<key>,<key>,或則 is<key>

依次來驗證一下:

一、先找setter

代碼:

LGPerson.h

#import <Foundation/Foundation.h>
#import "LGStudent.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject{
    @public
     NSString *_name;
     NSString *_isName;
     NSString *name;
     NSString *isName;
}
@end
複製代碼

LGPerson.m

#import "LGPerson.h"

@implementation LGPerson

#pragma mark - 關閉或開啓實例變量賦值
+ (BOOL)accessInstanceVariablesDirectly{
    return YES;
}

//MARK: - setKey. 的流程分析
- (void)setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

- (void)_setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

- (void)setIsName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

@end
複製代碼

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LGPerson *person = [[LGPerson alloc] init];
    
    // 1: KVC - 設置值的過程
    [person setValue:@"Janice" forKey:@"name"];

    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
    NSLog(@"%@-%@",person->name,person->isName);
    NSLog(@"%@",person->isName);
}
複製代碼

運行結果:

二、 當 accessInstanceVariablesDirectly 爲 YES

LGPerson.m 中代碼稍微作一點改動,以下:

//MARK: - setKey. 的流程分析
//註釋掉
//- (void)setName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}

- (void)_setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

- (void)setIsName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}
複製代碼

打印結果:

這裏走的就是 _setName 方法

而後代碼再作一點改動,以下

//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}

//- (void)_setName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}

- (void)setIsName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}
複製代碼

打印結果:

而後這裏就調用了 setIsName

二、 當 accessInstanceVariablesDirectly 爲 NO 時,作以下改動

#pragma mark - 關閉或開啓實例變量賦值
+ (BOOL)accessInstanceVariablesDirectly{
    return NO;
}

//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}

//- (void)_setName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}

//- (void)setIsName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}
複製代碼

打印結果:

崩潰了

以上,打印估計有注意到了我下面打印的值所有都爲 null,這是由於在重寫方法時,並無複製的哈,如_name = name;

下面將重寫方法全都註釋掉,以下,再來打印成員變量的值,再將 accessInstanceVariablesDirectly 而後YES。

代碼:

LGPerson.m

#pragma mark - 關閉或開啓實例變量賦值
+ (BOOL)accessInstanceVariablesDirectly{
    return YES;
}

//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}

//- (void)_setName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}

//- (void)setIsName:(NSString *)name{
//    NSLog(@"%s - %@",__func__,name);
//}
複製代碼

ViewController.m

#import "ViewController.h"
#import "LGPerson.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LGPerson *person = [[LGPerson alloc] init];
    
    // 1: KVC - 設置值的過程
    [person setValue:@"Janice" forKey:@"name"];

    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
    NSLog(@"%@-%@",person->name,person->isName);
    NSLog(@"%@",person->isName);
}
複製代碼

打印結果:

能夠看出只有第一個成員有值,而後將第一個成員變量註釋掉,並將第一個打印註釋掉

打印: LGPerson.h

#import <Foundation/Foundation.h>
#import "LGStudent.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject{
    @public
//     NSString *_name;
     NSString *_isName;
     NSString *name;
     NSString *isName;
}
@end
複製代碼

ViewController.m

#import "ViewController.h"
#import "LGPerson.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LGPerson *person = [[LGPerson alloc] init];
    
    // 1: KVC - 設置值的過程
    [person setValue:@"Janice" forKey:@"name"];

//    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
    NSLog(@"%@-%@",person->name,person->isName);
    NSLog(@"%@",person->isName);
}
複製代碼

打印結果:

再驗證最後一個

#import <Foundation/Foundation.h>
#import "LGStudent.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject{
    @public
//     NSString *_name;
//     NSString *_isName;
//     NSString *name;
     NSString *isName;
}
@end
複製代碼

ViewController.m

#import "ViewController.h"
#import "LGPerson.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LGPerson *person = [[LGPerson alloc] init];
    
    // 1: KVC - 設置值的過程
    [person setValue:@"Janice" forKey:@"name"];

//    NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
//    NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
//    NSLog(@"%@-%@",person->name,person->isName);
 //   NSLog(@"%@",person->isName);
}
複製代碼

打印結果:

以上打印驗證了只有,在編譯期過程當中,原本只有一個name,可是還會生成 其餘變量,如_name, 布爾值時,就有 isOpen,等,這就歸功於 runtime的做用。

KVC原理取值過程

官方文檔的查找流程以下,get<key>, <key>, is<key>,_<key>

代碼:

LGPerson.h

@interface LGPerson : NSObject{
    @public
     NSString *_name;
     NSString *_isName;
     NSString *name;
     NSString *isName;
}
@end
複製代碼

LGPerson.m

- (NSString *)getName{
    return NSStringFromSelector(_cmd);
}

- (NSString *)name{
    return NSStringFromSelector(_cmd);
}

- (NSString *)isName{
    return NSStringFromSelector(_cmd);
}

- (NSString *)_name{
    return NSStringFromSelector(_cmd);
}
複製代碼

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LGPerson *person = [[LGPerson alloc] init];
    // 2: KVC - 取值的過程
    
    //先賦值不一樣的值
     person->_name = @"_name";
     person->_isName = @"_isName";
     person->name = @"name";
     person->isName = @"isName";
    
    //取值
     NSLog(@"取值:%@",[person valueForKey:@"name"]);
}
複製代碼

打印結果:

打印了 getName 的方法名,說明在找變量以前先找的方法。

再驗證一個,就將getName註釋掉,走下一個方法

LGPerson.m

//MARK: - valueForKey 流程分析 - get<Key>, <key>, is<Key>, or _<key>,

//- (NSString *)getName{
//    return NSStringFromSelector(_cmd);
//}

- (NSString *)name{
    return NSStringFromSelector(_cmd);
}

- (NSString *)isName{
    return NSStringFromSelector(_cmd);
}

- (NSString *)_name{
    return NSStringFromSelector(_cmd);
}
複製代碼

打印結果

KVC設置與取值總結:

KVC 設置過程

一、判斷是否存在'set<key>' 或者 '_set<key>'(帶下劃線的屬性)'setls<key>'

二、若是沒有條件 1,(簡單訪問方式)

2.一、判斷 'accessInstanceVariablesDirectly' 是否存在,返回 'YES'

2.二、判斷 '_<key>','_is<key>','<key>','is<key>'等實例變量

2.三、直接給這些實例變量設置

三、'setValue:forUndefinedKey:'報錯!

KVC 取值過程:

一、若是找到'get<key>',<key>,'is<key>','_<key>'這幾個方法就跳到 '第五步'

二、若是沒有條件 1 ,開始是不是 NSArray 判斷

三、是不是 NSSet 判斷

四、非集合類型

4.一、'accessInstanceVariablesDirectly' 返回 YES

4.二、'_<key>,_is<key>,,is<key>'這些實例屬性

4.三、若是找到,直接獲取實例變量的值,而後繼續執行步驟 5

五、細節處理

5.一、若是檢索到的屬性值是對象指針,則只需返回結果

5.二、若是該值是 NSNumber 支持的標量類型,則將其存儲在 NSNumber 實例中並返回它

5.三、若是結果是NSNumber 不支持的標量類型,則轉換爲 NSValue 對象並返回

六、'valueForUndefineKey'報錯!!!

七、集合類型的還須要操做!

KVC使用小技巧

一、KVC 自動轉換類型

如,

[person setValue:@"20" forKey:@"age"];
複製代碼

age 爲 int 類型,可是給了一個 NSString 類型,因而,便會自動轉換類型爲 __NSCFNumber

二、能夠對int,NSValue 類型設置空值,對NSString設空值變不會走設置的任何方法

當找不到時能夠用一下方法能夠檢測到

- (void)setNilValueForKey:(NSString *)key;
複製代碼

三、找不到Key

當找不到時能夠用一下方法能夠檢測到

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
複製代碼

四、取值時 - 找不到 key

當找不到時能夠用一下方法能夠檢測到

- (nullable id)valueForUndefinedKey:(NSString *)key;
複製代碼

五、鍵值驗證

在此方法能夠作一些容錯處理

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
複製代碼
相關文章
相關標籤/搜索