文章目录

  • 一、clang初探
  • 1.clang简介
  • 2.使用clang生成底层实现源码
  • 3.clang底层初探
  • 二、malloc底层原理
  • 三、联合体和位域
  • 四、initIsa
  • 五、nonpointer isa
  • 六、isa推导class
  • 七、isa的位运算
  • 八、总结


一、clang初探

1.clang简介

  • clang是一个C语言、C++Objective-C语言的轻量级编译器。源代码发布于BSD协议下;
  • Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
  • Clang是一个由Apple主导编写,基于LLVMC/C++/Objective-C编译器
    2013年4月,Clang已经全面支持C++11标准,并开始实现C++1y特性(也就是C++14,这是C++的下一个更新版本)。Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
  • Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/Objective-C++编译器。它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容,包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载(通过__attribute_((overloadable))来修饰函数),其目标(之一)就是超越GCC。

2.使用clang生成底层实现源码

  • 把目标文件main.m编译成main.cpp C++文件
clang -rewrite-objc main.m -o main.cpp
  • 如果导入了UIKit库、则需要添加附带参数
clang -rewrite-objc -fobjc-arc -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.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

3.clang底层初探

  • 创建macOS - Command Line Tools工程、编写HSPerson类、
#import <Foundation/Foundation.h>
@interface HSPerson : NSObject
@end
@implementation HSPerson
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 查看clang后的编译底层源码
clang -rewrite-objc main.m -o main.cpp && open main.cpp
  • 首先在main.cpp文件中查找我们的 HSPerson、
#ifndef _REWRITER_typedef_HSPerson
#define _REWRITER_typedef_HSPerson
typedef struct objc_object HSPerson;
typedef struct {} _objc_exc_HSPerson;
#endif

struct HSPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};
  • 底层实现可以看到、对象在底层的本质就是一个结构体 struct
struct HSPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};
  • 于此同时、在HSPerson类中增加一个属性
@interface HSPerson : NSObject
@property (nonatomic,strong)NSString *hsName;
@end
  • 再次生成底层实现代码、可以看到增加了部分代码;如此就鲜明的表现了HSPerson类在底层确实是一个结构体
extern "C" unsigned long OBJC_IVAR_$_HSPerson$_hsName;
struct HSPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_hsName;
};
  • 下面我们看结构体中嵌套的结构体 NSObject_IMPL
  • 在OC上层中我们的HSPerson是继承于NSObject的、
  • 然而在底层实现中、结构体不存在继承关系、只能理解为伪继承关系。
  • 那么NSObject_IVARS是什么呢? 其中的关键字为IVARS、是不是isa呢?
    查找NSObject_IMPL 的定义
struct NSObject_IMPL {
	Class isa;
};
  • 一目明了、说明 HSPerson类底层实现中包含了一个 isa变量。
  • 回到底层HSPerson定义处
typedef struct objc_object HSPerson;
  • 在OC层面HSPerson继承于NSObject、而NSObject在CPP底层则体现为 objc_object
typedef struct objc_class *Class;
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
  • 同理 往上瞄一眼、
  • OC层面的Class在CPP底层是以什么形式存在呢?
  • 是不是就是objc_class
typedef struct objc_class *Class;
struct objc_class {
    Class _Nonnull isa __attribute__((deprecated));
} __attribute__((unavailable));
  • Class是一个结构体指针、这也就是为什么我们在OC上层使用Class的时候不需要 * 的原因
  • 与此同时
    id类型在底层是什么表现形式呢?
typedef struct objc_object *id;
  • 这就是申明一个对象时、id类型不加 * 的原因、因为本身就是指针类型。
  • 继续看HSPerson相关的内容
// @implementation HSPerson
static NSString * _I_HSPerson_hsName(HSPerson * self, SEL _cmd) {
    //(char *)self指针地址 + OBJC_IVAR_$_HSPerson$_hsName偏移指针地址
    //对两者所取到的值进行类型强转拿到(NSString **)值 
    //返回(NSString **)值的指针地址
 	return (*(NSString **)((char *)self + OBJC_IVAR_$_HSPerson$_hsName)); 
 }
