美文网首页iOS面试题
iOS面试-类的动态创建

iOS面试-类的动态创建

作者: xxxxxxxx_123 | 来源:发表于2020-02-26 19:25 被阅读0次

    概念

    1. 如何动态创建一个类?

    Class TPerson = objc_allocateClassPair([NSObject class], "TPerson", 0);
    class_addIvar(TPerson, "nickName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
    objc_registerClassPair(TPerson);
    

    那么objc_allocateClassPair是如何实现的呢?

    Class objc_allocateClassPair(Class superclass, const char *name, 
                                 size_t extraBytes)
    {
        Class cls, meta;
    
        // 如果已经存在相同名称的类,则创建失败,直接返回nil
        if (look_up_class(name, NO, NO)) return nil;
    
        mutex_locker_t lock(runtimeLock);
    
        // Fail if the class name is in use.
        // Fail if the superclass isn't kosher.
        if (getClassExceptSomeSwift(name)  ||
            !verifySuperclass(superclass, true/*rootOK*/))
        {
            return nil;
        }
    
        // Allocate new classes.
        cls  = alloc_class_for_subclass(superclass, extraBytes);
        meta = alloc_class_for_subclass(superclass, extraBytes);
    
        // fixme mangle the name if it looks swift-y?
        objc_initializeClassPair_internal(superclass, name, cls, meta);
    
        return cls;
    }
    

    由代码可知,objc_allocateClassPair有3个参数:

    • 需要创建的类的父类
    • 需要创建的类名
    • 需要创建的类的内存容量

    创建一个类的时候,首先我们要判断是否已经存在相同名称的类,如果存在则创建失败,直接返回nil;其次还需要判断其父类是否已经实现,如果没有实现也会创建失败;然后调用

    static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
    

    进行创建类的相关信息,设置类和元类的data数据如rw/ro,并绑定父类、子类、元类的关系;最后将该类加入到存放class的表中。

    2. 添加成员变量

    添加成员变量的实现如下:

    BOOL class_addIvar(Class cls, const char *name, size_t size, 
                  uint8_t alignment, const char *type)
    {
        ...... 
    
        // Can only add ivars to in-construction classes.
        if (!(cls->data()->flags & RW_CONSTRUCTING)) {
            return NO;
        }
    
        // Check for existing ivar with this name, unless it's anonymous.
        // Check for too-big ivar.
        // fixme check for superclass ivar too?
        if ((name  &&  getIvar(cls, name))  ||  size > UINT32_MAX) {
            return NO;
        }
    
        class_ro_t *ro_w = make_ro_writeable(cls->data());
    
        // fixme allocate less memory here
        
        ivar_list_t *oldlist, *newlist;
        if ((oldlist = (ivar_list_t *)cls->data()->ro->ivars)) {
            size_t oldsize = oldlist->byteSize();
            newlist = (ivar_list_t *)calloc(oldsize + oldlist->entsize(), 1);
            memcpy(newlist, oldlist, oldsize);
            free(oldlist);
        } else {
            newlist = (ivar_list_t *)calloc(sizeof(ivar_list_t), 1);
            newlist->entsizeAndFlags = (uint32_t)sizeof(ivar_t);
        }
        
        ......
    
        ro_w->ivars = newlist;
        cls->setInstanceSize((uint32_t)(offset + size));
    
        // Ivar layout updated in registerClass.
    
        return YES;
    }
    
    

    由代码可知,需要传入的参数如下:

    • 需要创建成员变量的类
    • 成员变量的名称
    • 成员变量的内存容量
    • 该成员变量的内存对齐所偏移的量
    • 成员变量的类型

    给类添加成员变量时,首先要确定该类是正在构造的类;其次需要确定该变量名称的唯一性,即在作用域内的唯一性,且不是内存超大类型的变量;然后对已经创建的内存进行扩容,将新的变量写入到roivars中。

    3. 注册类

    • 将类的状态置为RW_CONSTRUCTED
    • 将类插入存储类的表中
    void objc_registerClassPair(Class cls)
    {
        mutex_locker_t lock(runtimeLock);
    
        checkIsKnownClass(cls);
    
        ......
    
        // 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);
    
        // Add to named class table.
        addNamedClass(cls, cls->data()->ro->name);
    }
    

    这样,动态创建类就完成了。上面的例子中,我们不止创建了一个名称为TPerson的类,并且为该类创建了一个名称为nickName类型为NSString的成员变量。我们可以打印验证结果:

    id person = [TPerson alloc];
    [person setValue:@"chengXuYuan" forKey:@"nickName"];
    NSLog(@"--%@--",[person valueForKey:@"nickName"]);
    
    结果为:
    --chengXuYuan--
    

    题目

    1. 以下成员变量是否能够添加成功?为什么?

    Class TPerson = objc_allocateClassPair([NSObject class], "TPerson", 0);
    objc_registerClassPair(TPerson);
    class_addIvar(TPerson, "nickName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
    

    答案:不能添加成功。因为成员变量是存在于classclass_ro_t中,当我们调用objc_registerClassPair注册完类之后,类的状态就置为RW_CONSTRUCTED,而在调用class_addIvar添加成员变量的时候,会判断当前类的状态是否是RW_CONSTRUCTED,如果是则会添加成员变量失败。简而言之,成员变量存在ro中,创建类成功之后ro就是不可变的,所以就不能添加成员变量了。

    2. 是否可以添加property?如果可以,如何添加?

    答案:可以添加,因为property是存在rw里的,所以可以在任意时刻添加。首先我们来看一下添加属性的API:

    BOOL 
    class_addProperty(Class cls, const char *name, 
                      const objc_property_attribute_t *attrs, unsigned int n)
    {
        return _class_addProperty(cls, name, attrs, n, NO);
    }
    
    static bool 
    _class_addProperty(Class cls, const char *name, 
                       const objc_property_attribute_t *attrs, unsigned int count, 
                       bool replace)
    {
        if (!cls) return NO;
        if (!name) return NO;
    
        property_t *prop = class_getProperty(cls, name);
        if (prop  &&  !replace) {
            // already exists, refuse to replace
            return NO;
        } 
        else if (prop) {
            // replace existing
            mutex_locker_t lock(runtimeLock);
            try_free(prop->attributes);
            prop->attributes = copyPropertyAttributeString(attrs, count);
            return YES;
        }
        else {
            mutex_locker_t lock(runtimeLock);
            
            assert(cls->isRealized());
            
            property_list_t *proplist = (property_list_t *)
                malloc(sizeof(*proplist));
            proplist->count = 1;
            proplist->entsizeAndFlags = sizeof(proplist->first);
            proplist->first.name = strdupIfMutable(name);
            proplist->first.attributes = copyPropertyAttributeString(attrs, count);
            
            cls->data()->properties.attachLists(&proplist, 1);
            
            return YES;
        }
    }
    

    由代码可知,class_addProperty方法需要传入的参数如下:

    • 属性的名称
    • 属性的修饰词数组
    • 属性修饰词数组的元素个数

    属性的修饰词数组中的第一个元素是按照属性的类型规定的Code:

    Property declaration Code
    char Tc
    double Td
    enum/int/signed Ti
    float Tf
    long Tl
    short Ts
    signed TI
    id T@
    int指针 T^i
    void指针 T^v

    其中属性的修饰词指的就是strong、weak、copy、assign、nonatomic、atomic等修饰词。

    Code Meaning
    R read-only
    & retain
    C copy
    N nonatomic
    G<name> getter
    S<name> setter
    D dynamic
    W weak

    属性的修饰词数组的最后一个元素则是V_拼接属性名称。

    综上所述,假如我们需要给TPerson添加一个属性:

    @property (nonatomic, copy) NSString *englishName; 
    

    那么我们可以如下实现:

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

    但是需要注意的是,此时添加的属性是无法进行赋值和取值操作的,因为我们并没有实现其set/get方法。 只有实现了set/get方法,动态添加的属性,才是一个完整的属性。

    3. 为TPerson添加方法

    API如下:

    BOOL 
    class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    {
        if (!cls) return NO;
    
        mutex_locker_t lock(runtimeLock);
        return ! addMethod(cls, name, imp, types ?: "", NO);
    }
    
    static IMP 
    addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
    {
        IMP result = nil;
        
        ......
        
        method_t *m;
        if ((m = getMethodNoSuper_nolock(cls, name))) {
            // already exists
            if (!replace) {
                result = m->imp;
            } else {
                result = _method_setImplementation(cls, m, imp);
            }
        } else {
            // fixme optimize
            method_list_t *newlist;
            newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
            newlist->entsizeAndFlags = 
                (uint32_t)sizeof(method_t) | fixed_up_method_list;
            newlist->count = 1;
            newlist->first.name = name;
            newlist->first.types = strdupIfMutable(types);
            newlist->first.imp = imp;
    
            prepareMethodLists(cls, &newlist, 1, NO, NO);
            cls->data()->methods.attachLists(&newlist, 1);
            flushCaches(cls);
    
            result = nil;
        }
    
        return result;
    }
    

    由代码可知,需要的参数如下:

    • 要添加方法的类
    • 方法编号(方法名称)
    • 方法实现的指针
    • 方法签名

    给类动态的添加方法的时候,首先通过类名和方法去寻找是否存在method_t,如果存在则添加失败,否则将当前方法绑定到类的data中的methods里。这样方法就添加成功了。

    以下代码给TPersonenglishName属性添加了set/get方法:

    class_addMethod(TPerson, @selector(setEnglishName:), (IMP)SetterEnglishName, "v@:@");
    class_addMethod(TPerson, @selector(englishName), (IMP)GetEnglishName, "@@:");
    
    void SetEnglishName(NSString *value){
        printf("%s/n",__func__);
    }
    
    NSString *GetEnglishName(){
        printf("%s/n",__func__);
        return @"HaHaHa";
    }
    

    总结

    当我们需要动态创建类的时候,一定要注意该类相关信息的处理。比如添加成员变量必须在注册类之前;添加属性必须为其添加set/get方法才能正常使用。

    相关文章

      网友评论

        本文标题:iOS面试-类的动态创建

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