OC 底层探索 03、对象本质 & isa结构

本文开始探索 OC 对象的本质是什么?

一、对象 

对象的本质 - 结构体

1、编译后的对象

1、我们在 main.m 文件中做简单代码如下:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

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

@implementation MyPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

clangmain.m 文件编译为 C++ 文件 main.cpp,命令如下:

我们可从生成的 .cpp 文件中找到关于 MyPerson 的编译后的结构:

 

MyPerson 编译成了 struct MyPerson_IMP {...} 

即:对象 --> 在底层编译成了结构体 struct

问:.cpp文件中第112268行  ‘struct NSObject_IMPL NSObject_IVARS;’ 是什么呢?

伪继承。在C++ 中结构体是可以继承的,在C中可以也可以伪继承,如上图代码:将继承结构体作为当前结构体的第一个元素。此时,结构体 MyPerson_IMP 拥有 结构体 NSObject_IMPL 的所有成员变量。

任何类里面都有 NSObject_IVARS ?--> isa

NSObject_IMPL 结构体:

 

struct NSObject_IMPL {
    Class isa;
};

 

2、同样在 .cpp 文件中也可以找到 MyPerson 的 name 属性

setter/getter 方法

objc_setProperty(),我们通过 objc 源码跟踪进去具体实现:objc_setProperty() --> reallySetProperty()

从源码可以看出,setter 方法的操作可以简单理解为就是:新值 retain旧值 release .但需要注意代码中逻辑判断,当 copy 等属性为true时是不同操作。详细讲解 链接.

在这里我们可以考虑,我们所有的对象的 setter 方法,都是经历一个 retain release 过程,完全是统一的过程。那么这里我们就考虑到了将其进行类似工厂类的封装,所有的对象无需每个都分开处理,我们把它扔进来即可。即:无论外部对象如何变化,每个对象的 setter 方法都会走到  objc_setProperty() --> 一种思路。

Clang 

Clang 是什么?

Clang 是一个C语言、C++、Object-C 语言的轻量化编译器。它是由苹果主导编写 基于 LLVM 的 C/C++/OC 编译器。早起苹果编译器是GCC。

Clang 常用命令

// 把目标文件 main.m 编译成c++文件
clang -rewrite-objc main.m -o main.cpp 

// 若 编译时 UIKit 报错 --> 找到路径
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m

// `xcode`安装的时候顺带安装了`xcrun`命令,`xcrun`命令在`clang`的基础上进行了 一些封装,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp // (模拟器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp // (真机)

二、联合体位域

结构体(struct):结构体中所有变量是共存的,且大小至少是所有变量所占内存的的和。

  优点:变量共存,有容乃大;

  缺点:对内存的分配是粗略的,存在空间浪费的。

联合体(union):联合体中,变量是互斥关系,即同一时间只能有一个变量。

  优点:内存使用更精细灵活,节省内存空间;

  缺点:互斥性,只有一个变量存在。

示例:

位域:变量每一位所代表的信息。

一字节的分配

联合体位域大大的优化节省了内存空间

三、isa 结构

isa 指针 8 字节64 位,可存储信息:264 ,完全足够我们的地址存储了。

通过之前 alloc 流程探索我们可知,对象与 isa 关联是 initInstanceIsa() 方法,我们通过源码跟踪进去:

initInstanceIsa --> initIsa() --> isa_t

联合体 isa_t :

union isa_t {
  // 2个初始化 isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits;// cls bits 两个互斥的变量
#if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; #endif };

ISA_BITFIELD

- 手机端:

- Mac OS:

以 arm64 为例,位域代表含义:

nonpointer -- 占据第0位,是否对 isa 开启指针优化。0:纯isa 指针;1:不只是类对象地址,isa 中包含了类信息、对象的引用计数等。一般我建的类都是 nopointer 的。

shiftcls -- 占据第3~35位,为存储类指针的值。开启指针优化的情况下,在 arm64 架构中有33 位用来存类指针。

has_sidetable_rc -- 为 1 时,则表明有外部挂的引用计数 sideTable,下面的 extra_rc 存引用计数不够用了。

extra_rc -- 引用计数值-1的 值。例:对象的引用计数为10,则 extra_rc = 9;若 extra_rc 不足以保存引用计数,则上面的 has_sidetable_rc 为true,使用sideTable 管理。

2、验证 isa 关联

1:源码跟踪 验证 (本质也是位移操作)

执行可编译源码工程进入 initIsa() 函数,cls 为 MyPerson,nonpointer = ture:

newisa.bits = ISA_MAGIC_VALUE;// 赋初值 

migic = 59 验证:

打开系统编程计算器:

二进制 110111 -->  10进制 59

执行 newisa.shiftcls = (uintptr_t)cls >> 3;

cls --> MyPerson --> 强转为 uintptr_t 类型 --> 值:4294975728,为何?

计算机是不识别字符串的,需要将其转成可识别的二进制的 010101......的机器指令。

右移3位 >>3: shiftcls 从第3号位开始,之前 0 1 2位有三个变量,我们取 shiftcls 时不能受其影响,故做 >>3 抹零操作。 

继续运行,回到 _class_createInstanceFromZone() 函数。

我们继续验证。

isa 地址 & ISA_MASK

isa 地址 & ISA_MASK --> MyPerson

 

提一下 ISA_MASK:0x00007ffffffffff8ULL --> 第3~47 位为1,&运算即两头抹零。

2:runtime - object_getClass() 验证

跟踪进入 object_getClass() 方法 --> getIsa() --> ISA(): 

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;#else
    return (Class)(isa.bits & ISA_MASK);// shiftcls
#endif
}

3:手动位运算验证

如下图:

 

补图中漏写文字:$11 = 4294975728 10进制 --> 16进制 0x00000001000020f0

p/u cls
(Class) $15 = 4294975728 MyPerson // 等于它

验证 over。 

NSObject 中 Class isa:

NSObject 中 class 对象,我们拿到的是 class 对象,其实是通过 isa_t 拿到的,只是做了处理转化成了class。NSObject 中代码:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

拿到 class 的过程:NSObject --> Class isa --> object_getclass --> ISA():

return 的是有做 Class 强转的 -->  NSObject 中 Class isa OBJC_ISA_AVAILABILITY;

 

posted @ 2020-09-13 23:59  张张_z  阅读(222)  评论(0编辑  收藏  举报