static void _I_HSPerson_setHsName_(HSPerson * self, SEL _cmd, NSString *hsName) { 
    (*(NSString **)((char *)self + OBJC_IVAR_$_HSPerson$_hsName)) = hsName; 
}
// @end
  • 这里将其格式化展开,是不是明显的就是我们hsName属性的getter方法和setter方法在底层的实现
  • 我们在OC上层并没有看到getter方法中 (HSPerson * self, SEL _cmd) 参数和setter方法中的(HSPerson * self, SEL _cmd, NSString *hsName)参数;这就是我们的隐藏参数;

下边的内容我们先混个脸熟、以后再深入探索

struct _objc_method {
	struct objc_selector * _cmd;
	const char *method_type;
	void  *_imp;
};

struct _protocol_t {
	void * isa;  // NULL
	const char *protocol_name;
	const struct _protocol_list_t * protocol_list; // super protocols
	const struct method_list_t *instance_methods;
	const struct method_list_t *class_methods;
	const struct method_list_t *optionalInstanceMethods;
	const struct method_list_t *optionalClassMethods;
	const struct _prop_list_t * properties;
	const unsigned int size;  // sizeof(struct _protocol_t)
	const unsigned int flags;  // = 0
	const char ** extendedMethodTypes;
};
struct _ivar_t {
	unsigned long int *offset;  // pointer to ivar offset location
	const char *name;
	const char *type;
	unsigned int alignment;
	unsigned int  size;
};
struct _class_ro_t {
	unsigned int flags;
	unsigned int instanceStart;
	unsigned int instanceSize;
	unsigned int reserved;
	const unsigned char *ivarLayout;
	const char *name;
	const struct _method_list_t *baseMethods;
	const struct _objc_protocol_list *baseProtocols;
	const struct _ivar_list_t *ivars;
	const unsigned char *weakIvarLayout;
	const struct _prop_list_t *properties;
};
struct _class_t {
	struct _class_t *isa;
	struct _class_t *superclass;
	void *cache;
	void *vtable;
	struct _class_ro_t *ro;
};
struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};

二、malloc底层原理

  • 下载可调试源码、创建工程
  • maloc切入点
obj = (id)calloc(1, size);
  • 难点分析文档、查看掘金文档、暂时不想分析。

三、联合体和位域

  • 我们在探索创建对象的过程中、最终会来到 _class_createInstanceFromZone方法、需要详细探索的点还有 initInstanceIsainitIsa、可以看到initInstanceIsa函数中除去两个断言、也都是调用了initIsa、区别在于initIsa参数中 nonpointer 赋值是否为true。
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());
    initIsa(cls, true, hasCxxDtor);
}
  • 在探索 initIsa之前、我们需要先了解两个概念、联合体和位域

案例分析:先查看下两个代表汽车前进方向的结构体

struct HSCar1{
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
}car1;
struct HSCar2{
    BOOL front :1;
    BOOL back  :1;
    BOOL left  :1;
    BOOL right :1;
}car2;
  • 上述HSCar1结构体占用的内存是多少?
  • 经过前面的学习、我们可以看出car1占用内存为4字节
  • 经过sizeof输出验证结果
NSLog(@"%lu",sizeof(hsChar1));//4
  • 对于我们想要前进的方向来说、car1中假设我们想要往前、那么front的值也就只有 0和1。对于car1来说占用了 4字节x8= 32位;但是对于数据存储来说我们并不需要32位来存储四个BOOL类型的数据、
0000 0000 0000 0000 0000 0000 0000 1111
  • 假设四个BOOL类型数据全为true、也只占用了4位。实际上只需要4位就够了、也就是半个字节、实际上并没有半个字节、最小一个字节单位、最优解也就是一个字节就够了。因此实际上这就造成了资源的浪费、浪费了三倍的空间。
  • 此时此刻为了节省资源空间、引入位域这个概念、也就是HSCar2的形式、冒号后表示设置的位数;front:1; 表示front占用了1位。
  • 再输出car2的占用内存大小、得到其占用了一个字节、这就大大减少了资源的浪费。
