• Objective-C的反射机制


    从JSONModel看Objective-C的反射机制

    前言

    移动互联时代,JSON作为一种数据传输格式几乎随处可见。作为iOS开发者,收到一串JSON字符串要怎么处理?我想多数情况下是需要将它转成自定义的NSObject对象再使用,对于这个转换的过程,大部分人是这么做的:

    1
    2
    3
    4
    5
    6
    7
    
    NSDictionary* json = (fetch from Internet) ...
    User* user=[[User alloc] init];
    user.userId =[json objectForKey:@"userId"];
    user.nick= [json objectForKey:@"nick"];
    user.image = [json objectForKey:@"image"];
    user.age = [json objectForKey:@"age"];
    ...
    

    这样的代码没错,但也绝对说不上优雅,model里面的属性越多,冗余的代码量也相应越多。对于这个问题,自然是有更好的解决方案,比如说这样:

    1
    2
    
    NSError* err = nil;
    User* user = [[User alloc] initWithDictionary:json error:&err];
    

    两行代码足矣,当然,实现这个功能,实际上是把很多背后的工作交给JSONModel这个开源包去做了。至于其实现原理,则主要是基于Objective-C Runtime的反射机制。

    关于反射

    《Thinking in Java》中将反射称解释为运行时的类信息,说白了就是这个类信息在编译的时候是未知的,需要在程序运行期间动态获取,而这正是我们之前试图去解决的问题。对于从网络上获取到的一段JSON字符串,在代码编译期间当然是无法知晓的。虽然这里说的是Java语言,但是对于Objective-C,这种反射机制也是同样支持的。

    JSONModel中的实现

    打断点记录了下JSONModel这个类中的方法调用顺序如下:

    调用顺序

    对象属性的获取则主要在最后一个inspectProperties方法

    以下是inspectProperties方法的代码片段:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    
    ···
    
    //inspects the class, get's a list of the class properties
    -(void)__inspectProperties
    {
        //JMLog(@"Inspect class: %@", [self class]);
        
        NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];
        
        //temp variables for the loops
        Class class = [self class];
        NSScanner* scanner = nil;
        NSString* propertyType = nil;
        
        // inspect inherited properties up to the JSONModel class
        while (class != [JSONModel class]) {
            //JMLog(@"inspecting: %@", NSStringFromClass(class));
            
            unsigned int propertyCount;
            objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
            
            //loop over the class properties
            for (unsigned int i = 0; i < propertyCount; i++) {
    
                JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
    
                //get property name
                objc_property_t property = properties[i];
                const char *propertyName = property_getName(property);
                p.name = [NSString stringWithUTF8String:propertyName];
                
                //JMLog(@"property: %@", p.name);
                
                //get property attributes
                const char *attrs = property_getAttributes(property);
                NSString* propertyAttributes = [NSString stringWithUTF8String:attrs];
                
                if ([propertyAttributes hasPrefix:@"Tc,"]) {
                    //mask BOOLs as structs so they can have custom convertors
                    p.structName = @"BOOL";
                }
                
                scanner = [NSScanner scannerWithString: propertyAttributes];
                
                //JMLog(@"attr: %@", [NSString stringWithCString:attrs encoding:NSUTF8StringEncoding]);
                [scanner scanUpToString:@"T" intoString: nil];
                [scanner scanString:@"T" intoString:nil];
    
    ···
    
    //finally store the property index in the static property index
    objc_setAssociatedObject(self.class,
                             &kClassPropertiesKey,
                             [propertyIndex copy],
                             OBJC_ASSOCIATION_RETAIN // This is atomic
                             );
    

    在这边可以看到基本步骤如下

    1. 通过调用自身的class方法获取当前类的元数据信息
    2. 通过runtime的 class_copyPropertyList 方法取得当前类的属性列表,以指针数组的形式返回
    3. 遍历指针数组,通过property_getName获取属性名,property_getAttributes获取属性类型
    4. 使用NSScanner来扫描属性类型字符串,将类似如下的形式"T@"NSNumber",&,N,V_id",处理成NSNumber,逐个属性循环处理
    5. 将所有处理好的数据放入propertyIndex这个字典中
    6. 通过objc_setAssociatedObject将这些数据关联到kClassPropertiesKey

    使用时在properties方法中这样取出属性数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    //returns a list of the model's properties
    -(NSArray*)__properties__
    {
        //fetch the associated object
        NSDictionary* classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
        if (classProperties) return [classProperties allValues];
    
        //if here, the class needs to inspect itself
        [self __setup__];
        
        //return the property list
        classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
        return [classProperties allValues];
    }
    

    以上就是JSONModel中使用反射机制实现的类属性获取过程,相比常见的逐个取值赋值的方式,这种方法在代码上的确简洁优雅了很多,特别是对于使用的类属性比较复杂的情况,免除了很多不必要的代码。

  • 相关阅读:
    react-umi 光速上手
    vue 和 react 的区别
    SP12323 NAKANJ
    UVA439 骑士的移动
    NOI 2020 Vlog
    二叉查找树
    可持久化线段树(主席树)
    权值线段树
    YNOI2020 游记
    《四月是你的谎言》语录
  • 原文地址:https://www.cnblogs.com/sunshaowen/p/4157371.html
Copyright © 2020-2023  润新知