objc 源码

作者: 忆思梦 | 来源:发表于2018-11-23 11:12 被阅读106次

    类的结构

    typedef struct objc_class *Class;
    // OBJC2 以前
    struct objc_class {
        Class isa;
    #if !__OBJC2__
        Class super_class;
        const char *name;
        long version;
        long info;
        long instance_size;
        struct objc_ivar_list *ivars;
        struct objc_method_list **methodLists;
        struct objc_cache *cache;
        struct objc_protocol_list *protocols;
    #endif
    }OBJC2_UNAVAILABLE;
    
    // 之后
    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;
        class_data_bits_t bits;
        class_rw_t *data();
        void setData(class_rw_t *newData);
        void setInfo(uint32_t set);
        void clearInfo(uint32_t clear);
        // set and clear must not overlap
        void changeInfo(uint32_t set, uint32_t clear);
        bool hasCustomRR();
        void setHasDefaultRR();
        void setHasCustomRR(bool inherited = false);
        void printCustomRR(bool inherited);
        bool hasCustomAWZ();
        void setHasDefaultAWZ();
        void setHasCustomAWZ(bool inherited = false);
        void printCustomAWZ(bool inherited);
        bool requiresRawIsa();
        void setRequiresRawIsa(bool inherited = false);
        void printRequiresRawIsa(bool inherited);
        bool canAllocIndexed();
        bool canAllocFast();
        bool hasCxxCtor();
        void setHasCxxCtor();
        bool hasCxxDtor();
        void setHasCxxDtor();
        bool isSwift()
    #if SUPPORT_NONPOINTER_ISA
        // Tracked in non-pointer isas; not tracked otherwise
    #else
        bool instancesHaveAssociatedObjects();
        void setInstancesHaveAssociatedObjects();
    #endif
        bool shouldGrowCache();
        void setShouldGrowCache(bool);
        bool shouldFinalizeOnMainThread();
        void setShouldFinalizeOnMainThread()
        bool isInitializing();
        void setInitializing();
        bool isInitialized();
        void setInitialized();
        bool isLoadable();
        IMP getLoadMethod();
        // Locking: To prevent concurrent realization, hold runtimeLock.
        bool isRealized();
        // Returns true if this is an unrealized future class.
        // Locking: To prevent concurrent realization, hold runtimeLock.
        bool isFuture();
        bool isMetaClass();
        // NOT identical to this->ISA when this is a metaclass
        Class getMeta()
        bool isRootClass()
        bool isRootMetaclass()
        const char *mangledName();
        const char *demangledName(bool realize = false);
        const char *nameForLogging();
        // May be unaligned depending on class's ivars.
        uint32_t unalignedInstanceSize();
        // Class's ivar size rounded up to a pointer-size boundary.
        uint32_t alignedInstanceSize();
        size_t instanceSize(size_t extraBytes);
        void setInstanceSize(uint32_t newSize)
    

    关于isa的理解

    ios-runtime-class.png
    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
        property_list_t *propertiesForMeta(bool isMeta) {
            if (isMeta) return nil; // classProperties;
            else return instanceProperties;
        }
    };
    
    • 由上图可知:实例变量的isa指向的是类,而类的isa指向的是元类(metaClass)。
    • 由代码可知:isMeta为YES,返回的是classMethods,反之返回instanceMethods。

    由此分析:**实例方法存放在类中,由类结构体可以看出。而类方法存放在元类(metaClass)里。

    方法缓存 struct objc_cache *cache

    类的所有缓存都存在metaclass上,所以每个类都只有一份方法缓存,而不是每一个类的object都保存一份。

    从父类中继承的方法,也会存在类本身的方法缓存中。当父类的对象调用那个方法的时候,会在父类的metaclass中缓存一份。

    方法缓存限制

    这个问题翻了下runtime的源码:runtime-cache

    static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver){
      //  省略
          if (cache->isConstantEmptyCache()) {
              // Cache is read-only. Replace it.
              cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
          }else if (newOccupied <= capacity / 4 * 3) {
              // Cache is less than 3/4 full. Use it as-is.
          }else {
              // Cache is too full. Expand it.
              cache->expand();            //  1
          }
      //插入数据省略
    }
    
    void cache_t::expand(){
        uint32_t oldCapacity = capacity();
        uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;   //  2
        if ((uint32_t)(mask_t)newCapacity != newCapacity) {     //  3
            // mask overflow - can't grow further
            // fixme this wastes one bit of mask
            newCapacity = oldCapacity;
        }
        reallocate(oldCapacity, newCapacity);
    }
    
    /* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */
    enum {
        INIT_CACHE_SIZE_LOG2 = 2,
        INIT_CACHE_SIZE      = (1 << INIT_CACHE_SIZE_LOG2)
    };
    
    #if __LP64__
    typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
    #else
    typedef uint16_t mask_t;
    #endif
    
    

    由源码可以看出:

    1. 当缓存大于3/4时,调用expand()方法进行扩容。
    2. 计算新的缓存个数,INIT_CACHE_SIZE由下面代码可以看出是一个枚举值为4。
    3. 计算完新的缓存大小,进行了两次强转之后判断是否与原值相等。
    • 强转为mask-t类型,在64位下uint32_t类型。
    • 强转为uint32_t类型。

    结论:缓存的最大限制为2^mask_t

    注意:这里是OBJC2以前的实现。

    类的方法为什么存成一个数组,而不是散列表?

    • 散列表是无序的,OC方法列表是有序的,OC查找方法是会顺着list依次寻找,并且category方法的优先级高于本身,所以要保证category方法在前面。如果用hash,则顺序无法保证。(同时解释了为什么category的方法优先级高)
    • 散列表是有空槽的,会浪费空间。
    • list的方法还保存了除了selector和imp之外其他属性。(下面代码
    struct objc_cache {
        uintptr_t mask  OBJC2_UNAVAILABLE;            /* total = mask + 1 */
        uintptr_t occupied  OBJC2_UNAVAILABLE;
        cache_entry *buckets[1]  OBJC2_UNAVAILABLE;
    };
    typedef struct {
        SEL name;     // same layout as struct old_method
        void *unused;
        IMP imp;  // same layout as struct old_method
    } cache_entry;
    

    方法列表是数组,缓存列表是散列表

    • 类的方法列表是数组,需要保证顺序。
    • 方法缓存是散列表,要保证效率,检索快。

    经测试:当存在Category中的方法和类中方法名相同时,Category的方法总是排在类中的方法之前。(这就是Cagegory方法的优先级高于类中方法的原因)。

    方法列表的顺序部分和load的顺序有关,先是方法,然后是getter、setter方法。如果方法重名,category方法在前。

    SEL与IMP,Method,Ivar,property。

    SEL:是用字符串表示的某个对象的方法(虚拟表中指向某个函数指针的字符串)

    IMP:表示的是指向函数实现的指针。

    Method:是SEL+IMP+类型

    method.png

    Ivar:实例变量

    property:实例变量+setter+getter

    关联属性

    方法

    • objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    • objc_getAssociatedObject(id object, const void *key):获取对象关联值
    • objc_removeAssociatedObjects(id object):移除对象的所有关联
    object: 目标对象
    key:键
    value:关联值
    policy:关联策略
    

    关联策略

    objc_AssociationPolicy:一共有以下几种关联策略。

    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
                                                *   The association is not made atomically. */
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.
                                                *   The association is not made atomically. */
        OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                                *   The association is made atomically. */
        OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                                *   The association is made atomically. */
    };
    

    分别就是:assign,nonatomic retain,notatomic copy, retain, copy。

    内部剖析

    class AssociationsManager {
        AssociationsHashMap &associations() //hash map,所有的关联引用,对象指针 -> PtrPtrHashMap.
    };
    
    //关联
    class ObjcAssociation {
        uintptr_t policy()  //策略
        id value()  //值
    };
    

    系统管理着一个关联管理者AssociationsManager,AssociationsManager内部有个AssociationsHashMap属性,这是一个hashmap,以对象指针为key,以“这个对象所有的关联引用map”对象为value,“这个对象所有的关联引用map”是以设置的key为关联键,以ObjcAssociation为值,ObjcAssociation包涵关联值和策略。

    问题:用runtime 关联一个属性,这个属性什么时候释放?

    • 当直接调用objc_removeAssociatedObjects方法时。
    • 当销毁对象的时候,会调用objc_destructInstance方法,最终还是会调用移除关联对象的方法。

    相关文章

      网友评论

      • chouheiwa:你的objc_class已经已经过时了,runtim中已经给出相应的OBJC2_UNAVAILABLE,最好再给出新的底层结构,否则可能会造成一定程度的误导哦:smiley:
        忆思梦:多谢指正
      • 齐舞647:增光哥优秀!

      本文标题:objc 源码

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