iOS進階_KVC(基本使用&KVC賦值取值過程分析&KVC自定義&異常處理)

KVC(Key-value coding)

鍵值編碼

基本使用

  1. 能夠對對象的私有成員進行取值賦值
  2. 對數值和結構體型的屬性進行的打包解包處理

實例:
WTPerson.h

#import <Foundation/Foundation.h>

@interface WTPerson : NSObject{
//    @public  //@protect默認
    NSString * _name;
}

/** name  **/
//@property(nonatomic,strong)NSString * name;

@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *text;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];
    //訪問成員變量
    //p.name = @"wt";
    //NSLog(@"%@",p.name);
    
    //訪問私有變量(必須要要設置爲public纔可訪問)
    //p->_name = @"wt";
    //NSLog(@"%@",p->_name);
    
    //KVC(即使不用public修飾,也可以訪問私有變量)
    [p setValue:@"wt" forKey:@"name"];
    NSLog(@"%@",[p valueForKey:@"name"]);
    
    [self.text setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
}

gitHub參考Demo:KVC基本使用

KVC賦值取值過程分析和自定義及異常處理

賦值過程

  • 1、先找相關方法set<Key>; _set<Key>; setIs<Key>;
  • 2、若是沒有相關方法+(BOOL)accessInstanceVariablesDirectly判斷是否可以直接訪問成員變量
  • 3、如果判斷NO,直接執行KVC的setValue:forUndefinedKey:(系統拋出一個異常,未定義key)
  • 4、如果是YES,繼續找相關變量_<key> _is<Key> <key> is<Key>
  • 5、方法或成員都不存在,setValue:forUndefinedKey:方法默認是拋出異常

實例驗證

WTPerson.h

#import <Foundation/Foundation.h>
@interface WTPerson : NSObject{
    @public  //@protect默認
    NSString * _name;
    NSString * _isName;
    NSString * name;
    NSString * isName;
}
@end

WTPerson.m

#import "WTPerson.h"
@implementation WTPerson

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

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

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

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new]; 
    //驗證KVC賦值過程
    [p setValue:@"wt" forKey:@"name"];
    
    NSLog(@"name = %@",p->name);
    NSLog(@"_name = %@",p->_name);
    NSLog(@"isname = %@",p->isName);
    NSLog(@"_isname = %@",p->_isName);
}

@end
  • 運行程序,我們把WTPerson.m中的-(void)setName:(NSString *)name-(void)_setName:(NSString *)name-(void)setIsName:(NSString *)name三個方法依次註釋,我們發現三個方法都會被依次執行。

  • 然後我們把WTPerson.h中的NSString * _name;NSString * _isName;NSString * name;NSString * isName;依次註釋,我們會發現4個屬性依次被賦值。

WTPerson.m中我們讓accessInstanceVariablesDirectly返回NO,則程序直接崩潰。

+ (BOOL)accessInstanceVariablesDirectly{
    return NO;
}

取值過程

  • 1、先找相關方法get<Key>,key
  • 2、若沒有相關方法,+(BOOL)accessInstanceVariabkesDirectly判斷是否可以直接訪問成員變量
  • 3、如果是NO,直接執行KVC的valueForUndefinedKey:(系統拋出一個異常,未定義key)
  • 4、如果是YES,繼續找相關變量_<key>、_is<Key>、<key>、is<Key>
  • 5、方法或成員都不存在,valueForUndefineKey:方法,默認是拋出異常

實例驗證

WTPerson.m

#import "WTPerson.h"

@implementation WTPerson

//- (NSString*) getName{
//    NSLog(@"%s",__func__);
//    return @"getName";
//}

- (NSString*) name {
    return @"name";
}

//+ (BOOL)accessInstanceVariablesDirectly{
//    return NO;
//}
@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *text;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];

    //驗證KVC取值過程
    NSLog(@"name = %@",[p valueForKey:@"name"]);
}

@end

取值方式與賦值方式大致相同。

GitHub參考Demo:KVC賦值取值過程分析

KVC自定義

自定義KVC代碼實現

創建分類NSObject+KVC

NSObject+KVC.h

#import <Foundation/Foundation.h>

@interface NSObject (KVC)

- (void)wt_setValue:(nullable id)value forKey:(NSString *)key;

- (id)wt_valueForKey:(NSString *)key;

@end

NSObject+KVC.m


#import "NSObject+KVC.h"
#import <objc/runtime.h>

@implementation NSObject (KVC)

- (id)wt_valueForKey:(NSString *)key{
    //判斷是否合法
    if (key == nil && key.length ==0) {
        return nil;
    }
    
    //Key
    NSString * Key = key.capitalizedString;
    
    //先找相關方法 get<Key>,key
    NSString * getKey = [NSString stringWithFormat:@"get%@:",Key];
    
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }
   
    if ([self respondsToSelector:NSSelectorFromString(key)]) {
        return [self performSelector:NSSelectorFromString(key)];
    }
    
    if (![self.class accessInstanceVariablesDirectly]) {
        NSException * exception = [NSException exceptionWithName:@"NSUnknownKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
        @throw exception;
    }
    
    //再找相關變量
    //獲取所有的成員變量
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([self class], &count);
    NSMutableArray * arr = [[NSMutableArray alloc]init];
    for (int i = 0; i<count; i++) {
        Ivar var = ivars[i];
        const char * varName = ivar_getName(var);
        NSString *name = [NSString stringWithUTF8String:varName];
        [arr addObject:name];
    }
    
    //_<key> _is<Key> <key> is<Key>
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {

            return object_getIvar(self, ivars[i]);
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",Key]]) {
      
            return object_getIvar(self, ivars[i]);
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"%@",key]]) {
   
            return object_getIvar(self, ivars[i]);
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",Key]]) {
            return object_getIvar(self, ivars[i]);
        }
    }
    free(ivars);
    return nil;
}

