联合体
联合体(共用体):一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。
- 所有成员占用同一段内存,修改一个成员会影响其余所有成员。
共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。 - 各变量是
互斥的——缺点就是不够包容; 但优点是内存使用更为精细灵活,也节省了内存空间。 - 共用体占用的内存等于
最长的成员占用的内存。
Clang
-
Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器 -
作用:借助
Clang可以将oc文件输出成C++文件,方便探究其底层的一些结构、逻辑、底层的实现原理等。
简单的使用命令
//把目标文件编译成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 (手机)
接下来借助Clang来简单的探索一下OC的对象。
oc对象探索
- 准备
oc对象LGPerson,并添加属性name。此时LGPeson应该有name和isa两个成员。
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
@property (copy, nonatomic) NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
- 转化输出
C++文件,找到LGPerson对象
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
// @property (copy, nonatomic) NSString *name;
/* @end */
// @implementation LGPerson
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }
// @end
- 从输出的
C++文件可以看到LGPerson是objc_object的结构体。
可以看到定义的属性_name,但是没有发现isa。反而有一个struct NSObject_IMPL NSObject_IVARS;。
在c++文件中搜索NSObject_IMPL {会发现缺少的isa在里面。但是却变成了Class类型。
struct NSObject_IMPL {
Class isa;
};
我们在探索alloc &init时,有initInstanceIsa是将isa与cls相关联。那么应该是在关联这里做什么。下面进入源码探索一下关联的操作。
isa关联cls
-
isa和cls的源码关联操作
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}

可以看到isa和cls的关联,其实就是将cls向右移动了3位,赋值保存在isa的shiftcls的区域中。因此LGPerson其实就保存在isa中。
那么通过object_getClass获取Class时,源码底层肯定是从isa中通过某种操作取出我们需要的Class。
进入object_getClass源码

可以发现object_getClass方法就是获取isa。

最终发现是isa的bits成员或者&ISA_MASK的结果在转成Class返回。
isa是如何存储cls
-
isa类型是isa_t是一个联合体(union)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits ,两者是互斥关系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
从isa_t的定义有Class cls和一个uintptr_t bits互斥的成员及结构体中宏定义的位域ISA_BITFIELD

-
nonpointer:表示是否对isa指针开启指针优化
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
arm64下ISA_BITFIELD位域的存储的信息

位域中shiftcls位置的33存储类的指针的值。那么取出isa存储的位域中的shiftcls的值,应该是我们的存储的Class。
x86_64下存储cls的流程
- 断点
cls为LGPerson时进入isa源码
isa源码
断点newisa的初始化isa_t newisa(0);赋初值的操作newisa.bits = ISA_MAGIC_VALUE;分别输出:
isa初始化、赋初值
从结果可以看到cls和bits赋值后,位域中存储的信息,也发生了变化。nonpointer代表对isa开启指针优化。magic值成了59。因为这里只是进行了赋初值那么这59肯定来自初始值ISA_MAGIC_VALUE。
从x86_64结构图中看magic是从47位开始的占6位。将初始值ISA_MAGIC_VALUE输出二进制进行查看。看到从magic位置的二进制111011是十进制的59。

-
cls的赋值newisa.shiftcls = (uintptr_t)cls >> 3;
将cls转化成uintptr_t右移3位存储在shiftcls区域。
存储cls
了解了cls的存储的位置及操作。那么通过存储的逆向操作应该能从isa中取出LGPerson。
- 从
isa中获取LGPerson


通过反向对isa的反向操作确实获得了LGPerson。
但是通过object_getClass获取Class时是isa.bits& ISA_MASK操作。
return (Class)(isa.bits & ISA_MASK);
ISA_MASK = 0x00007ffffffffff8ULL其实就是上面的移动抹0操作的位与运算。遮住不需要的信息,保留需要的信息。

通过isa的与cls的关联存储及object_getClass获取源码也就可以理解c++文件中isa是Class类型了。
struct NSObject_IMPL {
Class isa;
};


