OC对象的本质与initIsa

先上结论:OC对象的本质是结构体,结构体内部是一个指针;

1. 如何探究

通常我们编写的OC代码,其底层都是 C/C++代码,所以 OC对象都会转换成 C/C++的某种数据结构。

代码流程.png

那么我们可以通过clang,将相关的OC文件编译成C/C++文件:

clang -rewrite-objc main.m -o main.cpp

2. 源码分析

main.m文件源码:

@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation Student
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *s = [[Student alloc] init];
        s.name = @"Tom";
        NSLog(@"%@", s.name);
    }
    return 0;
}

打开clang编译好的文件,搜索我们自定的类Student,不难发现我们的Student类最终被编译成了一个叫objc_object结构体,而Student_IMPL则保存着一些相关的类信息,比如isa指针,成员变量name等。

typedef struct objc_object Student;

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
}

往下翻翻,我们还能发现成员变量nameset方法和get方法,还有一些隐藏参数selfSEL_cmd

static NSString * _I_Student_name(Student * self, SEL _cmd) {
    return (*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name));
}

static void _I_Student_setName_(Student * self, SEL _cmd, NSString *name) { 
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Student, _name), (id)name, 0, 1); 
}

以及还能发现struct _protocol_t, struct _category_t等等一些信息。

那么成员变量NSObject_IVARS代表什么呢? 其实对应的就是我们的isa指针。经阅读相关的文章得知:

在早期的32位系统中isa就是一个单一的指针,用于存储当前对象的类或者类的元类。 但是在64位操作系统上,用一个8字节指针的长度只存储一个对象地址显然是浪费的(操作系统只有一部分地址是可用于存储对象地址的空间),所以apple对这个isa指针进行了优化。

typedef struct objc_object NSObject;

struct NSObject_IMPL {
    Class isa;
}

Class就是结构体objc_class的指针。

typedef struct objc_class *Class;

同时还发现

typedef struct objc_object *id;

这样就解释的清:用id定义变量时,为何不加*。所以id可以指向任何一个合法对象。

3. isa_t分析

通过我的前几篇文章,类clsisa进行绑定是通过obj->initInstanceIsa(cls, hasCxxDtor)来实现的,那我们来看下initIsa函数的底层实现:


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

    initIsa(cls, true, hasCxxDtor);
}


objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ...
    isa_t newisa(0);

    if (!nonpointer) { 
        ... 
    } else {
        ...
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;

        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }
    ...
}

那么isa_t是个啥玩意呢?点进去查看我们发现是一个联合体

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

    uintptr_t bits;

private:
    Class cls;

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

    ...
#endif
    ...
};

上文中提到过,apple优化了64位系统下isa指针所占的空间,那么是如何优化的呢?增加了uintptr_t(unsigned long)类型的变量bits,由于使用的是联合体+位域,所以该结构只占用一个指针的空间。当使用bits变量进行存储时,利用位域结构将变量的各个位进行拆分赋予不同的含义,充分利用了内存空间。关于结构体、联合体与位域的解释可以查看这篇文章,我们再这里不过多讨论。

这样利用位域使得变量内不仅仅保存了指针值,同时还保存了很多有用的信息。

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     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
#     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__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   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

以主流的arm64为例,

  • nonpointer 占用1bit,标识是否开启isa优化.如果是一个指针值该位为0,则表示当前结构的值只是一个指针没有保存其他信息;如果为1,则表示当前结构不是指针,而是一个包含了其他信息的位域结构
  • has_assoc 当前对象是否使用objc_setAssociatedObject动态绑定了额外的属性
  • has_cxx_dtor 是否含有C++或者OC的析构函数,不包含析构函数时对象释放速度会更快
  • shiftcls 这个值相当于早期实现中的isa指针,是真实的指针值,在arm64处理器上只占据33位,可见其实在内存中可以用来存储对象指针的空间是很有限的
  • magic 用于判断对象是否已经完成了初始化,在arm640x16是调试器判断当前对象是真的对象还是没有初始化的空间(在x86_64中该值为0x3b)
  • weakly_referenced 是否是弱引用对象
  • deallocating 对象是否正在执行析构函数(是否在释放内存)
  • has_sidetable_rc 判断是否需要用sidetable去处理引用计数
  • extra_rc 存储该对象的引用计数值减一后的结果. 当对象的引用计数使用extra_rc足以存储时
  • unused 对象是否被释放
  • has_sidetable_rc=0 当对象的引用计数使用extra_rc不能存储时has_sidetable_rc=1.可见对象的引用计数主要存储在两个地方:如果isaextra_rc足以存储则存储在isa的位域中;如果isa位域不足以存储,就会使用sidetable去存储。

根据ISA_MASK还原类信息还未研究透彻,请等待后续更新

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值