- (void)wt_setValue:(nullable id)value forKey:(NSString *)key{
 
    //判斷是否合法
    if (key == nil && key.length ==0) {
        return;
    }
    
    //Key
    NSString * Key = key.capitalizedString;
    
    //先找相關方法 set<Key>; _set<Key>; setIs<Key>;
    NSString * setKey = [NSString stringWithFormat:@"set%@:",Key];
    
    if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
        [self performSelector:NSSelectorFromString(setKey) withObject:value];
        return;
    }
    
    NSString * _setKey = [NSString stringWithFormat:@"_set%@:",Key];
 
    if ([self respondsToSelector:NSSelectorFromString(_setKey)]) {
        [self performSelector:NSSelectorFromString(_setKey) withObject:value];
        return;
    }
    
    NSString * setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
        [self performSelector:NSSelectorFromString(setIsKey) withObject:value];
        return;
    }
    
    if (![self.class accessInstanceVariablesDirectly]) {
        NSException * exception = [NSException exceptionWithName:@"NSUnknownKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
        @throw exception;
    }
    
    //再找相關變量
    //獲取所有的成員變量
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([self class], &count);
    NSMutableArray * arr = [[NSMutableArray alloc]init];
    for (int i = 0; i<count; i++) {
        Ivar var = ivars[i];
        const char * varName = ivar_getName(var);
        NSString *name = [NSString stringWithUTF8String:varName];
        [arr addObject:name];
    }
    
    //_<key> _is<Key> <key> is<Key>
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",Key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"%@",key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString *keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",Key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }
    
    [self setValue:value forUndefinedKey:Key];
    free(ivars);
}
@end

驗證

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"
#import "NSObject+KVC.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    WTPerson * p =[WTPerson new];
    [p wt_setValue:@"wt" forKey:@"name"];
    
    NSLog(@"name-KVC = %@",[p wt_valueForKey:@"name"]);
    NSLog(@"_name = %@",p->_name);
    NSLog(@"_isName = %@",p->_isName);
    NSLog(@"name = %@",p->name);
    NSLog(@"isName = %@",p->isName);
}
@end

GitHub示例Demo:自定義KVC

在項目中
commond+shift+o 搜索setValue:forKey發現在Foundation框架下的NSKeyValueCoding文件下

在這裏插入圖片描述

我們查看這個文件中的方法,發現這個文件中是一些分類的集合

在這裏插入圖片描述

KVC異常處理及正確性驗證

KVC異常處理

  • 1、賦值爲空 setNilValueForKey
  • 2、Key值不存在 setValue:forUndefinedKey

正確性驗證

validateValue
該方法的工作原理:

  • 1、先找一下你的類中是否實現了方法 -(BOOL)validate<Key>:error;
  • 2、如果實現了就會根據實現方法裏面的自定義邏輯返回NO或者YES;如果沒有實現這個方法,則系統默認返回YES

示例代碼

WTPerson…h

#import <Foundation/Foundation.h>

@interface WTPerson : NSObject

/** name  **/
@property(nonatomic,strong)NSString * name;

/** age  **/
@property(nonatomic,assign)int age;

@end

WTPerson.m

#import "WTPerson.h"

@implementation WTPerson

//對非對象類型,值不能爲空
- (void) setNilValueForKey:(NSString *)key{
    NSLog(@"%@ 值不能爲空",key);
}

//賦值的key不存在
- (void) setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"key = %@值不存在",key);
}

//取值的key不存在
- (id) valueForUndefinedKey:(NSString *)key{
    NSLog(@"key = %@值不存在",key);
    return nil;
}

//正確性驗證
- (BOOL) validateAge:(inout id  _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError{
    NSNumber* value = (NSNumber*)*ioValue;
    NSLog(@"%@",value);
    if ([value integerValue] >= 0 && [value integerValue] <= 200) {
        return YES;
    }
    return NO;
}

@end

ViewController.m

#import "ViewController.h"
#import "WTPerson.h"
#import "WTContainer.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    WTPerson * p = [WTPerson new];
    
    //異常處理
    [p setValue:@18 forKey:@"name"];
    [p setValue:nil forKey:@"name"];
    NSLog(@"name = %@",p.name);

    [p setValue:nil forKey:@"age"];
    NSLog(@"age = %d",p.age);

    [p setValue:@"hello" forKey:@"name1"];

    NSLog(@"name = %@",[p valueForKey:@"name1"]);

    //萬能容器
    WTContainer * container = [WTContainer new];
    
    [container setValue:@"wt" forKey:@"name"];
    [container setValue:@18 forKey:@"age"];

    NSLog(@"name = %@,age = %@",[container valueForKey:@"name"],[container valueForKey:@"age"]);
    
    
    //正確性驗證
    NSNumber * value = @200;
    NSNumber * value1 = @199;
    
    if ([p validateValue:&value1 forKey:@"age" error:NULL]) {
        
        [p setValue:value1 forKey:@"age"];
    }
    
    NSLog(@"%@",[p valueForKey:@"age"]);
}

@end

GitHub示例Demo:KVC異常處理及正確性驗證

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