美文网首页
runtime应用

runtime应用

作者: 半边枫叶 | 来源:发表于2020-01-12 12:02 被阅读0次

    我们来通过runtime的动态属性创建一个Class:

    • 首先创建Class
    Class LGPerson = objc_allocateClassPair([NSObject class], "LGPerson", 0);
    

    下面我们来看下这个方法的参数

    objc_allocateClassPair(<#Class  _Nullable __unsafe_unretained superclass#>, <#const char * _Nonnull name#>, <#size_t extraBytes#>)
    

    上面的这个方法用来创建Class,有三个参数:第一个为superClass,第二个为属性的name,第三个为extraBytes。

    • 然后增加成员变量
    class_addIvar(LGPerson, "lgName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
    

    这个方法有四个参数

    class_addIvar(<#Class  _Nullable __unsafe_unretained cls#>, <#const char * _Nonnull name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char * _Nullable types#>)
    

    参数一:目标class;
    参数二:成员变量名称;
    参数三:成员变量的字节大小,字符串就是8个字节;
    参数四:alignment:对齐方式。因为成员变量占用8个字节,所以对齐方式应该是3。我们可以通过log函数计算得到;
    参数五:类型为任意类型。

    • 注册Class
    objc_registerClassPair(LGPerson);
    

    这样我们就完成了Class的动态注册添加,然后就可以使用该类了

    id person = [LGPerson alloc];
    [person setValue:@"KC" forKey:@"lgName"];
    NSLog(@"%@",[person valueForKey:@"lgName"]);
    

    问题:如果我们将增加成员变量和注册Class两个步骤调换一下顺序可不可以呢?
    我们通过实验,调换顺序后,会crash崩溃。下面我们来探究为什么不行呢?
    通过查看Class的结构我们发现对象的成员变量ivars存在class的ro中
    const ivar_list_t * ivars;

    struct class_ro_t {    
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
        property_list_t *baseProperties;
    }
    

    ro是在编译时候就确定了,不能进行动态添加的。所以在注册完成Class后就不能再添加了。
    我们通过跟踪objc_registerClassPairclass_addIvar方法也可以找到证据
    下面是objc_registerClassPair中的,使用RW_CONSTRUCTED做了标识

    // Clear "under construction" bit, set "done constructing" bit
    cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
    cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
    

    然后在class_addIvar方法中判断,是否标识了RW_CONSTRUCTING,如果是直接return,中指添加流程。

    // Can only add ivars to in-construction classes.
    if (!(cls->data()->flags & RW_CONSTRUCTING)) {
        return NO;
    }
    

    现在我们明白了为什么不能再注册完Class后不能再添加ivars了,那么能不能动态添加property属性呢?
    答案是可以的,因为property可以存储在rw中。下面我们来添加property:

    首先我们给LGTeacher对象正常添加一个subject属性。

    @interface LGTeacher : NSObject
    @property (nonatomic, copy) NSString *subject; // 属性的属性
    @end
    

    然后我们添加下面的方法来协助我们打印出Class的property信息。

    void lg_printerProperty(Class targetClass){
        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList(targetClass, &outCount);
        for (i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
        }
    }
    

    我们调用打印方法来打印LGTeacher的属性信息

    lg_printerProperty([LGTeacher class]);
    

    打印结果如下:

    subject T@"NSString",C,N,V_subject
    (lldb) 
    

    属性的name为“subject”,属性的相关属性配置为"T@"NSString",C,N,V_subject"。我们来解读一下这个属性配置:
    其中的T@"String"意思为属性的类型为String, T@为固定的拼接格式;C表示copy;N表示nonatomicV_subject中的subject为名字,V_为固定的拼接格式。
    然后我们就可以模仿上面的属性配置来动态的给Class增加property了,代码如下:

    void lg_class_addProperty(Class targetClass , const char *propertyName){
        
        objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
        objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
        objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
        objc_property_attribute_t backingivar  = { "V", [NSString stringWithFormat:@"_%@",[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]].UTF8String };  //variable name
        objc_property_attribute_t attrs[] = {type, ownership0, ownership,backingivar};
    
        class_addProperty(targetClass, propertyName, attrs, 4);
    
    }
    

    封装好了上述方法,我们就可以直接调用来增加property,并且打印出我们添加的property。

    lg_class_addProperty(LGPerson, "subject");
    lg_printerProperty(LGPerson);
    

    发现结果和正常添加的一样。
    给Class添加完成property后,我们就可以使用添加的property了。

    [person setValue:@"master" forKey:@"subject"];
    NSLog(@"%@",[person valueForKey:@"subject"]);
    

    我们可以直接通过KVC设置我们添加的属性吗?
    现在还不行,因为我们通过KVC给对象设置属性的时候,KVO会通过set方法来进行赋值,而我们现在只是给Class添加了属性,还没有添加set和get方法。下面我们来给属性添加set和get方法

    class_addMethod(LGPerson, @selector(setSubject:), (IMP)lgSetter, "v@:@");
    class_addMethod(LGPerson, @selector(subject), (IMP)lgName, "@@:");
    

    其中的imp实现为我们自己实现的方法,现在我们就可以正常的对属性进行赋值和访问了。

    void lgSetter(NSString *value){
        printf("%s/n",__func__);
    }
    
    NSString *lgName(){
        printf("%s/n",__func__);
        return @"master NB";
    }
    

    相关文章

      网友评论

          本文标题:runtime应用

          本文链接:https://www.haomeiwen.com/subject/djuiactx.html