isa探究

544 阅读8分钟

Class、objc_object、id的定义,都基于objc_class,一个NSObject对象的就是objc_object类型的结构体指针objc_object *

typedef struct objc_class *Class; //objc_class的结构体指针objc_class *,重定义一个名字Class

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id; //重定义一个NSObject对象名字id,类型为objc_object *

可以看到一个NSObject对象为objc_object类型,里面有8个字节的结构体指针isa,而alloc探究的时候,得知实际实际上给NSObject对象申请的的空间至少为16字节,除了isa之外,并且新增加的属性字段内容就存放在里面,且数据就在isa后面剩余的空间地址存放,下面探究一下对象的数据存放

先从objc_object开始(下面均以objc-750为例探究)

objc_object

通过NSObject类型可以得到,存放的是带有Class参数的Object_object结构体指针,而里面的Class代表的是对象的所属类(后面介绍),那么其参数和isa是怎么区分开的呢,参考下面一段代码

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

发现里面存在一个 isa_t类型的isa,似乎有什么玄机,点击查看其结构

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

发现了一个联合体,顿时豁然开朗,原来isa和数据都存放在一个联合体中,其共享一片内存,系统通过联合体,将Class类型的isa和其他属性内容存放到了里面的,并且通过某种手段处理其信息,合理区分,并且可以拿出

参考上面的获取ISA()方法,可以看到如何从该联合体中拿出isa的

#   define ISA_MASK        0x00007ffffffffff8ULL

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

从上面可以看出去除isa的过程,TaggedPointer这个特殊结构先不提,后面再讲,可以看到最终的返回了这么一句:

return (Class)(isa.bits & ISA_MASK);

而ISA_MASK的值为0x00007ffffffffff8ULL,通过将isa.bits地址的内容按位与ISA_MASK,即可得到isa的地址

而其他的属性内容就存放在isa地址后面的剩余空间,系统通过类似的手段将字段内的值读取出来

objc_class

前面可以看到Class是一个objc_class类型的结构体指针,并且NSObject对象中只保留了属性字段内容,其方法、类方法、属性方法、属性名字协议等均不在对象中,那么只能在objc_class类型的isa中寻找答案了

下面查看一下objc_class的部分源码

struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;             // formerly cache pointer and vtable
class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

class_rw_t *data() { 
    return bits.data();
}
void setData(class_rw_t *newData) {
    bits.setData(newData);
}

这个就是isa的方法,发现其继承自objc_object,里面也有一个isa,其isa获取过程同对象一样,里面存放的为元类的结构体指针

superclass 则为其父类的结构体指针,占用8字节

cache 为快速缓存,为了快速调用方法,占用16字节,可以查看其结构

bits 通过data方法可以知道从里面获取到了class_rw_t *类型的数据

前面给了参数的字节大小,可以通过class的地址直接向后偏移32位,即:8 + 8 + 16字节(注意最新的可能就不是这个了),即可找到bits参数,然后通过data方法可以找到class_rw_t,然后可以看到ro结构

那么class_rw_t结构体是什么样子,查看一下源码

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif

    void setFlags(uint32_t set) 
    {
        OSAtomicOr32Barrier(set, &flags);
    }

    void clearFlags(uint32_t clear) 
    {
        OSAtomicXor32Barrier(clear, &flags);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        assert((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }
};

顿时茅塞顿开,原来这里面就是存放了方法、属性、协议等的地方

里面存放了该class方法、属性、协议(注意不包括对象的类方法,类方法等在元类的rw中),且以method_array_t类型的methods为例,其为二级指针类型,后面介绍

紧接着,rw里面还有一个const类型的class_ro_t结构体指针,里面也存放了了方法、属性、协议、ivars列表

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

ro被const修饰,里面的数据为编译器就已经确定了的方法、属性、字段、协议方法(注意里面是对象方法,类方法等在元类的ro中),加入到数组中,当加载应用时,方法、协属性会被加载到rw里面

在ro的方法列表中,里面存放了每个方法的名称、类型(包括返回值、参数等简称,例如:v16@0:8)、imp指针

method、ivar和property等结构源码如下

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};

struct property_t {
    const char *name;
    const char *attributes;
};

介绍完ro,查看一下rw中method_array_t等类型,其都继承自list_array_tt类型,为一个二维数组,以method为例,还额外添加了beginCategoryMethodLists,endCategoryMethodLists等方法

methods:

class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_list_t **beginCategoryMethodLists() {
        return beginLists();
    }
    
    method_list_t **endCategoryMethodLists(Class cls);

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

properties:

class property_array_t : 
    public list_array_tt<property_t, property_list_t> 
{
    typedef list_array_tt<property_t, property_list_t> Super;

 public:
    property_array_t duplicate() {
        return Super::duplicate<property_array_t>();
    }
};

protocols:

class protocol_array_t : 
    public list_array_tt<protocol_ref_t, protocol_list_t> 
{
    typedef list_array_tt<protocol_ref_t, protocol_list_t> Super;

 public:
    protocol_array_t duplicate() {
        return Super::duplicate<protocol_array_t>();
    }
};

下面查看一下list_array_tt类型,发现里面定义了二维数组的一些基本操作方法,其中也包括往里面添加一个数组attachLists,后面不少地方会用到,attachLists方法会将后添加进入的数组,放到原始数组的前面

uint32_t count() {
    uint32_t result = 0;
    for (auto lists = beginLists(), end = endLists(); 
         lists != end;
         ++lists)
    {
        result += (*lists)->count;
    }
    return result;
}

iterator begin() {
    return iterator(beginLists(), endLists());
}

iterator end() {
    List **e = endLists();
    return iterator(e, e);
}


uint32_t countLists() {
    if (hasArray()) {
        return array()->count;
    } else if (list) {
        return 1;
    } else {
        return 0;
    }
}

List** beginLists() {
    if (hasArray()) {
        return array()->lists;
    } else {
        return &list;
    }
}

List** endLists() {
    if (hasArray()) {
        return array()->lists + array()->count;
    } else if (list) {
        return &list + 1;
    } else {
        return &list;
    }
}

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}

然后查看用到此方法的函数,addMethod、addMethods、class_addProtocol、_class_addProperty、attachCategories,以addMethod为例,发现新增加的方法,通过attachLists直接被添加到rw的method里面去了

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    runtimeLock.assertLocked();

    checkIsKnownClass(cls);
    
    assert(types);
    assert(cls->isRealized());

    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;
}

然后查看methodizeClass方法,其在应用加载的时候,会将ro里面的baseMethods、baseProtocols、baseProperties均加载到了rw里面

* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);

    if (PrintConnecting) {
        if (cats) {
            for (uint32_t i = 0; i < cats->count; i++) {
                _objc_inform("CLASS: attached category %c%s(%s)", 
                             isMeta ? '+' : '-', 
                             cls->nameForLogging(), cats->list[i].cat->name);
            }
        }
    }
    
    if (cats) free(cats);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name));
        }
        assert(sel_registerName(sel_getName(meth.name)) == meth.name); 
    }
#endif
}

最后还调用了attachCategories方法,将类别里面的属性、协议、方法都加入到了rw里面,由于attachLists会把后加入的数组放到列表最前面,因此通过类别添加的方法,会被放置到方法列表的最前面。

因此你可能想到了,为什么同名会优先调用分类的方法了,毕竟方法查找是从前往后找的,找到即执行

追加分类的方法如下所示:

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

最后

可以根据前面的方法,分别打印出改类ro中的方法和其元类的ro方法

可以发现,对象方法均储存在类或者父类中,类方法均储存在元类或者元类的父类中

isa目前就先探究到这里了