美文网首页
ObjC 学习笔记(四):ivar

ObjC 学习笔记(四):ivar

作者: zevwings | 来源:发表于2019-07-21 12:07 被阅读0次

    在阅读完property_t相关代码之后,接下来学习和property_t十分相似的内容Ivar,我们就不再去做一个和上一篇文章一样的示例代码了,我们直接从Ivar的定义开始学习相关内容。

    /// An opaque type that represents an instance variable.
    typedef struct objc_ivar *Ivar;
    
    struct objc_ivar {
        char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
        char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
        int ivar_offset                                          OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
    }                   
    
    

    Ivar的定义可以看到Ivar有四个属性ivar_name, ivar_type, ivar_offset分别代表了属性的名称类型内存偏移量

    然后我们回过头来再去阅读类的定义,可以看到objc_class中定义了一个objc_ivar_list用于存放属性,接下来我们看看objc_ivar_list的定义

    
    struct objc_class {
    ...
        // objc存在一个objc_ivar_list的指针,用于保存属性
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    ...
    
    } OBJC2_UNAVAILABLE;
    
    // objc_ivar_list 定义
    struct objc_ivar_list {
        // 属性数量
        int ivar_count                                           OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
        /* variable length structure */
        struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    
    

    objc_ivar_list的定义中,可以看到定义了ivar_count用于存储属性数量,ivar_list用于存储Ivar

    然后我们继续学习Ivar相关的方法,和property_t一样,我们先来看获取Ivar相关属性的方法。

    ivar_getName、ivar_getTypeEncoding和ivar_getOffset

    我们简单的看一下这三个方法的代码。

    // 获取属性的名称
    const char *
    ivar_getName(Ivar ivar)
    {
        if (!ivar) return nil;
        return ivar->name;
    }
    
    // 获取属性的类型
    const char *
    ivar_getTypeEncoding(Ivar ivar)
    {
        if (!ivar) return nil;
        return ivar->type;
    }
    
    // 获取属性的偏移量
    ptrdiff_t
    ivar_getOffset(Ivar ivar)
    {
        if (!ivar) return 0;
        return *ivar->offset;
    }
    

    property_t相较,Ivar获取相关属性就简单许多,直接获取了相关的名称,属性等,但是,获取类型的方法,相较property_t只返回了变量的类型,并没有返回变量的相关的修饰符,如strongweak等。

    class_copyIvarList

    接下来我们看获取类中的变量列表。

    Ivar *
    class_copyIvarList(Class cls, unsigned int *outCount)
    {
        const ivar_list_t *ivars;
        Ivar *result = nil;
        unsigned int count = 0;
    
        if (!cls) {
            if (outCount) *outCount = 0;
            return nil;
        }
    
        mutex_locker_t lock(runtimeLock);
    
        assert(cls->isRealized());
        
        if ((ivars = cls->data()->ro->ivars)  &&  ivars->count) {
            result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar));
            
            for (auto& ivar : *ivars) {
                if (!ivar.offset) continue;  // anonymous bitfield
                result[count++] = &ivar;
            }
            result[count] = nil;
        }
        
        if (outCount) *outCount = count;
        return result;
    }
    
    

    从上述代码可以看出ivars的存储结构是cls->data()->ro->ivars,然后也是和property_t一样分配空间给result,最后拼接成返回结果。

    object_setIvar和object_setIvarWithStrongDefault

    我们可以通过object_setIvarobject_setIvarWithStrongDefault去修改变量的值。

    // 如果内存管理方式没有设置,使用weak
    void object_setIvar(id obj, Ivar ivar, id value)
    {
        return _object_setIvar(obj, ivar, value, false /*not strong default*/);
    }
    // 如果内存管理方式没有设置,使用strong
    void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value)
    {
        return _object_setIvar(obj, ivar, value, true /*strong default*/);
    }
    
    // `object_setIvar`和`object_setIvarWithStrongDefault`最后都回使用`_object_setIvar`去设置值。
    static ALWAYS_INLINE 
    void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
    {
        if (!obj  ||  !ivar  ||  obj->isTaggedPointer()) return;
    
        ptrdiff_t offset;
        objc_ivar_memory_management_t memoryManagement;
        
        // 查找ivar的offset,及内存存储方式。
        _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
    
        if (memoryManagement == objc_ivar_memoryUnknown) {
            if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
            else memoryManagement = objc_ivar_memoryUnretained;
        }
    
        //计算ivar的内存位置
        id *location = (id *)((char *)obj + offset);
    
        // 根据内存存储方式进行赋值
        switch (memoryManagement) {
        case objc_ivar_memoryWeak:       objc_storeWeak(location, value); break;
        case objc_ivar_memoryStrong:     objc_storeStrong(location, value); break;
        case objc_ivar_memoryUnretained: *location = value; break;
        case objc_ivar_memoryUnknown:    _objc_fatal("impossible");
        }
    }
    
    

    为示例对象赋值的方法还有object_setInstanceVariableobject_setInstanceVariableWithStrongDefault,这两个方法可以为实例变量赋值。

    object_getIvar

    我们可以通过object_getIvar获取实例对象的变量值。

    id object_getIvar(id obj, Ivar ivar)
    {
        if (!obj  ||  !ivar  ||  obj->isTaggedPointer()) return nil;
    
        ptrdiff_t offset;
        objc_ivar_memory_management_t memoryManagement;
        // 查找ivar的offset,及内存存储方式。
        _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
        
        //计算ivar的内存位置
        id *location = (id *)((char *)obj + offset);
        // 根据内存管理方式返回对应的值
        if (memoryManagement == objc_ivar_memoryWeak) {
            return objc_loadWeak(location);
        } else {
            return *location;
        }
    }
    
    

    class_addIvar

    我们也可以通过class_addIvar为类添加一个成员变量

    BOOL 
    class_addIvar(Class cls, const char *name, size_t size, 
                  uint8_t alignment, const char *type)
    {
        if (!cls) return NO;
    
        if (!type) type = "";
        if (name  &&  0 == strcmp(name, "")) name = nil;
    
        mutex_locker_t lock(runtimeLock);
        // 检查类是否可以被识别
        checkIsKnownClass(cls);
        assert(cls->isRealized());
    
        // No class variables
        if (cls->isMetaClass()) {
            return NO;
        }
    
        // 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);
        }
    
        uint32_t offset = cls->unalignedInstanceSize();
        uint32_t alignMask = (1<<alignment)-1;
        offset = (offset + alignMask) & ~alignMask;
    
        ivar_t& ivar = newlist->get(newlist->count++);
    #if __x86_64__
        // Deliberately over-allocate the ivar offset variable. 
        // Use calloc() to clear all 64 bits. See the note in struct ivar_t.
        ivar.offset = (int32_t *)(int64_t *)calloc(sizeof(int64_t), 1);
    #else
        ivar.offset = (int32_t *)malloc(sizeof(int32_t));
    #endif
        *ivar.offset = offset;
        // 设置Ivar相关属性
        ivar.name = name ? strdupIfMutable(name) : nil;
        ivar.type = strdupIfMutable(type);
        ivar.alignment_raw = alignment;
        ivar.size = (uint32_t)size;
    
        ro_w->ivars = newlist;
        cls->setInstanceSize((uint32_t)(offset + size));
    
        // Ivar layout updated in registerClass.
    
        return YES;
    }
    

    总结

    ivar也可以满足我们添加和获取变量,那和property_t有什么区别呢?
    property包含了ivargettersetter方法,也就是说我们添加一个property会直接为我们增加gettersetter的方法,而ivar只会增加属性。
    所以,我们在大多数时候都会选择property而不是ivar

    更好的阅读体验可以参考个人网站:https://zevwings.com

    相关文章

      网友评论

          本文标题:ObjC 学习笔记(四):ivar

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