主动已经是我对热爱东西表达的极限了
对象的本质?
联合体位域的简析?
isa的结构信息?
isa如何关联类?
通过位运算验证关联类
总结。
什么是对象?
对象在底层变成了什么呢?-
什么是Clang?
Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
-
用Clang做些什么?
Clang 通过底层编译,将一些m文件编译为cpp。 因为OC为C++或者C的超集,通过Clang底层编译,可以更多的看到底层的实现原理与逻辑和底层的架构
-
如何使用Clang?
Clang终端操作四种命令如下:
clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件
`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 具体操作请查看:后续
通过Clang编译查看类的底层是如何实现的,首先定义如下:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!"); }
return 0;
}
找到已经编译好的main.cpp文件,搜索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 (nonatomic, copy) 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
-
对象在底层会被编译为struct
那对象是如何被编译为结构体的呢?
我们知道结构体在C++可以继承,C可以伪继承
伪继承方式:
直接将NSObject结构体定义为LGPerson中的第一个属性,意味着LGPerson拥有NSObject中的所有成员变量。
LGPerson中的第一个属性NSObject_IVARS等效于NSObject中的isa
这里将LGPerson_IMPL结构体中的第一属性命名为当前结构体,即LGPerson_IMPL拥有 NSObject中的所有成员变量。而NSObject_IVARS即isa
而LGPerson类中的name的get, set方法也是一一对应的:

而set方法调用了objc_setProperty,通过分析得出objc_setProperty采用工厂模式:return newValue, remove oldValue ;意味着任何类中的set方法都会执行了如下操作:

继续分析isa结构,OC底层原理 结构体&联合体
-
isa结构分析:
在之前探索 OC底层原理 alloc & init & new 篇 时提到过initInstanceIsa方法,通过initInstanceIsa为切入点来分析isa是如何初始化的,可以看到isa指针的类型isa_t定义是通过联合体(union)来定义,联合体不清楚的小伙伴请参考:OC底层原理 结构体&联合体
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls; //这里返回的cls 为什么会是一个class类型?请查看后续
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
这里的isa_t为什么要定义成联合体呢?是为了优化内存,而isa指针占用的内存大小为8字节,即64位,已经能够存储大量信息了,这样可以节省内存空间,以提高性能的目的
而isa_t的定义中可以看出,
通过cls初始化,bits无默认值
通过bits初始化,cls有默认值
体现了联合体的互斥性
通过宏ISA_BITFIELD可以看出isa在iOS( __arm64__)和macOS(__x86_64__)的计算是存在差异的

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

通过 initIsa方法,查看 isa指针是如何被初始化的
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);//isa初始化
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0); //isa初始化
#if SUPPORT_INDEXED_ISA // !nonpinter 执行,即isa通过cls定义
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else // bits 时执行的流程
newisa.bits = ISA_MAGIC_VALUE; // bits进行赋值
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3; //isa与类关联
#endif
// 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
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
-
initIsa方法主要使用:cls初始化isa,和bits初始化isa
执行代码,开始验证,以LGPerson为例:
对赋值newisa.bits赋值之后打印出newisa查看LLDB结果

- 对赋值
newisa.bits赋值会对cls进行追加;对cls赋值不会同时对bits进行赋值(联合体互斥性)
通过LLDB打印结果,在进行赋值的时候,bits中的magic会存在一个59呢?
使用计算器查看宏ISA_MAGIC_VALUE与59的2进制

magic的占位为6,从47~52表示magic的占位,所以magic的默认值为59,二进制表示;也可以理解为isa指针将magic的占位为6,转换为2进制存储
- isa与类关联
当执行完如下代码后,LLDB打印出当前newisa,
cls 与 isa 关联原理就是isa指针中的shiftcls位域中存储了类信息,其中initInstanceIsa的过程是将 calloc 指针 和当前的 类cls 关联起来。而newisa.shiftcls = (uintptr_t)cls >> 3 是将当前cls 强转为系统能够编译的0,1识别码。然后右移3位。之所以要移动3位,要知道shiftcls才是我们需要存储的类信息,从上面isa对照表中可以看出,前面三位并不是我们需要的类内容,需要右移3位进行抹0操作。
newisa.shiftcls = (uintptr_t)cls >> 3; //isa与类关联

这个时候我们已经知道isa 与类 进行了关联。
-
开始验证
isa中的shiftcls是否真正的存储了当前类信息
将initInstanceIsa方法返回出去,然后开始验证:
-
方式1. 通过
isa指针地址与宏ISA_MSAK的值 通过位运算&来验证
LLDB 操作:# define ISA_MASK 0x00007ffffffffff8ULL //__x86_64__ # define ISA_MASK 0x0000000ffffffff8ULL //__arm64__po obj,然后x/4gx LGPerson的地址 或者直接x/4gx,然后取LGPerson的isa地址指针&宏ISA_MASK,结果如下:
位运算验证 -
方式2. 使用位运算验证
shiftcls存储在当前3~46位,
向右移动3位,抹除0~2号位数据,
向左移动20位,移除47~64号位的数据,
向右移动17位,回到原来的shiftcls所在的存储位,直接取出,即LGPerson 类信息
位运算操作截图 -
方式3. 通过
在runtime的object_getClass来验证main.m导入头文件#import <objc/runtime.h>,然后直接打印出当前的类也能够验证,这里就不打印了,直接去查看object_getClass的实现,
object_getClass方法如下:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
我们进入getIsa方法继续查看,
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA(); //直接return了当前isa
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
继续查看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); //这里与我们方式1做的操作是一样的
#endif
}
我们可以看出object_getClass的内部实现,也是采用了位运算&的方式对当前类进行处理。
验证总结:验证isa与类是否关联还有其他方式,这里不一一阐述,有兴趣的同学可以自己探索。
-
isa结构分析总结:
1.isa通过isa_t方法进行初始化,其中使用到了联合体位域;isa与类关联的同时,做了强转的操作和位运算计算存储,其中newisa.shiftcls = (uintptr_t)cls >> 3;的uintptr_t表示long类型。
2.x86_64和arm64环境不同,存在部分计算差异
3.掌握如何对类和isa的关联验证
4.如何使用Clang查看源码。

