objc 源码

类的结构

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方法,最终还是会调用移除关联对象的方法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352