NSLog(@"%lu",sizeof(car2));//1
  • 那么假设我们设置其中所占用的位数增大、会不会增大占用的内存呢?
struct HSCar2{
    BOOL front:1;
    BOOL back:2;
    BOOL left:6;
    BOOL right:1;
}car2;
  • 此时car2整体占用了10位、那么它所占用的内存空间有多大?讲道理的来说它应该占用2个字节、假设如下表示其占位
// 0000 00	    0   000000  00    0
// 空位 空位  right left   back front
NSLog(@"%lu",sizeof(car2));//2
  • 然而当一辆车运行的过程中、向前的同时是不能向后的、所以就产生了互斥效应。
  • 关于互斥、我们来看引入另一个案例
struct HSTeacher1{
    char *name;
    int      age;
    double  height;
}teacher1;
  • 当我们对teacher1进行赋值的时候、依次可以看到在未赋值的情况下、默认值都是正常值
int main(int argc, const char * argv[]) {
    teacher1.name = "Holothurian";//断点
    teacher1.age  = 18;		//断点
    NSLog(@"%lu",sizeof(teacher1));//断点
    return 0;
}
(lldb) p teacher1
(HSTeacher1) $0 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p teacher1
(HSTeacher1) $1 = (name = "Holothurian", age = 0, height = 0)
(lldb) p teacher1
(HSTeacher1) $2 = (name = "Holothurian", age = 18, height = 0)
  • 此时此刻、我们想要看到的互斥现象并没有产生、这就需要引入一个叫做联合体的概念了、下面查看联合体的案例
union HSTeacher2{
    char *name;
    int      age;
    double  height;
};
  • 在main函数中初始化并且赋值、打上断点、依次输出结果
union HSTeacher2    teacher2;
teacher2.name = "Holothurian";
teacher2.age  = 18;
  • 可以看到在初次赋值后、就出现了脏数据、脏内存、表明了在同一时间只有一个成员能够被使用、这就体现了互斥的概念。
(lldb) p teacher2
(HSTeacher2) $0 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p teacher2
(HSTeacher2) $1 = (name = "Holothurian", age = 15970, height = 2.1220036811936364E-314)
(lldb) p teacher2
(HSTeacher2) $2 = (name = "", age = 18, height = 2.1219957998584539E-314)
(lldb)

综上总结

  • 结构体struct中所有变量遵循“共存”原则、优点是有容乃大、全面;缺点是struct内存空间的分配是粗放的,不管用不用,全部分配;
  • 联合体union中各变量是“互斥”的,缺点是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间;

四、initIsa

  • 接下来我们查看initIsainitIsa使内存申请的结构体指针和Class绑定在一起。去除ASSERT部分。
inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    isa_t newisa(0);
    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
#if SUPPORT_INDEXED_ISA
        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 = newisa;
}
  • 从函数最后可以看到、所有的操作都是为了最后的赋值isa = newisa;而newisa的来源在于 isa_t;下面我们来查看 isa_t 的结构
union isa_t {
    //构造方法
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    //属性bits
    uintptr_t bits;
private:
    //访问类需要自定义 ptrauth 操作,因此强制客户端使用私有成员来操作 setClass/getClass
    Class cls;
public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif
    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

五、nonpointer isa

