isa和superclass,对象的内存分布 -- 类的结构体-1

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指针优化 。

  • 0 表示 isa_t 没有开启指针优化,不使用 isa_t 中定义的结构体。访问 objc_object 的 isa 会直接返回 isa_t 结构中的 cls 变量,cls 变量会指向对象所属的类的结构,在 64 位设备上会占用 8byte。
  • 1 表示 isa_t 开启了指针优化,不能直接访问 objc_object 的 isa 成员变量 (因为 isa 已经不是一个合法的内存指针了,而是一个 Tagged Pointer ),从其名字 nonpointer 也可获知这个 isa 已经不是一个指针了。但是 isa 中包含了类信息、对象的引用计数等信息,在 64 位设备上充分利用了内存空间。

has_assoc : 对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存 . 当对象有关联引用时,释放对象时需要做额外的逻辑。关联引用就是我们通常用 objc_setAssociatedObject 方法设置给对象的。

has_cxx_dtor : 表示该对象是否有 C++ 或者 Objc 的析构器. 如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。

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

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

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

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,并执行函数。

 

最后 总结一张图

 

能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

1.不能向编译后得到的类增加实例变量
2.能向运行时创建的类中添加实例变量
解释:
1.编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong weak引用.所以不能向存在的类中添加实例变量
2.运行时创建的类是可以添加实例变量,调用class_addIvar函数.但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值