本文的主要目的是理解类与isa是如何关联的
首先了解一下OC对象的本质是什么
- Clang
clang
是一个由Apple
主导编写,基于LLVM
的C/C++/OC
的编译器 - 主要是用于底层编译,将一些文件输出成
c++
文件,例如main.m
输出成main.cpp
,其目的是为了更好的观察底层的一些结构及实现的逻辑,方便理解底层原理。 - 在main中自定义一个类LGPerson,有一个属性name
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LGPerson
@end
- 通过终端,利用
clang
将main.m
编译成main.cpp
,有以下几种编译命令
//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp
//2、将 ViewController.m 编译成 ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m
//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
- 打开
main.app
,发现 LGPerson在底层会被编译为struct结构体-
LGPerson_IMPL
中的第一个属性 其实就是isa
,是继承自NSObject
,属于伪继承
,伪继承的方式是直接将NSObject结构体定义
为LGPerson中的第一个属性
,意味着LGPerson 拥有NSObject中的所有成员变量。
-
//NSObject的定义
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
//NSObject 的底层编译
struct NSObject_IMPL {
Class isa;
};
//LGPerson的底层编译
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 等效于 Class isa;
NSString *_name;
};
这里产生了一个疑问,为什么
isa
的类型是class
?
- 这个答案在之前的
initInstanceIsa
方法中可以得出。其根本原因是由于isa 对外反馈的是类信息,为了让开发人员更加清晰明确,需要在isa返回时做了一个类型强制转换,类似于swift中的 as 的强转。
总结
-
OC对象的本质
其实就是结构体
- LGPerson中的
isa
是继承自NSObject
中的isa
objc_setProperty源码探索
- 【1】在main.app中除了
LGPerson
的底层定义,还有属性name
对应的get
和set
方法,set
方法的实现是依赖于runtime
中的objc_setProperty
- 【2】在objc-781中搜索进入
objc_setProperty
的源码实现
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
-【3】进入reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);//设置isa指向
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);//retain新值
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;//设置新值
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;//设置新值
slotlock.unlock();
}
objc_release(oldValue);//relrase 旧值
}
总结
- 实际上
objc_setProperty
就是用来将上层的属性set
方法与底层reallySetProperty
关联。 - 设计
objc_setProperty
的原因是为了避免上层set
方法直接调用底层set方法
,这样会产生很大的临时变量
- 因此,苹果采用了
适配器设计模式(即将底层接口适配为客户端需要的接口)
,对外
提供一个接口
供上层的set方法使用,对内
调用底层的set方法
,使其相互不受影响,即无论上层怎么变,下层都是不变的
,或者下层的变化也无法影响上层
,主要是达到上下层接口隔离
的目的
cls 与 类 的关联原理
在此之前,需要先了解什么是联合体,为什么
isa
的类型isa_t
是使用联合体
定义
联合体(union)
构造数据类型的方式有以下两种:
-
结构体(struct)
结构体
是指把不同的数据组合成一个整体
,其变量是共存
的,变量不管是否使用,都会分配内存。-
缺点:所有属性都分配内存,比较
浪费内存
,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费 -
优点:
存储容量较大
,包容性强
,且成员之间不会相互影响
-
-
联合体(union,也称为共用体)
联合体
也是由不同的数据类型组成
,但其变量是互斥
的,所有的成员共占一段内存
。而且共用体采用了内存覆盖
技术,同一时刻只能保存一个成员
的值,如果对新的成员赋值
,就会将原来成员的值覆盖掉
-
缺点:包容性弱
-
优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也
节省了内存空间
-
isa的类型 isa_t
isa_t
类型使用联合体
的原因也是基于内存优化
的考虑,这里的内存优化是指在isa指针中通过char + 位域
(即二进制中每一位均可表示不同的信息)的原理实现。通常来说,isa指针占用的内存大小是8
字节,即64
位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能
- 源码实现
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
};
-
提供了两个成员,
cls
和bits
,由联合体的定义所知,这两个成员是互斥
的,也就意味着,当初始化isa指针时,有两种初始化方式-
通过
cls
初始化,bits无默认值
-
通过
bits
初始化,cls有默认值
-
-
还提供了一个结构体定义的
位域
,用于存储类信息及其他信息
,结构体的成员ISA_BITFIELD
,这是一个宏
定义,有两个版本__arm64__(对应ios 移动端)
和__x86_64__(对应macOS)
,以下是它们的一些宏定义-
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
:extra_rc
不足以保存引用计数时,标记为true
,使用sidetable
来进行管理,借⽤该变量存储进位
-
extra_rc
:表示该对象的引⽤计数值
,实际上是引⽤计数值减1
,例如,如果对象的引⽤计数为10
,那么extra_rc
为9
。如果引⽤计数⼤于10
,则需要使⽤到上⾯的has_sidetable_rc
isa的存储情况
-
原理探索
- 【1】进入
initInstanceIsa
的路径:alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
//初始化isa
initIsa(cls, true, hasCxxDtor);
}
- 【2】进入initIsa方法的源码实现,主要是初始化isa指针
- 通过
cls
初始化isa
- 或
- 通过
bits
初始化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 // !nonpointer 执行流程,即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进行赋值 为 0x001d800000000001ULL
// 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;
#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;
}
}
isa 与 类 的关联
cls
与isa
关联原理就是isa指针中的shiftcls位域中存储了类信息
,其中initInstanceIsa
的过程是将calloc 指针
和当前的类cls
关联起来
网友评论