  • 上面的结构可以看出isa_t本质是个union联合体/共用体; 我们普遍在表现类的地址的时候经常看到一个 nonpointer isa;那么什么是 nonpointer isa呢?
  • 类也是一个对象、也就是一个指针、类上的很多信息都是可以存储的;一个指针具有8字节、也就是8x8 = 64位;
  • 如果64位只是存储一个指针,空间就大大的浪费了、所以我们需要存储一些与类息息相关的信息来优化这片存储空间。
  • 比如说:是否正在释放、引用计数、是否有weak弱饮用、是否存在关联对象、是否有析构函数等
  • 所以出现了nonpointer isa概念、意味着我们可以将类相关的信息存在64位信息中,不再是一个简单的指针地址。
  • 接下来我们需要看isa_t中存储的信息,去验证存储的信息、怎么看呢?
  • 经过前面的铺垫、其实我们需要看的是位域部分的设置、也就是ISA_BITFIELD中的信息了
struct {
  ISA_BITFIELD;  // defined in isa.h
};
  • 对于x86_64来说、我们可以看到其中的位域设置信息如下
# 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
  • 对于arm64结构来说、其中的位域信息如下
# if __arm64__
// ARM64 模拟器具有更大的地址空间,因此即使模拟器是为 ARM64-not-e 构建的,也要使用 ARM64e 方案。
#   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;  //是否对指针开启指针优化、0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。                                     \
        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;  //是否对指针开启指针优化、0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。                                     
        uintptr_t has_assoc         : 1;  //关联对象标志位,0没有,1存在                                    
        uintptr_t has_cxx_dtor      : 1;  //是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。                                    
        uintptr_t shiftcls          : 33; //存储类指针的地址值。/*MACH_VM_MAX_ADDRESS 0x1000000000*/ 
        uintptr_t magic             : 6;  //用于调试器判断当前对象是真的对象还是没有初始化的空间。                                    
        uintptr_t weakly_referenced : 1;  //是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。                                    
        uintptr_t unused            : 1;  //是否正在释放对象。                                     
        uintptr_t has_sidetable_rc  : 1;  //如果对象引用计数大于10时,则需要借用该变量存储进位                                     
        uintptr_t extra_rc          : 19  //当表示该对象的引用计数值,实际上是引用计数减1。
        //如果对象的引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用has_sidetable_rc。
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

上述位域中的关键信息及对应的解释

  • nonpointer: 表示是否对指针开启指针优化、0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。
  • has_assoc: 关联对象标志位,0没有,1存在
  • has_cxx_dtor: 该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
  • shiftcls: 存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针。
  • magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间。
  • weakly_referenced:标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
  • deallocating: 标志对象是否正在释放对象。
  • has_sidetable_rc:当对象引用计数大于10时,则需要借用该变量存储进位
  • extra_rc: 当表示该对象的引用计数值,实际上是引用计数减1。如果对象的引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用has_sidetable_rc

六、isa推导class

