isa和superclass,对象的内存分布

ios中对象有几种?
实例对象:isa 指针、具体的成员变量的值(自己的公开,私有变量+父类的公开,私有变量),比如age=10,这里存的是10,
类对象:isa、superclass、类属性信息(用class修饰的属性)、对象方法、协议、成员变量列表,这里存的是变量的名字,比如age,也是既有自己的,也有父类的
元类对象:isa、superclass、类方法……

类对象也是可以有属性的 , 比如 NSURLSession就有一个class修饰的属性,相当于单例,其他的还有NSFileManager的defaultManager之类的

isa 指向?superclass 指向?


 

方法说明

1.Class objc_getClass(const char *aClassName)

  • 1> 传入字符串类名
  • 2> 返回的都是对应的类对象,因为传入的是字符串,

2.Class object_getClass(id obj)

  • 1> 传入的obj可能是instance对象、class对象、meta-class对象
  • 2> 返回值
    a) 如果是instance对象,返回class对象
    b) 如果是class对象,返回meta-class对象
    c) 如果是meta-class对象,返回NSObject(基类)的meta-class对象

    总结就是返回isa指向的对象

3 . class方法

NSObject *obj1 = [[NSObject alloc] init];
Class objClass1 = [obj1 class];
Class objClass2 = [NSObject class];
//class 方法返回的一直是class对象,类对象,而不是元类对象
Class objClass3 = [[NSObject class] class];
Class objClass4 = object_getClass(obj1);
NSLog(@"%p-%p-%p-%p",objClass1,objClass2,objClass3,objClass4);

上述都是同一个类对象,打印出的地址相同,每个类在内存中有且只有一个class对象
 

源码分析

+ (Class)class {
    return self;
}
 
- (Class)class {
    return object_getClass(self);
}
 
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
 
inline Class 
objc_object::getIsa() 
{
    if (isTaggedPointer()) {
        uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
    return ISA();
}
 

类的本质

类的本质是结构体

#objc_class:objc_object
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();
    }
    // 诸多方法
}

#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_ro_t
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//instance对象占用的内存空间
#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;
    }
};

#objc_object
struct objc_object {
private:
    isa_t isa;//类的isa指针是私有的

public:
      // 诸多方法
}

结构体结构

右边的值 

第一位index,代表是否开启isa指针优化 ,  https://mp.csdn.net/postedit/85324111。index = 1,代表开启isa指针优化。

has_assoc : 对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存

has_cxx_dtor : 表示该对象是否有 C++ 或者 Objc 的析构器

shiftcls : 类的指针。arm64架构中有33位可以存储类指针。

源码中isa.shiftcls = (uintptr_t)cls >> 3;将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。具体可以看从 NSObject 的初始化了解 isa这篇文章里面的shiftcls分析。

magic : 判断对象是否初始化完成,在arm64中0x16是调试器判断当前对象是真的对象还是没有初始化的空间。

weakly_referenced : 对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放

deallocating : 对象是否正在释放内存

has_sidetable_rc : 判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。

extra_rc : 存放该对象的引用计数值减一后的结果。对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc的值就为 9。

class_ro_t是一个指向常量的指针,存储来编译器决定了的属性、方法和遵守协议。rw-readwrite (可读写),ro-readonly(只读) , 

在编译期类的结构中的 class_data_bits_t *data指向的是一个 class_ro_t *指针 , 在运行时调用 realizeClass方法,会做以下3件事情:

  1. 从 class_data_bits_t调用 data方法,将结果从 class_rw_t强制转换为 class_ro_t指针

  2. 初始化一个 class_rw_t结构体

  3. 设置结构体 ro的值以及 flag

    最后调用methodizeClass方法,把类里面的属性,协议,方法都加载进来。

cache_t的具体实现

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
}

typedef unsigned int uint32_t;
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

typedef unsigned long  uintptr_t;
typedef uintptr_t cache_key_t;

struct bucket_t {
private:
    cache_key_t _key;
    IMP _imp;
}

根据源码,我们可以知道cache_t中存储了一个bucket_t的结构体,和两个unsigned int的变量。

mask:分配用来缓存bucket的总数。
occupied:表明目前实际占用的缓存bucket的个数。

bucket_t的结构体中存储了一个unsigned long和一个IMP。IMP是一个函数指针,指向了一个方法的具体实现。

cache_t中的bucket_t *_buckets其实就是一个散列表,用来存储Method的链表。

Cache的作用主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。


struct method_t {
    SEL name;
    const char *types;
    IMP 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; }
    };
};

方法method的定义如上。里面包含3个成员变量。SEL是方法的名字name。types是Type Encoding类型编码,表示函数的类型 , 函数的返回值类型 和 参数类型。

IMP是一个函数指针,指向的是函数的具体实现。在runtime中消息传递和转发的目的就是为了找到IMP,并执行函数。

最后 总结一张图

猜你喜欢

转载自blog.csdn.net/u014600626/article/details/85235979
isa
今日推荐