【OC底层】(4)对象创建之内存布局

一.前言

基于objc4-781
基于64bit运行环境

二.抛砖引玉:起源的问题

1.下面的代码输出什么?

@implementation Son : Father
- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

2.下面代码的结果

BOOL res1 = [(id)NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)NSObject class] isMemberfClass:[NSObject class]];
BOOL res3 = [(id)Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)Sark class] isMemberfClass:[Sark class]];

3.下面的代码会?compile Error / Runtime Crash / NSLog…?

@interface NSObject (Sark)
- (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
    NSLog(@"IMP:%@",NSStringFromSelector(_cmd));
}
@end
//  测试代码
[NSObject foo];
[[NSObject new] foo];

4.下面的代码会?compile Error / Runtime Crash / NSLog…?

@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak {
    NSLog(@"my name's %@", self.name);
}
@end
//  测试代码
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];

以上问题转载https://www.jianshu.com/p/9d649ce6d0b8

三.认识OC对象类型与结构

在iOS当中,一共有3种对象,分别为

实例对象(instance
类对象(class
元类对象(meta-class

通过基于objc2源码分析得到NSObject的结构

typedef struct objc_class *Class;
typedef struct objc_object *id;
//  NSObject转为C++的结构,实质只有一个Class的成员
struct NSObject_IMPL {
    Class isa;
};
//  Class是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
}
struct objc_object {
private:
    isa_t isa;
public:
    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);
}

(1)id类型实际上是objc_object类型的别称
(2)Class类型实际上是objc_class类型的别称
(3)objc_class是继承于objc_object
(4)描述了对象的结构,元类描述了的结构,根元类描述了元类的结构,根元类描述了自己的结构。
(5)所有衍生的对象或类,都是objc_object的子类,objc_objectsuperclassnil
如此一来:所有的对象(实例对象、类对象、元类对象)的查找方法的机制就完全统一
object_struct
实例对象、类对象、元类对象之间的关系图
oc_object
(1)Instance对象isa指向类对象类对象isa指向元类对象元类对象isa指向根元类对象根元类对象isa指向自己
(2)Root class (class)其实就是NSObject,其superclassnil
(3)所有的Meta classisa指针都指向Root class (meta)
(4)类对象和元类对象是唯一的,类的实例对象是不限的

四.认识isa_t

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
};

objc_object里面的isaisa_t类型。通过查看源码,我们可以知道isa_t是一个union联合体。
union叫共用体,又叫联合、联合体。“联合体”是一种特殊的类,也是一种构造类型的数据结构。在一个“联合体”内能够定义多种不同的数据类型。一个被说明为该“联合体”类型的变量中。同意装入该“联合体”所定义的不论什么一种数据。这些数据共享同一段内存,以达到节省空间的目的。

inline void
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

inline void
objc_object::initClassIsa(Class cls)
{
    if (DisableNonpointerIsa  ||  cls->instancesRequireRawIsa()) {
        initIsa(cls, false/*not nonpointer*/, false);
    } else {
        initIsa(cls, true/*nonpointer*/, false);
    }
}

inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
    ASSERT(!isTaggedPointer());
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());
        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

isa
ISA_MAGIC_VALUE = 0x000001a000000001ULL转换成二进制是11010000000000000000000000000000000000001,结构如下图:
isa_value

为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。对于 64 位程序,引入Tagged Pointer后,相关逻辑能减少一半的内存占用,以及 3 倍的访问速度提升,100 倍的创建、销毁速度提升。

正常情况下,如果这个整数只是一个NSInteger的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。如果没有Tagged Pointer对象,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumberNSDate一类的对象所占用的内存会翻倍。如下图所示:
Nono_Tagged_Pointer
苹果提出了Tagged Pointer对象。由于NSNumberNSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能表示的有符号整数就可以达到20多亿(注:2^31=2147483648,另外1位作为符号位),对于绝大多数情况都是可以处理的。所以,引入了Tagged Pointer对象之后,64位CPU下NSNumber的内存图变成了以下这样:
Tagged_Pointer

Tagged Pointer的引入也带来了问题,即Tagged Pointer因为并不是真正的对象,而是一个伪对象,所以你如果完全把它当成对象来使,可能会让它露马脚。比如我在 《Objective-C 对象模型及应用》 一文中就写道,所有对象都有 isa 指针,而Tagged Pointer其实是没有的,因为它不是真正的对象。

(1)has_assoc:对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存
(2)has_cxx_dtor:表示该对象是否有 C++ 或者 Objc 的析构器
(3)shiftcls:类的指针,arm64架构中有33位可以存储类指针。
源码中isa.shiftcls = (uintptr_t)cls >> 3;
将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。具体可以看从NSObject的初始化了解 isa这篇文章里面的shiftcls分析。
(4)magic:判断对象是否初始化完成,在arm64中0x16是调试器判断当前对象是真的对象还是没有初始化的空间。
(5)weakly_referenced:对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
(6)deallocating:对象是否正在释放内存
(7)has_sidetable_rc:判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。
(8)extra_rc:放该对象的引用计数值减一后的结果,对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc的值就为 9。
(9)ISA_MAGIC_MASKISA_MASK 分别是通过掩码的方式获取MAGIC值 和 isa类指针。

五.cache_t的具体实现

Cache的作用主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiverisa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。
objc_class中的cachecache_t类型,如下:
cache_t
(1)cache_t能缓存调用过的方法。
(2)cache_t的三个成员变量中,
_buckets的类型是struct bucket_t *,也就是指针数组,它表示一系列的哈希桶(已调用的方法的SELIMP就缓存在哈希桶中),一个桶可以缓存一个方法。
_mask的类型是mask_tmask_t在64位架构下就是uint32_t,长度为4个字节),它的值等于哈希桶的总数-1capacity - 1),侧面反映了哈希桶的总数。
_occupied的类型也是mask_t,它代表的是当前_buckets已缓存的方法数。

(3)当缓存的方法数到达临界点(桶总数的3/4)时,下次再缓存新的方法时,首先会丢弃旧的桶,同时开辟新的内存,也就是扩容(扩容后都是全新的桶,以后每个方法都要重新缓存的),然后再把新的方法缓存下来,此时_occupied1
当多个线程同时调用一个方法时,可分以下几种情况:
多线程读缓存:读缓存由汇编实现,无锁且高效,由于并没有改变_buckets_mask,所以并无安全隐患。
多线程写缓存:OC用了个全局的互斥锁(cacheUpdateLock.assertLocked())来保证不会出现写两次缓存的情况。
多线程读写缓存:OC使用了ldp汇编指令编译内存屏障技术内存垃圾回收技术等多种手段来解决多线程读写的无锁处理方案,既保证了安全,又提升了系统的性能。

更详细的分析可参考OC源码分析之方法的缓存原理

六.class_data_bits_t的具体实现

struct objc_class : objc_object {
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    class_rw_t *data() const {
        return bits.data();
    }
}
struct class_data_bits_t {
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值