ios開発のあなたは本当にKVCを知っていますか?

9316 ワード

以前はずっと自分がKVCを知っていると思っていたが、実は井戸の中の蛙で、本当にKVCの元の本を言うのは本当に簡単なことではない.そこで今日はこちらの文章を書いて、自分が知っていることと知っていることを戒めました.
一.KVCの基本概念
key-value codingは、getterメソッドとsetterメソッドを直接呼び出すのではなく、文字列識別子を使用してオブジェクト属性に間接的にアクセスするメカニズムです.通常、getterメソッドの代わりにvalueForKeyを使用し、setValue:forKeyをsetterメソッドの代わりに使用します.
次はKVCを使用するコードと使用しないコードの比較です.
Persion *persion =  [ [Persion alloc] init ];

//   KVC
persion.name = @"hufeng" ;

//  KVC   
[persion  setValue:@"hufeng" forKey:@"name"];

違いは見えますか?あなたが書くのは簡単すぎると言うかもしれませんが、私たちが実際に使っている間にこのような複雑なクラスはあり得ません.次は複雑なことを書きます.私たちには携帯電話クラスがあります.この携帯電話クラスには電池クラスがあります.私たちはこの電池クラスを取得するのは以前より複雑でしょう.
KVCはありません
Persion *persion =  [ [Persion alloc] init ];

Phone *phone = persion.phone;

Battery *battery = phone.battery;

KVCの使用
Battery *battery = [persion valueForKeyPath: @"phone.battery" ];

注意-valueForKeyPathの値は大文字と小文字を区別しています.Phoneを書けば.Batteryはダメだ
ここまで言うとNSArrayにKVCを呼び出してもらえませんか?答えは否定的です.arrayにはkeysがありませんから.でも、arrayの中のitemにKVCを使うことができます.
KVCで最もよく使われるのは、シーケンス化と逆シーケンスの会話オブジェクトです.よくjson文字列を逆シーケンス化する必要があります.次の例では、辞書をNSKeydArchiverでオブジェクトにシーケンス化します.
- (id)initWithDictionary:(NSDictionary *)dictionary {

    self = [self init];

    if (self){

        [self setValuesForKeysWithDictionary:dictionary];

    }

    return self;

}

ここには、setValueが定義されていない辞書値(forUndefinedKey)を与えると、NSUndefinedKeyException異常の処理が投げ出されるピットがあります.
もう一つ注意しなければならないのは、KVCにはタイプチェックがありません.結局、Object-Cは動的です.やはり次のコードを見てみましょう
[persion setValue:[NSNumber numberWithInteger:1] forKey:@"name"]; 
// compiles and runs

persion.name = [NSNumber numberWithInteger:1]; 
// won't compile: Incompatible pointer types assigning to 'NSString *' from 'NSNumber *'

setValue forKeyで得られたオブジェクトは汎用的なidであり,使用時にのみタイプが決定される.OCの弱さを聞かないかもしれませんが、もちろんOCがvalidateValueを提供してこの問題を解決する方法ではありません.
@property (nonatomic, strong) NSString name;

- (BOOL)validateName:(id*)ioValue error:(NSError**)error {
    // Validation logic goes here

}

Person *p = [Person new];

NSString *name = @"Jason Hu";

NSError *error = nil;


// This call below actually calls our validateName: error: method

if ([p validateValue:&name forKey:@"name" error:&error]) {

    [p setValue:name forKey:@"name"];
}

このようなマルチコードを書くと1つの属性しか検証されないと聞くかもしれませんが、もし私のクラスにn+の属性があれば、私はn+の検証方法を書きますか?
二.KVC検証
ここまで私たちはKVCに対してすでに初歩的な印象を持っていて、ここまで実はまだ氷山の一角にすぎません.次に、keyが大文字と小文字を区別しないことをサポートする場合、より高い要求を高めます.
次に、initializeの方法について説明します.
initializeは、クラスまたはそのサブクラスの最初のメソッドが呼び出される前に呼び出されます.だからクラスがプロジェクトやクラスファイルに参照されていないのに使用されていない場合はinitializeも呼び出されません.ここで私たちが次に何をするのか知っています.
+ (void)initialize {
    [super initialize];

    dispatch_once(&onceToken, ^{
        modelProperties = [NSMutableDictionary dictionary];
        propertyTypesArray = @[/* removed for brevity */];
    });
    NSMutableDictionary *translateNameDict = [NSMutableDictionary dictionary];
    [self hydrateModelProperties:[self class] translateDictionary:translateNameDict];
    [modelProperties setObject:translateNameDict forKey:[self calculateClassName]];
}

+ (void)hydrateModelProperties:(Class)class translateDictionary:(NSMutableDictionary *)translateDictionary {
    if (!class || class == [NSObject class]){
        return;
    }

    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(class, &outCount);
    for (i = 0; i < outCount; i++){
        objc_property_t p = properties[i];
        const char *name = property_getName(p);
        NSString *nsName = [[NSString alloc] initWithCString:name encoding:NSUTF8StringEncoding];
        NSString *lowerCaseName = [nsName lowercaseString];
        [translateDictionary setObject:nsName forKey:lowerCaseName];
        //     
        NSString *propertyType = [self getPropertyType:p];
        [self addValidatorForProperty:nsName type:propertyType];
    }
    free(properties);

    [self hydrateModelProperties:class_getSuperclass(class) translateDictionary:translateDictionary];
}