上篇文章: 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
從打印結果可以看出這四個內存地址是連續(xù)的
绅你,切每個地址相差4字節(jié)
伺帘,因為int類型的內存大小為4字節(jié)
。也就是說如果我們知道一個對象的首地址忌锯,且知道其后排列的每個元素的內存大小伪嫁,那么我們就可以知道后面每個元素的大小。接下來我們來驗證一下
lldb
命令不熟的同學可以看下lldb內存讀取這篇文章脚猾。可能有的同學認為這個例子有點簡單,且數(shù)組中的元素的內存大小一致砚哗,無法說明問題龙助,如果是每個元素不一致又該怎么辦。
為了更具有說服力蛛芥,接下來咱們舉一個復雜的例子
struct StructPointTest { //內存大小 內存所在地址 比首地址多幾個字節(jié)
double a; // 8 (0-7) 0字節(jié)
short b; // 2 (8-9) 8字節(jié)
int c; // 4 (12-15) 12字節(jié)
struct Struct2 d; //16 (16-31) 16字節(jié)
WJPerson *e; // 8 (32-39) 32字節(jié)
}structPointTest;
根據(jù)這篇iOS底層之內存對齊文章提鸟,咱們能夠知道StructPointTest
結構體中每個元素的內存大小
和內存所在地址
以及比首地址多幾個字節(jié)
,咱們就用這個結構體來驗證一下
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
里各個元素的地址:
-
str.a
的地址為str的首地址就是0x00007ffeefbff550
-
str.b
的地址比首地址多8個字節(jié)
仅淑,推出地址為0x00007ffeefbff558
-
str.c
的地址比首地址多12個字節(jié)
称勋,推出地址為0x00007ffeefbff55c
-
str.d
的地址比首地址多16個字節(jié)
,推出地址為0x00007ffeefbff560
-
str.e
的地址比首地址多32個字節(jié)
涯竟,推出地址為0x00007ffeefbff570
接下來咱們使用lldb
調試打印一下這些地址
NSLog
打印的值
0x7ffeefbff550 -- 1.000000
0x7ffeefbff558 -- 3
0x7ffeefbff55c -- 2
0x7ffeefbff560 --
0x7ffeefbff570 -- <WJPerson: 0x1006b1110>
由此我們可以得出結論:只要知道一個對象的首地址的值赡鲜,就可以根據(jù)對象中元素的內存大小推導出每個元素的內存地址
。
二庐船、類結構分析
我們在最新的objc4源碼
中搜索objc_class
會發(fā)現(xiàn)兩個版本的結構體定義银酬,一個是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
我們已經(jīng)了解了屯曹,cache
我們根據(jù)名字就可以知道是緩存信息,那么class_data_bits_t bits;
中又存放了一下什么信息呢。
正常情況下我們無法直接訪問objc_class
中的bits
內容恶耽,要想了解bits
的信息密任,我們就需要想辦法訪問bits
所在的內存空間,這時候就需要用到我們上文提到的內存偏移
的知識了偷俭。
我們已經(jīng)知道isa
和superclass
的內存大小都是8字節(jié)浪讳,那么cache又占了多少字節(jié)呢。我們先看下cache_t
中除去static
修飾的靜態(tài)變量和方法外還有什么涌萤。
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; //8字節(jié)
explicit_atomic<mask_t> _mask; //4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //8字節(jié)
mask_t _mask_unused; //4字節(jié)
#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字節(jié)
mask_t _mask_unused; //4字節(jié)
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags; //2字節(jié)
#endif
uint16_t _occupied; //2字節(jié)
}
可以看到第一個#if
到#endif
直接不管滿足什么條件都是12字節(jié)
淹遵,如果第二個#if
滿足條件就是16字節(jié)
,否則就是14字節(jié)
负溪,但是不管是14字節(jié)
還是16字節(jié)
透揣,根據(jù)內存對齊原則cache_t
的大小都是16字節(jié)
。所以我們最后得出結論:cache_t的大小為16字節(jié)
川抡。
接下來我們通過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();
}
};
通過上面代碼我們發(fā)現(xiàn)拆祈,除了class_rw_t* data()
和const class_ro_t *safe_ro()
返回了對象外,剩下的返回值就是bool
類型倘感、void
類型或基礎數(shù)據(jù)類型
。所以我們接下來主要看class_rw_t* data()
和const class_ro_t *safe_ro()
通過上面的分析計算得出bits
比首地址多8(isa)+8(superclass)+16(cache)
也就是32
個字節(jié)咙咽。接下來我們實際操作獲取下信息老玛。
我們先獲取下class_rw_t* data()
的信息
class_ro_t
里有什么
struct class_rw_t {
//這里只展示我們經(jīng)常接觸的內容,如需看完整代碼請自行查看源碼
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;
}
}
};
發(fā)現(xiàn)class_ro_t
還有baseMethodList
、baseProtocols
寂玲、ivars
等方法
塔插、協(xié)議
、屬性
等信息拓哟。我們再來打印一下這些信息
class_ro_t
中的ivars
存的是成員變量
和屬性
想许,baseMethodList
存的和class_rw_t
中的methods
一模一樣。那么
WJPerson
中的類方法哪去了呢?