iOS底层之类结构分析
上篇文章: iOS底层之isa走位探索
前言
从上篇文章中我们了解了对象的isa
指针的走位逻辑,接下来咱们分析一下类的结构。
一、内存偏移
在咱们分析类结构之前,咱们先来了解一下内存偏移的知识。咱们先看一个例子
void pointOffset(){
int arr[4] = {1, 3, 5, 6};
int *p = arr;
for (int i=0; i<4; i++) {
NSLog(@"%p -- %d", p+i, arr[i]);
}
}
打印结果为
0x7ffeefbff4e0 -- 1
0x7ffeefbff4e4 -- 3
0x7ffeefbff4e8 -- 5
0x7ffeefbff4ec -- 6
从打印结果可以看出这四个内存地址是连续的
,切每个地址相差4字节
,因为int类型的内存大小为4字节
。也就是说如果我们知道一个对象的首地址,且知道其后排列的每个元素的内存大小,那么我们就可以知道后面每个元素的大小。接下来我们来验证一下
lldb
命令不熟的同学可以看下lldb内存读取这篇文章。可能有的同学认为这个例子有点简单,且数组中的元素的内存大小一致,无法说明问题,如果是每个元素不一致又该怎么办。
为了更具有说服力,接下来咱们举一个复杂的例子
struct StructPointTest { //内存大小 内存所在地址 比首地址多几个字节
double a; // 8 (0-7) 0字节
short b; // 2 (8-9) 8字节
int c; // 4 (12-15) 12字节
struct Struct2 d; //16 (16-31) 16字节
WJPerson *e; // 8 (32-39) 32字节
}structPointTest;
根据这篇iOS底层之内存对齐文章,咱们能够知道StructPointTest
结构体中每个元素的内存大小
和内存所在地址
以及比首地址多几个字节
,咱们就用这个结构体来验证一下
struct StructPointTest str = {1.0, 3, 2, {4,5,'a',7}, [WJPerson alloc]};
NSLog(@"\n%p -- %f \n%p -- %d \n%p -- %d \n%p -- \n%p -- %@", &str.a, str.a, &str.b, str.b, &str.c, str.c, &str.d, &str.e, str.e);
咱们首先打印下str所在的首地址
StructPointTest结构体首地址 根据咱们上面得出的结论我们可以推导出StructPointTest
里各个元素的地址:
-
str.a
的地址为str的首地址就是0x00007ffeefbff550
-
str.b
的地址比首地址多8个字节
,推出地址为0x00007ffeefbff558
-
str.c
的地址比首地址多12个字节
,推出地址为0x00007ffeefbff55c
-
str.d
的地址比首地址多16个字节
,推出地址为0x00007ffeefbff560
-
str.e
的地址比首地址多32个字节
,推出地址为0x00007ffeefbff570
接下来咱们使用lldb
调试打印一下这些地址
NSLog
打印的值
0x7ffeefbff550 -- 1.000000
0x7ffeefbff558 -- 3
0x7ffeefbff55c -- 2
0x7ffeefbff560 --
0x7ffeefbff570 -- <WJPerson: 0x1006b1110>
由此我们可以得出结论:只要知道一个对象的首地址的值,就可以根据对象中元素的内存大小推导出每个元素的内存地址
。
二、类结构分析
我们在最新的objc4源码
中搜索objc_class
会发现两个版本的结构体定义,一个是runtime.h
文件里定义的老版的objc_class
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE; //已废弃的
一个是objc-runtime-new.h
文件里定义的新版的objc_class
,由于内容太多所以只粘贴了部分代码。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分已省略,目前显示的是objc_class的
}
从新版的定义中,可以看到 objc_class
结构体类型是继承自 objc_object
的。
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
接下来我们分析一下新版的objc_class
。
1、类结构分析之bits
从最新的objc_class
的定义中我们知道objc_class
中有4个成员变量,分别为isa
,superclass
,cache
和bits
。isa
和superclass
我们已经了解了,cache
我们根据名字就可以知道是缓存信息,那么class_data_bits_t bits;
中又存放了一下什么信息呢。
正常情况下我们无法直接访问objc_class
中的bits
内容,要想了解bits
的信息,我们就需要想办法访问bits
所在的内存空间,这时候就需要用到我们上文提到的内存偏移
的知识了。
我们已经知道isa
和superclass
的内存大小都是8字节,那么cache又占了多少字节呢。我们先看下cache_t
中除去static
修饰的静态变量和方法外还有什么。
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; //8字节
explicit_atomic<mask_t> _mask; //4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //8字节
mask_t _mask_unused; //4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets; //8字节
mask_t _mask_unused; //4字节
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags; //2字节
#endif
uint16_t _occupied; //2字节
}
可以看到第一个#if
到#endif
直接不管满足什么条件都是12字节
,如果第二个#if
满足条件就是16字节
,否则就是14字节
,但是不管是14字节
还是16字节
,根据内存对齐原则cache_t
的大小都是16字节
。所以我们最后得出结论:cache_t的大小为16字节
。
接下来我们通过lldb打印一下bits
的信息。
@interface WJPerson : NSObject
{
NSString *habby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)sayGoodbye;
@end
@implementation WJPerson
- (void)sayHello{}
+ (void)sayGoodbye{}
@end
我们先给WJPerson添加一下信息,然后再看一下WJPerson的信息。
我们先来看下class_data_bits_t
都有什么信息
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
// Atomically set the bits in `set` and clear the bits in `clear`.
// set and clear must not overlap.
void setAndClearBits(uintptr_t set, uintptr_t clear)
{
ASSERT((set & clear) == 0);
uintptr_t oldBits;
uintptr_t newBits;
do {
oldBits = LoadExclusive(&bits);
newBits = (oldBits | set) & ~clear;
} while (!StoreReleaseExclusive(&bits, oldBits, newBits));
}
void setBits(uintptr_t set) {
__c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED);
}
void clearBits(uintptr_t clear) {
__c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED);
}
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
void setClassArrayIndex(unsigned Idx) {
#if SUPPORT_INDEXED_ISA
// 0 is unused as then we can rely on zero-initialisation from calloc.
ASSERT(Idx > 0);
data()->index = Idx;
#endif
}
unsigned classArrayIndex() {
#if SUPPORT_INDEXED_ISA
return data()->index;
#else
return 0;
#endif
}
bool isAnySwift() {
return isSwiftStable() || isSwiftLegacy();
}
bool isSwiftStable() {
return getBit(FAST_IS_SWIFT_STABLE);
}
void setIsSwiftStable() {
setAndClearBits(FAST_IS_SWIFT_STABLE, FAST_IS_SWIFT_LEGACY);
}
bool isSwiftLegacy() {
return getBit(FAST_IS_SWIFT_LEGACY);
}
void setIsSwiftLegacy() {
setAndClearBits(FAST_IS_SWIFT_LEGACY, FAST_IS_SWIFT_STABLE);
}
// fixme remove this once the Swift runtime uses the stable bits
bool isSwiftStable_ButAllowLegacyForNow() {
return isAnySwift();
}
_objc_swiftMetadataInitializer swiftMetadataInitializer() {
// This function is called on un-realized classes without
// holding any locks.
// Beware of races with other realizers.
return safe_ro()->swiftMetadataInitializer();
}
};
通过上面代码我们发现,除了class_rw_t* data()
和const class_ro_t *safe_ro()
返回了对象外,剩下的返回值就是bool
类型、void
类型或基础数据类型
。所以我们接下来主要看class_rw_t* data()
和const class_ro_t *safe_ro()
通过上面的分析计算得出bits
比首地址多8(isa)+8(superclass)+16(cache)
也就是32
个字节。接下来我们实际操作获取下信息。
我们先获取下class_rw_t* data()
的信息
class_ro_t
里有什么
struct class_rw_t {
//这里只展示我们经常接触的内容,如需看完整代码请自行查看源码
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 *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->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 *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->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 *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
}
可以看到在class_rw_t
中有methods
、properties
和protocols
。我们在类中定义了一些属性和方法看下能不能在这里看到。
从上面结果可以看出
class_rw_t
确实包含了一下属性和方法,不过只包含了我们添加的属性
、实例方法
和属性的setter
和getter
方法,那么我们定义的成员变量
和类方法
呢,是不是放在了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
、ivars
等方法
、协议
、属性
等信息。我们再来打印一下这些信息
class_ro_t
中的ivars
存的是成员变量
和属性
,baseMethodList
存的和class_rw_t
中的methods
一模一样。那么
WJPerson
中的类方法哪去了呢?