本系列博客是本人的源碼閱讀筆記吩愧,如果有 iOS 開發(fā)者在看 runtime 的贡耽,歡迎大家多多交流。
分析
之前的
iOS開發(fā)之 runtime(28) :獲取每個(gè) class 信息(1)
里面瑟慈,我們寫過如下一些代碼:
#import <dlfcn.h>
#include <mach-o/loader.h>
#include <mach-o/getsect.h>
struct class_ro_t1 {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
};
struct class_rw_t1 {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t1 *ro;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
#if !__LP64__
#define FAST_DATA_MASK 0xfffffffcUL
#elif 1
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#else
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#endif
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit)
{
return bits & bit;
}
public:
class_rw_t1* data() {
return (class_rw_t1 *)(bits & FAST_DATA_MASK);
}
};
#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 cache_t1 {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
// struct bucket_t * find(cache_key_t key, id receiver);
//
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
struct objc_class1 : objc_object {
// Class ISA;
Class superclass;
cache_t1 cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t1 *data() {
return bits.data();
}
const char *mangledName() {
return ((const class_ro_t1 *)data())->name;
}
const char *demangledName(bool realize = false);
const char *nameForLogging();
};
typedef struct objc_class1 *Class1;
typedef struct classref * classref_t;
#ifndef __LP64__
#define mach_header mach_header
#else
#define mach_header mach_header_64
#endif
const struct mach_header *machHeader = NULL;
static NSString *configuration = @"";
int main()
{
unsigned long byteCount = 0;
if (machHeader == NULL)
{
Dl_info info;
dladdr((__bridge const void *)(configuration), &info);
machHeader = (struct mach_header_64*)info.dli_fbase;
}
uintptr_t* data = (uintptr_t *) getsectiondata(machHeader, "__DATA", "__objc_classlist", &byteCount);
NSUInteger counter = byteCount/sizeof(void*);
for(NSUInteger idx = 0; idx < counter; ++idx)
{
Class1 cls =(Class1)( data[idx]);
NSLog(@"class:%s",cls->mangledName());
}
return 0;
}
這些代碼筆者也放到 github 中給大家參考了:
https://github.com/zjh171/RuntimeSample/tree/master/MyDIYClass
面對(duì)上面一些代碼将谊,相信大部分朋友結(jié)合筆者之前的文章應(yīng)該能看懂的,但如果您仍有疑問的院刁,本文就給大家再次深入分析一下以上代碼的作用糯钙,并做一些 Demo 以便大家更深的理解。
分析
其實(shí)以上代碼很簡(jiǎn)單,分兩部分超营,
第一部分是一些結(jié)構(gòu)體的聲明鸳玩,筆者在所有的結(jié)構(gòu)體后面都加了 1 這個(gè)數(shù)字,用于和系統(tǒng)的區(qū)分演闭。這一這么說,筆者聲明的這些結(jié)構(gòu)體颓帝,除了名字米碰,其他都和系統(tǒng)的一模一樣。
第二部分是 main 函數(shù)购城,熟悉的朋友也很容易理解吕座,無非是從 section 為 __objc_classlist 內(nèi)取出所有的類,然后調(diào)用其 mangledName 方法瘪板。只是 mangledName 方法比較有意思:
const char *mangledName() {
return ((const class_ro_t1 *)data())->name;
}
也就是說吴趴,他獲取的是 data() 方法 中的 name 字段,繼續(xù)侮攀,我們看一下 data 方法來自何處:
data 方法是獲取的結(jié)構(gòu)體 bits 中的 name 字段锣枝。而且 bits & FAST_DATA_MASK 獲取的居然是結(jié)構(gòu)體 class_ro_t1 !相信讀者已經(jīng)開始?xì)g呼了兰英,因?yàn)檎f明了以下幾點(diǎn):
- 一個(gè)完整的類其實(shí)有 readonly 結(jié)構(gòu)體 和 readwrite 結(jié)構(gòu)體構(gòu)成
- readonly 通過獲取 bits & FAST_DATA_MASK 獲得 readwrite 部分撇叁。
實(shí)戰(zhàn)
實(shí)戰(zhàn)部分的代碼在這里:
https://github.com/zjh171/RuntimeSample/tree/master/MyDIYClass/MyDIYClass2
完整的代碼不貼了,這里只貼幾個(gè)不一樣的部分:
for(NSUInteger idx = 0; idx < counter; ++idx)
{
Class1 cls =(Class1)( data[idx]);
NSLog(@"class:%s \n",cls->mangledName1());
bool isRoot = (cls->data()->flags & RO_ROOT);
if (isRoot) {
printf("is root \n");
} else {
printf("is not root \n");
Class1 superClass = cls->superclass;
printf("superClass:%s \n",superClass->mangledName1());
}
bool isRealized = cls->data()->flags & RW_REALIZED;
if (isRealized) {
printf("is isRealized\n");
} else {
printf("is not Realized\n");
}
bool isARC = cls->data()->flags & RO_IS_ARC;
if (isARC) {
printf("is arc\n");
} else {
printf("is not arc\n");
}
bool loadMethodHasCalled = cls->data()->flags & RW_LOADED;
if (loadMethodHasCalled) {
printf("loadMethod Has Called \n");
} else {
printf("loadMethod has not Called \n");
}
bool hasCXXCtor = cls->data()->flags & RO_HAS_CXX_STRUCTORS;
if (hasCXXCtor) {
printf(" Has HAS_CXX_CTOR \n");
} else {
printf(" has not HAS_CXX_CTOR \n");
}
bool hasWeakWithoutARC = cls->data()->flags & RO_HAS_WEAK_WITHOUT_ARC;
if (hasWeakWithoutARC) {
printf(" Has RO_HAS_WEAK_WITHOUT_ARC \n");
} else {
printf(" has not RO_HAS_WEAK_WITHOUT_ARC \n");
}
}
這里幾個(gè)例子都是從 readwrite 里取出的數(shù)據(jù)來和一些常數(shù)進(jìn)行 & 操作從而判斷是否為真/假畦贸,甚至從里面獲取某些數(shù)據(jù)陨闹。 關(guān)于 & 操作這里不多做介紹了,相信經(jīng)常讀筆者專欄的朋友應(yīng)該很熟悉了薄坏。
另外趋厉,筆者的測(cè)試類代碼如下:
class A
{
public:
//默認(rèn)構(gòu)造函數(shù)
A()
{
num=1001;
age=18;
}
//初始化構(gòu)造函數(shù)
A(int n,int a):num(n),age(a){}
private:
int num;
int age;
};
@interface TestObject : NSObject {
// __weak NSObject *propertyA;
// A a;
}
這里給大家打印出結(jié)果:
is not root
superClass:
is not Realized
is not arc
loadMethod has not Called
has not HAS_CXX_CTOR
has not RO_HAS_WEAK_WITHOUT_ARC
Program ended with exit code: 0
這里我們一個(gè)一個(gè)作解釋:
- 很顯然,只要不是 NSObject 胶坠,那肯定不是 root class
- super class 獲取不多君账,暫時(shí)不知道為什么
- 從上幾篇文章中我們可知,剛從 section 里取出來的數(shù)據(jù)肯定是 not Realized
- 未使用 ARC涵但,因?yàn)楣P者做了如下設(shè)置:
load 方法是否存在杈绸。筆者做了個(gè)實(shí)驗(yàn),如果在 TestObject 中 添加 load 方法矮瘟,那就 loadMethod has Called瞳脓,否則 loadMethod has not Called
判斷是否有 C++ 構(gòu)造函數(shù)。為了證明這個(gè)值澈侠,筆者在頭文件添加了一個(gè)類 A劫侧,后來筆者做了實(shí)驗(yàn),如果 調(diào)用了 A 里面的方法,或者在 TestObject 中聲明了 A 對(duì)象烧栋,則這個(gè)結(jié)果會(huì)變成 has HAS_CXX_CTOR
用于判斷写妥,在非 ARC 的情況下,有沒有使用 ARC 的特性审姓,比如筆者注釋的
__weak NSObject *propertyA;
使用的話就是 true珍特,否則 false
注意
注意!注意魔吐!特別注意扎筒!
如果前面我們寫了 load 方法,會(huì)影響后面所有的結(jié)果酬姆,這是為什么呢嗜桌?筆者后面的文章會(huì)給大家解釋,歡迎大家繼續(xù)關(guān)注筆者博客辞色。
總結(jié)
筆者在寫博客的過程中也經(jīng)常有豁然開朗的感覺骨宠,也許這就是 runtime 的魅力吧。有人疑問筆者相满,做這個(gè) runtime 分析有什么用层亿,筆者付之一笑。因?yàn)?runtime 中真的有太多可以提升你開發(fā)能力的東西雳灵,讓你網(wǎng)上層幫你的業(yè)務(wù)代碼更少 bug棕所,更優(yōu)秀設(shè)計(jì);往下層悯辙,提升你對(duì)整個(gè) iOS 系統(tǒng)有更深的理解琳省。