  • 对象指针占用8字节、那么我们来调试查看对象地址中所存储的信息
  • 创建Command Line Tools 工程、编写一个HSPerson类、然后在main函数中调用
  • 此时此刻我们可以看到格式化输出person对象的地址信息;其中 0x001d8001000082dd 表示最高两位为00、那么存储信息则差距很大
(lldb) p 0x1000000000000000 - 0x00100000000000000
(long) $1 = 1080863910568919040
(lldb) p/x  0x1000000000000000 - 0x00100000000000000
(long) $2 = 0x0f00000000000000
(lldb) p/t 0x001d8001000082dd
(long) $3 = 0b0000000000011101100000000000000100000000000000001000001011011101
(lldb)
  • x/4gx 输出空间存储信息
  • p/x 打印指针首地址
  • p/t 按照二进制打印地址存储信息
  • p *$0 获取$0指针首地址上的信息
  • 前面 x/4gx person的输出结果来看、意味着我们关联到了类的信息;通过 p/x HSPerson.class 可以得到类的地址
(lldb) x/4gx person
0x10058d530: 0x001d8001000082dd 0x0000000000000000
0x10058d540: 0x0000000000000000 0x0000000000000000
(lldb) p/x HSPerson.class
(Class) $4 = 0x00000001000082d8 HSPerson
  • 那么类的地址是怎么和我们的对象地址进行关联的呢?
  • 下面我们引入ISA_MASK掩码的概念:它就相当于一个面具、用来获取对象地址上类的信息;
  • 通过isa_t的位域部分、我们知道shiftcls是我们想要获取的类的存储信息的地方、此时运行在x86_64架构下、看看掩码所在的二进制位、
  • 从第3位到第46位,44位二进制位上都是1,都是掩码所在位置;而ISA_BITFIELD位域信息中所占的位数是44位、其他信息会按照顺序依次排放;为了取出对象地址上的shiftcls信息、是不是只需要 & ISA_MASK 就可以了? 这也就是面具存在的意义。
  • 0x001d8001000082dd为person对象的地址、0x00000001000082d8为HSPerson类的信息地址; ho address为获取堆栈MachO地址信息插件
(lldb) p/x 0x001d8001000082dd & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x00000001000082d8
(lldb) po 0x00000001000082d8
HSPerson
(lldb) ho address 0x00000001000082d8
address : 0x00000001000082d8, 28HSPerson <+0> ,(OBJC_CLASS_$_HSPerson),External: YES HSPerson.__DATA.__objc_data +28
(lldb)
  • 综上:对象地址 & isa掩码就可以得到类的信息
  • 回到objc_object::initIsa 中,如果当前对象没有开启指针优化:不是nonpointer isa,则直接setClass赋值操作。如果开启了指针优化、则对其进行了逐个赋值操作
  • 其中setClass(cls,this)做了什么操作呢?
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj){
    //匹配条件 
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#   if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
    // 使用原生指针
    uintptr_t signedCls = (uintptr_t)newCls;
#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
    // 我们只对 Swift 类进行签名。 非Swift类只使用原始指针
    uintptr_t signedCls = (uintptr_t)newCls;
    if (newCls->isSwiftStable())
        signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
    // 我们正在签署一切
    uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
#   else
#       error Unknown isa signing mode.
#   endif
    //签名后的类进行右移3位操作
    shiftcls_and_sig = signedCls >> 3;
#elif SUPPORT_INDEXED_ISA
    //Indexed isa 仅使用此方法来设置原始指针类。设置索引类是单独处理的。
    cls = newCls;
#else //非指针优化 isa,没有 ptrauth、直接右移3位操作
    shiftcls = (uintptr_t)newCls >> 3;
#endif
}

七、isa的位运算

  • 经过前面的推导、实际上在不知道掩码的情况下、我们想要获取0x001d8001000082dd 对象地址上的类信息、只需要进行位运算操作即可
  • 以x86_64架构为例、64位信息中 中间44位为类信息、总64位信息结构为 3 + 44 + 17 = 64.
  • 只需要我们右移3位抹零低三位、再回到原位置、左移17位抹零高17位、再回到原位置即可
(lldb) p/x  0x001d8001000082dd >> 3 << 3 << 17 >> 17 
(long) $18 = 0x00000001000082d8
(lldb) po 0x00000001000082d8
HSPerson
  • 也可以先抹零高17位、再抹零低3位;连贯起来也就是左移17位、右移20位、左移3位归位;
(lldb) p/x 0x001d8001000082dd << 17 >> 20 << 3
(long) $20 = 0x00000001000082d8
(lldb) po $20
HSPerson

八、总结

  • 通过clang把目标文件编译成底层代码
  • main.m编译成main.cpp C++文件
clang -rewrite-objc main.m -o main.cpp
  • 如果导入了UIKit库、则需要添加附带参数
clang -rewrite-objc -fobjc-arc -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
  • cpp底层通过self+内存平移来获取setter方法和getter方法
  • 对象和isa的关系:对象底层含有isa指针、isa指向对象的存储信息
  • 联合体和位域:
  • 结构体struct中所有变量遵循“共存”原则、优点是有容乃大、全面;缺点是struct内存空间的分配是粗放的,不管用不用,全部分配;
  • 联合体union中各变量是“互斥”的,缺点是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间;
  • nonpointer isa:如果开启了指针优化、可以将类相关的信息存在对象的指针64位信息中,不再是一个简单的指针地址。ISA_BITFIELD中的位域位数将成为探索其中信息的关键点
  • isa推导class
  • 对象地址 & isa掩码就可以得到类的信息
  • 对象地址经过位平移操作可以得到类信息