先转一下源码
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *KCName;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
clang -rewrite-objc main.m -o main.cpp
编译成功了之后就会出现这样的一个.cpp文件,这就是转换后的C++文件了:
由此可见:OC对象的本质就是结构体。
我们再顺藤摸瓜,找到struct NSObject_IMPL NSObject_IVARS;这个类型:
struct NSObject_IMPL结构体事实上是一个Class类型的isa指针,那么也就是说明,每一个新定义的类,它对应的结构体中都会有一个这个Class类型的isa指针。
并且NSObject底层是struct objc_object结构体。
Class superclass; //指向其父类
cache_t cache; //缓存
class_data_bits_t bits; //类的数据
可以看出objc_class是继承objc_object的。
看到这我们先来浅总结一下:
每个类的底层都会有一个Class类的isa指针。
Class底层是struct objc_class *类型,NSObject底层是struct objc_object结构体,id底层是struct objc_object *类型。
struct objc_object的实现是:
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
也就是说NSObject底层实现的结构体里只有一个成员变量isa,又因为Class底层是struct objc_class *类型,所以 NSObject的本质是objc_class。
id底层实现是struct objc_object 类型,怪不得声明id类型变量时 后面不用再加""了,因为它定义的时候就定义为了一个Class的指针。
首先数入参LGPerson * self,SEL _cmd,所有OC方法都有这两个隐藏参数,所以我们可以在OC的方法中使用self指针,但是这两个参数对外是不显示的。
然后可以看到getter和setter里是通过首地址指针+对应成员变量的地址值指针的偏移量的方式取和存的,最终通过(*(NSString **)还原为string类型。取值的过程就是:先拿到当前成员变量的地址,再去取这个地址里面所存的值
struct objc_class : objc_object {
...省略无关代码
// Class ISA; //ISA(从objc_object继承过来的)
Class superclass; //指向其父类
cache_t cache; //缓存
class_data_bits_t bits; //类的数据
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
...省略无关代码
}
从上面的结构体,可以看出:类是一个结构体,里面存放了isa、superClass、cache、bits等。
isa指针保存着指向类对象的内存地址,类对象全局只有一个,因此每个类创建出来的对象都会默认有一个isa属性,保存类对象的地址,也就是class,通过class就可以查询到这个对象的属性和方法,协议等;
isa分两种类型(isa指针是什么含义的时候):
每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//->表示使用优化的isa指针
uintptr_t has_assoc : 1;//->是否包含关联对象
uintptr_t has_cxx_dtor : 1;//->是否设置了析构函数,如果没有,释放对象更快
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
uintptr_t magic : 6;//->固定值,用于判断是否完成初始化
uintptr_t weakly_referenced : 1;//->对象是否被弱引用
uintptr_t deallocating : 1;//->对象是否正在销毁
uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
uintptr_t extra_rc : 19; //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
};
};
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
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
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// 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,两种方式:
superclass指向该对象或该类的父类,class*本身也是一个指针,占8字节。
cache缓存,追踪进去看一下cache_t结构体的类型,而不是结构体指针类型(占8字节),就需要计算一下了:
struct cache_t {
struct bucket_t *_buckets; // 8 散列表
mask_t _mask; // 4 散列长度-1
mask_t _occupied; // 4 已经缓存的方法数量
}
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;//函数的内存地址
cache_key_t _key;//SEL作为key
#else
cache_key_t _key;//SEL作为key
MethodCacheIMP _imp;//函数的内存地址
#endif
}
同时这里引入了bucket_t(散列表),cache_t哈希表结构,哈希表内部存储的bucket_t,bucket_t中存储的是SEL和IMP的键值对。
class_data_bits_t作为属性bits的类型,也是个结构体,其数据结构如下:
struct class_data_bits_t {
friend objc_class;//这里声明objc_class为class_data_bits_t的友元类,使得objc_class可以访问class_data_bits_t中的私有方法
uintptr_t bits;
......
从源码中可以看到,class_data_bits_t有一个属性uintptr_t bits,bits的类型是uintptr_t类型数据,那么uintptr_t是什么数据结构呢?以下是通过查看一些资料得到的解释:
是无符号整数类型,能够存储指针。这通常意味着它与指针的大小相同。 它可选地在C++ 11和更高版本的标准中定义。 想要一个可以保存体系结构指针类型的整数类型的常见原因是对指针执行特定于整数的操作,或者通过将指针提供为整数“句柄”来模糊指针的类型
这里面其实利用位域存储数据,简单来说因为这个bits跟指针大小相同,在iOS中指针大小是8个字节,也就是64位,但是通常单一数据无法用满这64位的,会有很多空位,造成空间浪费。通过位运算,把不同的数据按不同的位置存进这64位内存空间的不同位置里,这样就可以提高内存利用率。这里的bits其实就是这样的,他不只存储class_rw_t结构体指针,还存储其他的信息。
看到这里我们获取会有一些疑惑,那就是类结构里面我们没有看到方法列表、属性列表甚至成员列表等,那这些东西存在哪里呢?由前面分析知道bits里有个class_rw_t指针,而且objc_class里面的data和getData方法都涉及到class_rw_t的读写:
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
void set_ro_or_rwe(const class_ro_t *ro) {
ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
// the release barrier is so that the class_rw_ext_t::ro initialization
// is visible to lockless readers
rwe->ro = ro;
ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
void setFlags(uint32_t set)
{
__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
}
void clearFlags(uint32_t clear)
{
__c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
}
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
{
ASSERT((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!CompareAndSwap(oldf, newf, &flags));
}
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
}
从这个结构体可以看到,其中有const method_array_t methods()、const property_array_t properties() 等这样的方法,因此可以推测类结构中的方法列表、属性列表是从这里获取的。以const method_array_t methods()为例,获取类的实例方法也是通过调用methods()来获取的。比如说runtime函数class_copyMethodList,其代码如下:
Method *
class_copyMethodList(Class cls, unsigned int *outCount)
{
unsigned int count = 0;
Method *result = nil;
if (!cls) {
if (outCount) *outCount = 0;
return nil;
}
mutex_locker_t lock(runtimeLock);
const auto methods = cls->data()->methods();
ASSERT(cls->isRealized());
count = methods.count();
if (count > 0) {
auto iterator = methods.signedBegin();
auto end = methods.signedEnd();
result = (Method *)malloc((count + 1) * sizeof(Method));
count = 0;
for (; iterator != end; ++iterator) {
result[count++] = _method_sign(&*iterator);
}
result[count] = nil;
}
if (outCount) *outCount = count;
return result;
}
其中const auto methods = cls->data()->methods()就是通过class_rw_t的methods()方法获取实例方法列表。但是在class_rw_t结构体中,我们并没有看到结构体里由存储方法列表相关的属性。反而是在methods()方法里看到读取方法列表是从class_rw_ext_t或者class_ro_t结构体。其实不只是methods,包括properties和protocols也是一样的。那么他们之间到底有什么关系呢?
接下来我们先来看看class_rw_ext_t和class_ro_t的数据结构,看看为什么class_rw_t可以从他们这里读取类相关信息。
class_rw_ext_t是个结构体,其数据结构如下:
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
由此可知,结构体class_rw_ext_t中的确有方法列表、属性列表和协议等信息,而且还有一个class_ro_t指针,接下来继续看class_ro_t。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
class_ro_t *duplicate() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
return ro;
} else {
size_t size = sizeof(*this);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
return ro;
}
}
};
这里面我们发现class_ro_t不仅有baseMethodList、 baseProtocols、baseProperties等信息,还有成员变量const ivar_list_t * ivars等其他信息。在了解他们的结构之后,我们从结构上简单做一下它们之间关系的分析:首先class_rw_t有一个指针ro_or_rw_ext,ro_or_rw_ext指向可能是class_rw_ext_t或者是class_ro_t。ro_or_rw_ext指针的作用是,当读取类相关信息时,会优先判断是否指向class_rw_ext_t,如果是class_rw_ext_t就从它读取,没有就是class_ro_t,直接从class_ro_t读取。那么在程序运行中到底ro_or_rw_ext什么时候是class_rw_ext_t,什么时候是class_ro_t呢?其实WWDC2020有过对这三个数据结构的介绍,下面会做简单的总结。
要了解它们之间的关系,我们首先要了解几个关键跟它们相关的概念。
从字面意思解读:class_ro_t中的“ro”代表只读;class_rw_t中的“rw”代表可读可写;class_rw_ext_t中的“rw_ext”代表可读可写的扩展。
干净内存和脏内存:干净内存内存指的是内存一旦加载就不会被改变。反之,脏内存就是在运行时会被修改的。
Class本身是运行时加载的,在运行时会被改变,所以本身Class就是属于脏内存。那么如果想要获取Class的干净内存,也就是编译时确定的数据结构包括方法列表、成员变量等的,该怎么办?
这其实就是class_ro_t的作用。因为class_ro_t是只读,意味着 class_ro_t是从mach-o读取类的数据之后,就不会被改变。那如果我们想在运行时修改类的信息,比如添加方法,比如加载category怎么办呢?那这时候就有一个与之对应的class_rw_t结构,class_rw_t可以在运行时存储类的信息,可读可写的,可以在运行时修改。
说到这里,好像还漏掉一个结构class_rw_ext_t,这个东西又是干什么用的呢?存在的意义是什么?其实还是跟运行时有关。实际上在我们的app运行中,需要运行时修改的类是非常少的,据统计平均大概就10%左右。那也就是说大部分只需要读取class_ro_t中的数据就够了,少部分才需要修改。因此才会有class_rw_ext_t这个扩展的结构体。class_rw_ext_t的作用是这样的:当我们需要修改类结构时,比如添加方法、加载category等时,class_rw_t回去开辟一个额外的空间rwe(class_rw_ext_t),用于存储新的方法和class_ro_t中的方法等信息。这样做的目的有一个好处就是,对于绝大部分类是不需要这个开辟class_rw_ext_t这个结构体,节省内存。
因为category是运行时添加的,他只能操作class_rw_t结构,但是class_rw_t结构没有添加成员变量的入口。成员变量是存储在class_ro_t中的,是无法被修改的,所以category就不能添加成员变量。