对象的底层探索(下)

影响对象内存的因素

从原来的分析我们得出,对象的本质是isa + 成员变量的值,那什么因素会影响对象的内存呢?我们先从一段代码开始:

@interface LLPerson : NSObject

// isa --- 8
@property (nonatomic, strong) NSString *name;  // 8
@property (nonatomic, strong) NSString *hobby; // 8
@property (nonatomic, assign) int age;         // 4
@property (nonatomic, assign) double height;   // 8
@property (nonatomic, assign) short number;    // 2

@end

LLPerson *p = [LLPerson alloc];
p.name = @"LL";
p.hobby = @"boy";
p.height = 1.85;
p.age = 24;
p.number = 20;

NSLog(@"----------%lu", malloc_size((__bridge const void *)(p)));

在这里插入图片描述
LLPerson类中,isa指针8个字节,对象的内存大小打印可得4816字节对齐)。当我们为LLPerson类添加实例方法和类方法时:

@implementation LLPerson

- (void)test {
    
}
+ (void)test {
    
}

@end

打印还是48字节,所以影响对象内存大小的因素是成员变量。那么,在成员变量的值在内存中是怎么排布的呢?是我们编写属性的顺序还是我们赋值的顺序呢?我们可以通过终端命令打印获取内存信息:
在这里插入图片描述
我们通过打印可以看出,内存的排布不是根据我们编写属性的顺序,也不是是我们赋值的顺序,而是系统会根据成员变量各个的大小,进行优化,比如将4个字节的age2个字节的number放到一起。

对象是怎么关联类型的?

那么,对象是怎么关联上类型的?
我们在原来的的结论中知道,_class_createInstanceFromZone中返回了obj,所以我们可以在_class_createInstanceFromZone方法中,查看是怎么关联类型的。
在这里插入图片描述
我们通过打断点,po obj打印,可以得到对象的类型关联是在obj->initIsa(cls)中的,那我们可以看一下initIsa这个方法中是怎么做的。

// objc-object.h
#if !SUPPORT_INDEXED_ISA && !ISA_HAS_CXX_DTOR_BIT
#define UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT __attribute__((unused))
#else
#define UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT
#endif

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); // 是否优化
    
    isa_t newisa(0);  // 初始化 isa_t

    if (!nonpointer) {  // 没有优化过的isa 直接赋值class 原来的时候
        newisa.setClass(cls, this);
    } else {    // 优化过的 现在基本上都是优化过的
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA   // watch独有
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE         isa.magic是ISA_MAGIC_VALUE的一部分
        // isa.nonpointer is part of ISA_MAGIC_VALUE    isa.nonpointer是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.magic是ISA_MAGIC_VALUE的一部分
        // isa.nonpointer is part of ISA_MAGIC_VALUE    isa.nonpointer是ISA_MAGIC_VALUE的一部分
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;  // 引用计数器 alloc时直接引用计数器 1
    }

    // 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  //固定使用原子在这里保证单存储和保证内存顺序W.R.T.类索引表
    // ...but not too atomic because we don't want to hurt instantiation //但不要太原子化,因为我们不想破坏实例化
    isa = newisa; // 赋值isa
}

从方法中,我们可以看到isa指针就是isa_t这个结构的,在方法中进行isa_t的赋值,同时我们可以看到alloc对象后,引用计数器为1
接下来我们研究isa_t这个结构:

// objc-private.h
#include "isa.h"

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

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private. //访问类需要自定义的ptrauth操作,因此通过将此设置为私有来强制客户端通过setClass/getClass。
    Class cls;

public:
#if defined(ISA_BITFIELD)  // isa的结构
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {  //判断是都被在销毁时,判断的是引用计数器和sidetable都为0
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() { // 对象销毁时,引用计数器和sidetable都会置为0
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

从这个结构中我们可以看到,isa_tunion联合体来定义的,兼容是否是NonpointerIsa,当然,我们只研究NonpointerIsa,即ISA_BITFIELD这个结构。

// isa.h
# if __arm64__  // arm64架构下
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e. // ARM64模拟器有更大的地址空间,所以即使模拟器是为ARM64-not-e构建的,也要使用ARM64e方案
// __has_feature(ptrauth_calls) 是判断编译器是否支持指针身份验证功能
// ptrauth_calls 指针身份验证,针对arm64e架构;使用Apple A12或更高版本A系列处理器的设备(如iPhone XS、iPhone XS Max和iPhone XR或更新的设备)支持arm64e架构
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR  //arm64e 或者 模拟器
#     define ISA_MASK        0x007ffffffffffff8ULL     // 获取isa中指向class的指针掩码
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL  // 获取isa中指向class的指针掩码
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__   // x86架构
#   define ISA_MASK        0x00007ffffffffff8ULL    // 获取isa中指向class的指针掩码
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   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 unused            : 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_BITFIELD是使用位域的形式来保存信息的,isa8字节,8 * 8 = 64位来存储信息,并且是分平台架构的,下面我们值分析真机(iphone11)下的结构即:

#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;     // 表示是否对 isa 指针开启指针优化 	0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等                                   \
        uintptr_t has_assoc         : 1;    // 关联对象标志位,0没有,1存在                                    \
        uintptr_t weakly_referenced : 1;    // 标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。                                   \
        uintptr_t shiftcls_and_sig  : 52;   // 存储类指针的值。开启指针优化的情况下,在 arm64e 架构中有 52 位用来存储类指针。                                    \
        uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 来存储引用计数                                     \
        uintptr_t extra_rc          : 8   // 表示该对象的引用计数值

在这里插入图片描述
我们通过内存平移获得的地址和LLPerson的地址是一致的,当然我们咳哟使用define ISA_MASK 0x007ffffffffffff8ULL掩码来获取这个值,跟内存平移是一致的。
在这里插入图片描述
我们可以看到对象是通过isa来关联类型的,同时在isa中我们也可以看到对象的一些其他信息,比如引用计数器。

关于new方法

最后,我们看一下new方法,可以从源码中搜索:

// NSObject.mm
// callAlloc 实际上是 alloc + init
+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

其实new 方法就是alloc + init

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值