RunTime 이해와 실전(二)

13730 단어
지난 편에서 우리는 OC의 런타임에 대해 간단하게 소개했고 그 원리와 API의 사용 런타임 이해와 실전(一)을 이해했다. 이 글은 런타임으로 json 회전 모델의 예를 썼다.뒤에 온전한 코드가 다운받을 거예요.
JSON에서 모델로의 매핑 원리
평소에 우리는 이와 같은 라이브러리를 어떻게 사용하는지 생각해 보자. JSON이 있을 때 우리는 모든 JSON의 필드(예를 들어name, 페이지)를 대응하는 클래스의 속성으로 쓴다.그리고 라이브러리는 자동으로 JSON에 대응하는 필드의 값을 대응하는 속성에 부여합니다.속성은 @property로 정의합니다. 컴파일러가 대응하는 get set , . get set 방법을 만들어서 값을 부여할 수 있음을 의미합니다.Objective-C에 유명한 함수 objcmsgSend(...) 우리의 모든 유사한 [obj method] 방법은 (메시지 보내기)objc 로 변환됩니다msgSend(...) 라는 식으로 호출했다.
그래서 한 라이브러리에서 이 모델의set 방법을 사용하려면objcmsgSend(...) 훨씬 쉬울 거예요. 그래서 JSON이 모델에 비치는 원리는 사실 이 함수를 호출하는 거예요.그래서 전체 프로세스는 당신이 나에게 모델 클래스를 주면 나는runtime가 제공하는 각종 함수로 당신의 모든 속성과 대응하는 get`set을 얻을 것입니다. 상응하는 유형을 판단한 후에objc 를 호출합니다.msgSend(...).
json과 사전의 변환
//      
+ (NSString*)DataTOjsonString:(id)object
{
    NSString *jsonString = nil;
    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
                                                       options:NSJSONWritingPrettyPrinted 
// Pass 0 if you don't care about the readability of the generated string
                                                         error:&error];
    if (! jsonData) {
        NSLog(@"Got an error: %@", error);
    } else {
        jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    }
    return jsonString;
}

//      
+ (NSDictionary *)ww_dictionaryWithJSON:(id)json{
    if (!json || json == (id)kCFNull) {
        return nil;
    }
    NSDictionary *dic = nil;
    NSData *data = nil;
    if ([json isKindOfClass:[NSDictionary class]]) {
        dic = json;
    }else if ([json isKindOfClass:[NSString class]]) {
        data = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
    }else if ([json isKindOfClass:[NSData class]]) {
        data = json;
    }
    if (data) {
        dic = [NSJSONSerialization JSONObjectWithData:data 
                options:NSJSONReadingMutableLeaves error:nil];
        if (![dic isKindOfClass:[NSDictionary class]]) {
            dic = nil;
        }
    }
    return dic;
}

간단한 사전 회전 모형

//     
+ (instancetype)modelWithDict:(NSDictionary *)dict{
    id model = [[self alloc] init];
    unsigned int count = 0;
    //      
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0 ; i < count; i++) {
        Ivar ivar = ivars[i];
        
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //     
        ivarName = [ivarName substringFromIndex:1];
        id value = dict[ivarName];
        //nil      
        [model setValue:value forKeyPath:ivarName];
    }
    
    return model;
}
use
//     
- (void)simpleness_jsonToModle{
    
    Department *dp = [[Department alloc] init];
    NSDictionary *dict = @{
                           @"employeeId" : @2,
                           @"name" : @"Emp2",
                           @"department" : dp
                           };
    
    
    Employee *el = [Employee modelWithDict:dict];
    NSLog(@"%@",el.name);
}

이렇게 하면 사전을 간단하게 모델로 바꿀 수 있지만 몇 가지 단점이 있다. 확장성이 낮고 메모리 소모가 크며 속성 대상과 연결할 수 없고 이상하게 처리되지 않는다는 것이다.그러나 이 간단한 전환을 통해 JSON에서 모델로 비치는 원리를 알 수 있다. 바로 글의 첫머리에서 말한 바와 같다.
최적화
마지막으로 KVC를 통해 속성에 값을 부여해야 하기 때문에 속성 목록을 가져옵니다.
//      
+ (NSArray *)objectProperties {
    static const void * const kPropertyKey = &kPropertyKey;
    //runtime           
    NSMutableArray *properties = objc_getAssociatedObject(self, kPropertyKey);

    if (!properties) {
        properties = [NSMutableArray array];
        unsigned int count;
        //      
        objc_property_t *propertyList = class_copyPropertyList([self class], &count);
        for (unsigned int i=0; i%@", [NSString stringWithUTF8String:propertyName]);
            
            [properties addObject:[NSString stringWithUTF8String:propertyName]];
        }
        objc_setAssociatedObject(self, kPropertyKey, properties, OBJC_ASSOCIATION_COPY);
    }
    return properties;
}

하나의 매거진으로 속성 유형을 사용자 정의하여 어떤 속성 대상이 관련되어야 하는지를 판단하다
typedef NS_ENUM(NSUInteger, WWPropertyType) {
    WWPropertyTypeUndefined,
    WWPropertyTypeInteger,
    WWPropertyTypeFloat,
    WWPropertyTypeString,
    WWPropertyTypeBoolean,
    WWPropertyTypeDate,
    WWPropertyTypeData,
    WWPropertyTypeArray,
    WWPropertyTypeRelationship
};

//            
+ (NSDictionary *)propertyTypes {
    static const void * const kPropertyTypesKey = &kPropertyTypesKey;
    //runtime    
    NSDictionary *result = objc_getAssociatedObject(self, kPropertyTypesKey);
    if (!result) {
        NSArray *properties = [self objectProperties];
        result = [[NSMutableDictionary alloc] initWithCapacity:properties.count];
        for (NSString *property in properties) {
            WWPropertyType type = [self propertyTypeOfClass:self propertyName:property];
            [(NSMutableDictionary *)result setObject:@(type) forKey:property];
        }
        objc_setAssociatedObject(self, kPropertyTypesKey, result, OBJC_ASSOCIATION_COPY);
    }
    return result;
}

사용자 정의 속성 유형

//       
+ (WWPropertyType)propertyTypeOfClass:(Class)classType propertyName:(NSString *)propertyName {
    
    //       
    NSString *type = [WWReflection ww_propertyTypeOfClass:classType propertyName:propertyName];
    if ([@"int" isEqualToString:type] ||
        [@"unsigned" isEqualToString:type] ||
        [@"short" isEqualToString:type] ||
        [@"long" isEqualToString:type] ||
        [@"unsigned long" isEqualToString:type] ||
        [@"long long" isEqualToString:type] ||
        [@"unsigned long long" isEqualToString:type] ||
        [@"char" isEqualToString:type]) {
        return WWPropertyTypeInteger;
    } else if ([@"float" isEqualToString:type] ||
               [@"double" isEqualToString:type]) {
        return WWPropertyTypeFloat;
    } else if ([@"NSString" isEqualToString:type] ||
               [@"NSMutableString" isEqualToString:type]) {
        return WWPropertyTypeString;
    } else if ([@"bool" isEqualToString:type]) {
        return WWPropertyTypeBoolean;
    } else if ([@"NSDate" isEqualToString:type]) {
        return WWPropertyTypeDate;
    } else if ([@"NSData" isEqualToString:type] ||
               [@"NSMutableData" isEqualToString:type]) {
        return WWPropertyTypeData;
    } else if ([@"NSArray" isEqualToString:type] ||
               [@"NSMutableArray" isEqualToString:type]) {
        return WWPropertyTypeArray;
    }
    else {
        Class propertyClass = NSClassFromString(type);
        //                    
        if ([propertyClass isSubclassOfClass:[NSObject class]]) {
            return WWPropertyTypeRelationship;
        }
        return WWPropertyTypeUndefined;
    }
}

속성의 데이터 유형을 가져오는 방법도 필요합니다.
+ (NSString *)ww_propertyTypeOfClass:(Class)classType propertyName:(NSString *)propertyName {
    static NSMutableDictionary *cache = nil;
    static dispatch_once_t pred;
    dispatch_once(&pred, ^{
        cache = [[NSMutableDictionary alloc] init];
    });
    @synchronized(cache) {
        NSMutableDictionary *propertyTypeMap = [cache objectForKey:NSStringFromClass(classType)];
        if (!propertyTypeMap) {
            propertyTypeMap = [[NSMutableDictionary alloc] init];
            [cache setObject:propertyTypeMap forKey:NSStringFromClass(classType)];
        }
        NSString *type = [propertyTypeMap objectForKey:propertyName];
        if (!type) {
            objc_property_t property = class_getProperty(classType, [propertyName UTF8String]);
            NSString *attributes = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
            
            if ([attributes hasPrefix:@"T@"]) {
                NSArray *substrings = [attributes componentsSeparatedByString:@"\""];
                if ([substrings count] >= 2) {
                    type = [substrings objectAtIndex:1];
                } else {
                    type = @"id";
                }
            } else if ([attributes hasPrefix:@"T{"]) {
                type = @"struct";
            } else {
                if ([attributes hasPrefix:@"Ti"]) {
                    type = @"int";
                } else if ([attributes hasPrefix:@"TI"]) {
                    type = @"unsigned";
                } else if ([attributes hasPrefix:@"Ts"]) {
                    type = @"short";
                } else if ([attributes hasPrefix:@"Tl"]) {
                    type = @"long";
                } else if ([attributes hasPrefix:@"TL"]) {
                    type = @"unsigned long";
                } else if ([attributes hasPrefix:@"Tq"]) {
                    type = @"long long";
                } else if ([attributes hasPrefix:@"TQ"]) {
                    type = @"unsigned long long";
                } else if ([attributes hasPrefix:@"TB"]) {
                    type = @"bool";
                } else if ([attributes hasPrefix:@"Tf"]) {
                    type = @"float";
                } else if ([attributes hasPrefix:@"Td"]) {
                    type = @"double";
                } else if ([attributes hasPrefix:@"Tc"]) {
                    type = @"char";
                } else if ([attributes hasPrefix:@"T^i"]) {
                    type = @"int *";
                } else if ([attributes hasPrefix:@"T^I"]) {
                    type = @"unsigned *";
                } else if ([attributes hasPrefix:@"T^s"]) {
                    type = @"short *";
                } else if ([attributes hasPrefix:@"T^l"]) {
                    type = @"long *";
                } else if ([attributes hasPrefix:@"T^q"]) {
                    type = @"long long *";
                } else if ([attributes hasPrefix:@"T^Q"]) {
                    type = @"unsigned long long *";
                } else if ([attributes hasPrefix:@"T^B"]) {
                    type = @"bool *";
                } else if ([attributes hasPrefix:@"T^f"]) {
                    type = @"float *";
                } else if ([attributes hasPrefix:@"T^d"]) {
                    type = @"double *";
                } else if ([attributes hasPrefix:@"T*"]) {
                    type = @"char *";
                } else {
                    NSAssert(0, @"Unkonwn type");
                }
            }
            [propertyTypeMap setObject:type forKey:propertyName];
        }
        
        return type;
    }
}

마지막 귀속 호출

+ (instancetype)ww_objectWithDictionary:(NSDictionary *)dictionary {
    id object = [[self alloc] init];
    
    NSArray *objectProperties = [self objectProperties];
    NSDictionary *propertyTypes = [self propertyTypes];
    for (NSString *key in dictionary.allKeys) {
        if ([objectProperties containsObject:key]) {
            id value = [dictionary objectForKey:key];
            WWPropertyType propertyType = [[propertyTypes objectForKey:key] unsignedIntegerValue];
            //              
            if (propertyType == WWPropertyTypeRelationship) {
                NSAssert([value isKindOfClass:[NSDictionary class]], @"");
                Class propertyClass = [[self propertyClasses] objectForKey:key];
                value = [propertyClass ww_objectWithDictionary:value];
            }
            [object setValue:value forKey:key];
        }
    }
    
    return object;
}

우리는 또 방법을 교환하여 호출할 수 있다
- (void)viewDidLoad {
    [super viewDidLoad];
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
        SEL simpleness_Sel = @selector(simpleness_jsonToModle);
        SEL complex_Sel = @selector(complex_dicToObject);
        //     Method
        Method simpleMethod = class_getInstanceMethod([self class], simpleness_Sel);
        Method complexMethod = class_getInstanceMethod([self class], complex_Sel);
        
        //        ,         ,             
        BOOL isAdd = class_addMethod([self class], simpleness_Sel, method_getImplementation(complexMethod), method_getTypeEncoding(complexMethod));
        if (isAdd) {
            //    ,              
            //                     
            class_replaceMethod([self class], simpleness_Sel, method_getImplementation(simpleMethod), method_getTypeEncoding(simpleMethod));
        }else{
            //  ,         
            method_exchangeImplementations(simpleMethod, complexMethod);
        }
        
    });
    
    
    //             complex_dicToObject    
    [self simpleness_jsonToModle];
    
//    [self complex_dicToObject];

}

이렇게 소개하는 것은 비교적 난잡할 수 있으니 다운로드할 수 있고 코드를 대조해 보면 그래도 비교적 간단하다.전체 소스 주소
물론 이것은 런타임 지식을 간단하게 활용하는 것일 뿐입니다. 만약에 더 좋고 효율적인 json 회전 모델을 사용하고 싶다면GitHub의 YY모델을 참고하십시오.
만약 네가 여기까지 보았다면 나에게 좋아요를 눌러라. 너의 좋아요는 내가 억지를 부리는 (아아, 오리지널을 견지하는) 무진장한 동력이다.
그리고......
제 소원은...
세계 평화...

좋은 웹페이지 즐겨찾기