想要成爲一名
iOS開發高手
,免不了閱讀源碼。如下是筆者在OC源碼探索
中梳理的一個小系列——類與對象篇,歡迎你們閱讀指正,同時也但願對你們有所幫助。git
- OC源碼分析之對象的建立
- OC源碼分析之isa
- OC源碼分析之類的結構解讀
- 未完待續...
若是你使用過Objective-C
(簡稱OC
)這門語言開發過應用程序,你必定對NSObject
不陌生。OC
裏面有兩個NSObject
,一個是咱們熟知的NSObject
類,另外一個是NSObject
協議。協議相似於其餘面嚮對象語言(如Java
、C++
)的接口,NSObject
協議裏面定義了一些屬性和方法,但自己並未實現,而NSObject
類遵循了NSObject
協議,因此NSObject
類實現了這些方法。github
咱們跟NSObject
類打了那麼多交道,卻不必定對它瞭如指掌,今天筆者將帶你們對NSObject
類的結構進行全方位的解讀。swift
注意:api
- 本文筆者用的全部源碼都是基於蘋果開源的
objc4-756.2源碼
,文末會附上github
的地址。- 本文采用的是
x86_64
CPU架構,跟arm64
差異不大,有區別的地方會註明的。
從NSObject
類的定義開始緩存
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製代碼
能夠看到
NSObject
有個Class
類型的isa
成員變量,這裏你們留意一下。架構
接下來用Clang
編譯main.m
,輸出.cpp
文件,看一下NSObject
類的底層定義app
clang -rewrite-objc main.m -o main.cpp
複製代碼
打開main.cpp
文件,找到了NSObject
less
#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif
struct NSObject_IMPL {
Class isa;
};
複製代碼
發現NSObject
類本質上是objc_object
結構體,同時有定義一個NSObject_IMPL
結構體(IMPL
是implementation
的縮寫),裏面有NSObject類
的isa
成員變量(對應於OC
時的NSObject
類定義中的isa
成員變量)。ide
此時,筆者特別好奇咱們本身定義的類經Clang
編譯後是什麼樣子,索性看一下吧函數
@interface Person : NSObject
@property (nonatomic) NSInteger age;
- (void)run;
@end
@implementation Person
- (void)run {
NSLog(@"I am running.");
}
@end
複製代碼
一個簡單的Person
類,有個age
屬性和run
方法,編譯後就是
#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif
extern "C" unsigned long OBJC_IVAR_$_Person$_age;
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSInteger _age;
};
static void _I_Person_run(Person * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_mc_9fhhprrj4k92vxzqm3g127z40000gn_T_main_09fc70_mi_0);
}
static NSInteger _I_Person_age(Person * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_Person$_age)); }
static void _I_Person_setAge_(Person * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_Person$_age)) = age; }
複製代碼
可見,Person
類本質一樣是objc_object
結構體類型,惟一能體現與NSObject
類之間的繼承關係的就是,Person_IMPL
結構體內部多了個struct NSObject_IMPL
類型的NSObject_IVARS
成員變量——即以NSObject
爲根類的繼承體系裏的全部類,都有個Class
類型的isa
成員變量。
objc_object
結構若是你對isa
有所瞭解,或者有讀過筆者的 OC源碼分析之isa 這篇文章,相信必定對objc_object
有印象。
這裏就直接上源碼
struct objc_object {
private:
isa_t isa;
... // 一些函數
};
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
複製代碼
isa_t
的分析請戳 OC源碼分析之isa ,裏面已經很詳細了,這裏就很少做贅述。objc_object
結構體內部的方法有五十個左右,大體可分爲如下幾類
isa
的函數,如initIsa()
、getIsa()
、changeIsa()
等isWeaklyReferenced()
、setWeaklyReferenced_nolock()
等retain()
、release()
、autorelease()
等hasAssociatedObjects()
和setHasAssociatedObjects
Class
結構簡介一樣先上源碼
typedef struct objc_class *Class;
typedef struct objc_object *id;
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
... // 一些函數
};
複製代碼
從源碼能夠看出,Class
是objc_class
結構體類型的指針變量,繼承自objc_object
結構體。也就是說,Class
有4個成員變量,且它們在內存存儲上是有序的,依次分別是:
isa
:類型是isa_t
,64位
下長度爲8字節,因爲上篇博文已作過度析,這裏略過;superclass
:類型是Class
,表示繼承關係,指向當前類的父類,一樣8字節;cache
:類型是cache_t
,表示緩存,用於緩存指針和 vtable
,加速方法的調用。其具體結構以下struct cache_t {
struct bucket_t *_buckets; // 64位下是8字節
mask_t _mask; // 64位下是4字節
mask_t _occupied; // 64位下是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
typedef unsigned int uint32_t;
複製代碼
可見,cache
這個成員變量長度是16字節。
cache比較重要,關於它的分析筆者將另起一篇博文,這裏暫時擱置。
bits
:類型是class_data_bits_t
,用於存儲類的數據(類的方法、屬性、遵循的協議等信息),其結構以下struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits; // unsigned long
... // 一些函數
};
複製代碼
其長度也是8字節。根據bits
成員變量在objc_object
結構體中的描述,它實質上是class_rw_t *
加上自定義rr/alloc
標誌,也就是說,最重要的是class_rw_t
——筆者接下來將重點介紹它。
class_rw_t
& class_ro_t
分析OC
類中的屬性、方法還有遵循的協議等信息都保存在class_rw_t
中,首先看看class_rw_t
的結構:
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; // 方法列表
property_array_t properties; // 屬性列表
protocol_array_t protocols; // 協議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
...// 一些函數
};
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
複製代碼
發現class_rw_t
中還有一個被const
修飾的指針變量 ro
,是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];
... // 一些函數
};
複製代碼
說明:
其實,
rw
是readwrite
的意思,而ro
則是readonly
。
class_rw_t
要想獲取class_rw_t
指針地址,須要知道objc_class
的bits
指針地址,經過對objc_class
的結構分析得知,bits
指針地址是objc_class
首地址偏移32個字節(isa
+ superclass
+ cache
= 32字節)
也能夠從源碼得知如何拿到class_rw_t
指針
// objc_class結構體中
class_rw_t *data() {
return bits.data(); // bits是class_data_bits_t類型
}
// class_data_bits_t結構體中
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 64位下
#define FAST_DATA_MASK 0x00007ffffffffff8UL
複製代碼
在
64位下
,class_rw_t
指針地址是在[3, 46]數據段,因此也能夠用bits & FAST_DATA_MASK
計算出class_rw_t
指針地址。
接着筆者將經過一個例子來驗證class_rw_t
和class_ro_t
是否存儲了類的信息
給Person
類添加屬性、方法和協議,代碼以下
@protocol PersonProtocol <NSObject>
- (void)walk;
@end
@interface Person : NSObject <PersonProtocol> {
NSInteger _gender;
}
@property (nonatomic) NSString *name;
@property (nonatomic) NSInteger age;
+ (void)printMyClassName;
- (void)run;
@end
@implementation Person
+ (void)printMyClassName {
NSLog(@"my class name is Person");
}
- (void)run {
NSLog(@"I am running.");
}
- (void)walk {
NSLog(@"I am walking.");
}
@end
複製代碼
而後在合適的位置打上斷點
好了,準備工做完成,下面開始驗證
class_rw_t
驗證過程Person
類(lldb) x/5gx pcls
0x100002820: 0x001d8001000027f9 0x0000000100b39140
0x100002830: 0x00000001003dc250 0x0000000000000000
0x100002840: 0x0000000102237404
複製代碼
說明:
Person
類首地址是0x100002820
,所以,0x100002840
是其bits
地址(32字節就是0x20
,0x100002840
=0x100002820
+0x20
),bits
內容是0x0000000102237404
0x001d8001000027f9
是Person
類的isa
地址,指向Person元類
0x0000000100b39140
是Person
類的superclass
地址,也就是NSObject
類首地址0x00000001003dc250 0x0000000000000000
則是Person
類的cache
段
class_rw_t
// bits & FAST_DATA_MASK
(lldb) p (class_rw_t *)(0x0000000102237404 & 0x00007ffffffffff8)
(class_rw_t *) $1 = 0x0000000102237400
(lldb) p *$1
(class_rw_t) $2 = {
flags = 2148139008
version = 0
ro = 0x0000000100002788
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002608
arrayAndFlag = 4294977032
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002720
arrayAndFlag = 4294977312
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x00000001000025a8
arrayAndFlag = 4294976936
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}
複製代碼
這裏請你們留意一下class_rw_t
的幾個關鍵成員變量:
ro
地址是0x0000000100002788
methods
的list
地址是0x0000000100002608
properties
的list
地址是0x0000000100002720
protocols
的list
地址是0x0000000100002608
methods
目前來看,Person
類至少有6個實例方法,分別是run
、walk
以及name
和age
的getter
、setter
,還有1個類方法,即printMyClassName
,總計7個方法。
(lldb) p (method_list_t *)0x0000000100002608 // rw的methods的list地址
(method_list_t *) $7 = 0x0000000100002608
(lldb) p *$7
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 7
first = {
name = "walk"
types = 0x0000000100001e96 "v16@0:8"
imp = 0x0000000100001530 (CCTest`-[Person walk] at main.m:45)
}
}
}
複製代碼
正好是7個方法,讓咱們看看都是哪些(因爲method_list_t
繼承自entsize_list_tt
,能夠經過entsize_list_tt
的get()
函數一一打印)
(lldb) p $8.get(0)
(method_t) $9 = {
name = "walk"
types = 0x0000000100001e96 "v16@0:8"
imp = 0x0000000100001530 (CCTest`-[Person walk] at main.m:45)
}
(lldb) p $8.get(1)
(method_t) $10 = {
name = ".cxx_destruct"
types = 0x0000000100001e96 "v16@0:8"
imp = 0x0000000100001600 (CCTest`-[Person .cxx_destruct] at main.m:35)
}
(lldb) p $8.get(2)
(method_t) $11 = {
name = "name"
types = 0x0000000100001eb1 "@16@0:8"
imp = 0x0000000100001560 (CCTest`-[Person name] at main.m:27)
}
(lldb) p $8.get(3)
(method_t) $12 = {
name = "setName:"
types = 0x0000000100001f4b "v24@0:8@16"
imp = 0x0000000100001580 (CCTest`-[Person setName:] at main.m:27)
}
(lldb) p $8.get(4)
(method_t) $13 = {
name = "age"
types = 0x0000000100001f56 "q16@0:8"
imp = 0x00000001000015c0 (CCTest`-[Person age] at main.m:28)
}
(lldb) p $8.get(5)
(method_t) $14 = {
name = "run"
types = 0x0000000100001e96 "v16@0:8"
imp = 0x0000000100001500 (CCTest`-[Person run] at main.m:41)
}
(lldb) p $8.get(6)
(method_t) $15 = {
name = "setAge:"
types = 0x0000000100001f5e "v24@0:8q16"
imp = 0x00000001000015e0 (CCTest`-[Person setAge:] at main.m:28)
}
複製代碼
顯然,class_rw_t
的methods
確實包含了Person
類的所有實例方法,只是多了個.cxx_destruct
方法。.cxx_destruct
方法本來是爲了C++
對象析構的,ARC
借用了這個方法插入代碼實現了自動內存釋放的工做,關於其原理這裏略過不提。
思考:類方法
printMyClassName
哪裏去了?
properties
同理,Person
類至少有name
和age
這兩個屬性,且看
(lldb) p (property_list_t *)0x0000000100002720 // rw的properties的list地址
(property_list_t *) $18 = 0x0000000100002720
(lldb) p *$18
(property_list_t) $19 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 6
first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
}
}
(lldb) p $19.get(0)
(property_t) $20 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $19.get(1)
(property_t) $21 = (name = "age", attributes = "Tq,N,V_age")
(lldb) p $19.get(2)
(property_t) $22 = (name = "hash", attributes = "TQ,R")
(lldb) p $19.get(3)
(property_t) $23 = (name = "superclass", attributes = "T#,R")
(lldb) p $19.get(4)
(property_t) $24 = (name = "description", attributes = "T@\"NSString\",R,C")
(lldb) p $19.get(5)
(property_t) $25 = (name = "debugDescription", attributes = "T@\"NSString\",R,C")
複製代碼
顯然name
和age
存儲在properties
中。
多餘的
屬性
也不做贅述。
protocols
在驗證以前,先分析一下protocol_list_t
,這個結構體並非繼承自entsize_list_tt
的,其結構以下
struct protocol_list_t {
// count is 64-bit by accident.
uintptr_t count;
protocol_ref_t list[0]; // variable-size
... // 一些函數
}
複製代碼
注意到variable-size
這個註釋部分(可變大小),彷彿看到了但願
typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
const char *_demangledName;
property_list_t *_classProperties;
const char *demangledName();
... // 一些函數
};
複製代碼
protocol_ref_t
雖然未映射成protocol_t *
,不過應該能夠考慮一下強轉,實驗一下吧(此次是找到PersonProtocol
協議)
(lldb) p (protocol_list_t *)0x00000001000025a8 // rw的protocols的list地址
(protocol_list_t *) $26 = 0x00000001000025a8
(lldb) p *$26
(protocol_list_t) $27 = (count = 1, list = protocol_ref_t [] @ 0x00007fb5decb30f8)
(lldb) p (protocol_t *)$26->list[0]
(protocol_t *) $32 = 0x00000001000028a8
(lldb) p *$32
(protocol_t) $33 = {
objc_object = {
isa = {
cls = Protocol
bits = 4306735304
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 538341913
magic = 0
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
}
mangledName = 0x0000000100001d16 "PersonProtocol" // 出現了!!!
protocols = 0x0000000100002568
instanceMethods = 0x0000000100002580
classMethods = 0x0000000000000000
optionalInstanceMethods = 0x0000000000000000
optionalClassMethods = 0x0000000000000000
instanceProperties = 0x0000000000000000
size = 96
flags = 0
_extendedMethodTypes = 0x00000001000025a0
_demangledName = 0x0000000000000000
_classProperties = 0x0000000000000000
}
複製代碼
功夫不負有心人,最終驗證了class_rw_t
的protocols
中含有Person
類所遵循的PersonProtocol
協議。
到了這裏,class_rw_t
確實存儲了類的實例方法、屬性和遵循的協議了。
class_ro_t
驗證過程如今就剩下ro
了
class_ro_t
(lldb) p $1->ro
(const class_ro_t *) $38 = 0x0000000100002788
(lldb) p *$38
(const class_ro_t) $39 = {
flags = 388
instanceStart = 8
instanceSize = 32
reserved = 0
ivarLayout = 0x0000000100001d2e "\x11"
name = 0x0000000100001d0f "Person"
baseMethodList = 0x0000000100002608
baseProtocols = 0x00000001000025a8
ivars = 0x00000001000026b8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100002720
_swiftMetadataInitializer_NEVER_USE = {}
}
複製代碼
有沒有發現什麼!class_ro_t
的方法、屬性和協議的地址都與class_rw_t
的一致,既然指向的是同一塊內存空間,顯然class_ro_t
也存儲了Person
類的實例方法、屬性和協議。
與class_rw_t
不一樣的是,class_ro_t
多了一個ivars
列表,裏面存放的應該是Person
類的成員變量。
ivars
Person
類的成員變量有:_gender
、_name
和_age
所幸ivar_list_t
是繼承自entsize_list_tt
的,get()
函數又能夠用了。
(lldb) p $39.ivars
(const ivar_list_t *const) $40 = 0x00000001000026b8
(lldb) p *$40
(const ivar_list_t) $41 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 3
first = {
offset = 0x00000001000027e0
name = 0x0000000100001e83 "_gender"
type = 0x0000000100001f69 "q"
alignment_raw = 3
size = 8
}
}
}
(lldb) p $41.get(0)
(ivar_t) $42 = {
offset = 0x00000001000027e0
name = 0x0000000100001e83 "_gender"
type = 0x0000000100001f69 "q"
alignment_raw = 3
size = 8
}
(lldb) p $41.get(1)
(ivar_t) $43 = {
offset = 0x00000001000027e8
name = 0x0000000100001e8b "_name"
type = 0x0000000100001f6b "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $41.get(2)
(ivar_t) $44 = {
offset = 0x00000001000027f0
name = 0x0000000100001e91 "_age"
type = 0x0000000100001f69 "q"
alignment_raw = 3
size = 8
}
複製代碼
徹底符合預期,class_ro_t
確實存儲了Person
類的成員變量。
rw
和ro
的聯繫爲何class_rw_t
、class_ro_t
的方法、屬性和協議的地址一致?筆者在class_data_bits_t
結構體中的safe_ro()
函數中發現了端倪
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;
}
}
複製代碼
可見,rw
不必定是rw
,也多是ro
。實際上,在編譯期間,類的class_data_bits_t *bits
指針指向的是class_ro_t *
,而後在OC
運行時調用了realizeClassWithoutSwift()
(蘋果開源的objc4-756.2源碼
是realizeClassWithoutSwift()
,在此以前的版本是realizeClass()
方法),這個方法主要作的就是利用編譯期肯定的ro
來初始化rw
:
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 通常走這裏
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); // 給rw申請內存
rw->ro = ro; // 設置rw的ro
rw->flags = RW_REALIZED|RW_REALIZING; // 設置flags
cls->setData(rw); // 給cls設置正確的rw
}
... // 初始化 rw 的其餘字段,更新superclass、meta class
// Attach categories
methodizeClass(cls);
複製代碼
在代碼的最後,還調用了methodizeClass()
,其源碼以下
static void methodizeClass(Class cls) {
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
... // 打印信息
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
if (cls->isRootMetaclass()) {
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
... // 打印信息
if (cats) free(cats);
... // 打印信息
}
複製代碼
在這個方法裏,將類本身實現的方法(包括分類)、屬性和遵循的協議加載到 methods
、properties
和 protocols
列表中。
這就完美解釋了爲何運行時rw
和ro
的方法、屬性和協議相同。
rw
和ro
在運行時的不一樣之處目前爲止的驗證都是基於Person
類的現有結構,也就是在編譯期就肯定的,突出不了class_rw_t
和class_ro_t
的差別性。接下來筆者會用runtime
的api
在運行時爲Person
動態添加一個屬性fly()
方法,再來一試。
具體代碼以下:
void fly(id obj, SEL sel) {
NSLog(@"I am flying");
}
class_addMethod([Person class], NSSelectorFromString(@"fly"), (IMP)fly, "v@:");
複製代碼
再加一個打印方法,用於打印類的methods
void printMethods(Class cls) {
if (cls == nil) {
return ;
}
CCNSLog(@"------------ print %@ methods ------------", NSStringFromClass(cls));
uint32_t count;
Method *methods = class_copyMethodList(cls, &count);
for (uint32_t i = 0; i < count; i++) {
Method method = methods[i];
CCNSLog(@"名字:%@ -- 類型:%s", NSStringFromSelector(method_getName(method)), method_getTypeEncoding(method));
}
}
複製代碼
運行一下看效果,發現添加成功,如圖
先打印class_rw_t
,即
還有class_ro_t
對比後發現,二者的屬性、協議指針地址未發生變化,可是方法的指針地址不同了。 因爲class_rw_t
是運行時才初始化的,而class_ro_t
在編譯期間就肯定了,所以能夠猜想新增的fly
方法存儲在class_rw_t
的methods
指針上,class_ro_t
的baseMethodList
指針從編譯期以後就未發生改變。
下面繼續驗證,首先看class_ro_t
的方法列表
OK,編譯期就肯定的方法都在,而且沒有fly
方法,也就是說class_ro_t
的方法列表在運行時基本沒變。
class_ro_t
的屬性列表、成員變量列表、協議在運行時都沒有發生改變。感興趣的同窗能夠本身嘗試驗證一下。
接着看class_rw_t
的方法列表
class_rw_t
的methods
裏面數據竟然都沒有了? 沒辦法,這裏暫時留個坑吧,筆者也不知道緣由。
關於類的結構,咱們瞭解到:
objc_object
結構體,也就是類也是對象,即萬物是對象。Class
類型的成員變量isa
,Class
是objc_class
結構體類型的指針變量,內部有4個成員變量,即
isa
:類型是isa_t
,詳細請戳 OC源碼分析之isasuperclass
:類型是Class
,表示繼承關係,指向類的父類cache
:類型是cache_t
,表示緩存,用於緩存指針和 vtable
,加速方法的調用bits
:類型是class_data_bits_t
,用於存儲類的數據(類的方法、屬性、遵循的協議等信息),其長度在64位
CPU下爲8字節,是個指針,指向class_rw_t *
class_rw_t
和class_ro_t
總結class_ro_t
存儲了類在編譯期肯定的方法(包括其分類的)、成員變量、屬性以及遵循的協議等信息,在運行時不會發生變化。編譯期,類的bits
指針指向的是class_ro_t
指針(即此時的class_rw_t *
其實是class_ro_t *
)。
realizeClassWithoutSwift()
執行以後,class_rw_t
纔會被初始化,同時存儲類的方法、屬性以及遵循的協議,實際上,class_rw_t
和class_ro_t
二者的方法列表(或屬性列表、協議列表)的指針是相同的。class_rw_t
的屬性列表、方法列表指針,但class_ro_t
對應的屬性列表、方法列表不會變。一個待解決的坑:經過運行時添加方法(或屬性、協議)改變了 class_rw_t 對應的方法列表(或屬性列表、協議列表)的指針後,不知道爲何竟然在 class_rw_t 的方法列表(或屬性列表、協議列表)上找不到新增的方法(或屬性、協議)了。這個問題困擾筆者很久了,在這裏很是歡迎同窗在評論區留言討論。
(Person類的類方法printMyClassName()
)
// 1. 獲取 Person元類
(lldb) x/4gx pcls
0x100002820: 0x001d8001000027f9 0x0000000100b39140
0x100002830: 0x00000001003dc250 0x0000000000000000
(lldb) p/x 0x001d8001000027f9 & 0x00007ffffffffff8
(long) $50 = 0x00000001000027f8
(lldb) po 0x00000001000027f8
Person // Person元類
// 2. 獲取 Person元類 的 bits
(lldb) x/5gx 0x00000001000027f8
0x1000027f8: 0x001d800100b390f1 0x0000000100b390f0
0x100002808: 0x0000000102237440 0x0000000100000003
0x100002818: 0x00000001022373a0 // Person元類 的 bits
// 3. 獲取 Person元類 的 class_rw_t
(lldb) p (class_rw_t *)(0x00000001022373a0 & 0x00007ffffffffff8)
(class_rw_t *) $52 = 0x00000001022373a0
// 4. 驗證 Person元類 的 methods
(lldb) p $52->methods
(method_array_t) $55 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002270
arrayAndFlag = 4294976112
}
}
}
(lldb) p (method_list_t *)0x0000000100002270
(method_list_t *) $56 = 0x0000000100002270
(lldb) p *$56
(method_list_t) $57 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "printMyClassName" // 成功找到 Person類的類方法
types = 0x0000000100001e96 "v16@0:8"
imp = 0x00000001000014d0 (CCTest`+[Person printMyClassName] at main.m:37)
}
}
}
複製代碼
結論:類方法 存儲 在類的元類上,且位於元類的class_ro_t
的baseMethodList
指針上(或在class_rw_t
的methods
指針上)
深刻解析 ObjC 中方法的結構(by Draveness)
github
上,請戳 objc4-756.2源碼