技 術(shù) 文 章 / 超 人
Runtime(運(yùn)行時(shí)機(jī)制)概念
Object-C 是面向?qū)ο蟮恼Z(yǔ)言,C是面向結(jié)構(gòu)也就是面向過(guò)程的語(yǔ)言。Object-C是基于C基礎(chǔ)上的語(yǔ)言。而Runtime為Object-C添加了面向?qū)ο蟮奶匦裕瑢㈧o態(tài)語(yǔ)言編譯期和鏈接期做的事情放到了運(yùn)行時(shí)來(lái)處理。所以Runtime是每一個(gè)IOS開(kāi)發(fā)人員都應(yīng)該了解的。Runtime是一套純C的API。
Objective-C不僅需要一個(gè)編譯器,還需要一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)執(zhí)行編譯的代碼。對(duì)于Objective-C來(lái)說(shuō),這個(gè)運(yùn)行時(shí)系統(tǒng)就像一個(gè)操作系統(tǒng)一樣:它讓所有的工作可以正常的運(yùn)行。這個(gè)運(yùn)行時(shí)系統(tǒng)即Objc Runtime。Objc Runtime其實(shí)是一個(gè)Runtime庫(kù),它基本上是用C和匯編寫的,這個(gè)庫(kù)使得C語(yǔ)言有了面向?qū)ο蟮哪芰Α?/p>
Runtime庫(kù)主要做下面幾件事:
封裝
:在這個(gè)庫(kù)中,對(duì)象可以用C語(yǔ)言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來(lái)實(shí)現(xiàn),另外再加上了一些額外的特性。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后,我們就可以在程序運(yùn)行時(shí)創(chuàng)建,檢查,修改類、對(duì)象和它們的方法了。
找出方法的最終執(zhí)行代碼
:當(dāng)程序執(zhí)行[object doSomething]時(shí),會(huì)向消息接收者(object)發(fā)送一條消息(doSomething),runtime會(huì)根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)。這將在后面詳細(xì)介紹。
對(duì)于C語(yǔ)言而言函數(shù)的調(diào)用在編譯時(shí)就會(huì)決定調(diào)用那一個(gè)函數(shù)。而OC在編譯時(shí)并不能決定調(diào)用那一個(gè)函數(shù),只有在運(yùn)行時(shí)才能確定調(diào)用那一個(gè)函數(shù),舉個(gè)簡(jiǎn)單的例子:在日常開(kāi)發(fā)時(shí),在.h中聲明了某個(gè)方法,而.m中卻沒(méi)有實(shí)現(xiàn)該方法,但編譯項(xiàng)目卻不會(huì)報(bào)錯(cuò),因?yàn)镺C編譯時(shí)并沒(méi)有確定.h中聲明但方法具體調(diào)用.m的那一個(gè)函數(shù),當(dāng)然也就不知道該函數(shù)有沒(méi)有實(shí)現(xiàn)了,而在運(yùn)行時(shí)調(diào)用該函數(shù)時(shí)就會(huì)報(bào)錯(cuò),因?yàn)檫\(yùn)行時(shí)才會(huì)確定具體調(diào)用那一個(gè)函數(shù),函數(shù)沒(méi)有實(shí)現(xiàn)那么就會(huì)報(bào)錯(cuò)。OC是動(dòng)態(tài)調(diào)用,正因?yàn)閯?dòng)態(tài)性,所以在運(yùn)行時(shí)可以修改調(diào)用方法的指針。
Runtime其實(shí)有兩個(gè)版本: “modern” 和 “l(fā)egacy”。我們現(xiàn)在用的 Objective-C 2.0 采用的是現(xiàn)行 (Modern) 版的 Runtime 系統(tǒng),只能運(yùn)行在 iOS 和 macOS 10.5 之后的 64 位程序中。而 maxOS 較老的32位程序仍采用 Objective-C 1 中的(早期)Legacy 版本的 Runtime 系統(tǒng)。這兩個(gè)版本最大的區(qū)別在于當(dāng)你更改一個(gè)類的實(shí)例變量的布局時(shí),在早期版本中你需要重新編譯它的子類,而現(xiàn)行版就不需要。
Runtime 基本是用 C 和匯編寫的,可見(jiàn)蘋果為了動(dòng)態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋果維護(hù)的開(kāi)源代碼。蘋果和GNU各自維護(hù)一個(gè)開(kāi)源的 runtime 版本,這兩個(gè)版本之間都在努力的保持一致。
基礎(chǔ)知識(shí)補(bǔ)充
-
Class
:定義Objective-C類 -
Ivar
:定義對(duì)象的實(shí)例變量,包括類型和名字。 -
Protocol
:定義正式協(xié)議。 -
objc_property_t
:定義屬性。叫這個(gè)名字可能是為了防止和Objective-C 1.0中的用戶類型沖突,那時(shí)候還沒(méi)有屬性。 -
Method
:定義對(duì)象方法或類方法。這個(gè)類型提供了方法的名字(就是選擇器)、參數(shù)數(shù)量和類型,以及返回值(這些信息合起來(lái)稱為方法的簽名),還有一個(gè)指向代碼的函數(shù)指針(也就是方法的實(shí)現(xiàn))。 -
SEL
:定義選擇器。選擇器是方法名的唯一標(biāo)識(shí)符。 -
IMP
:定義方法實(shí)現(xiàn)。這只是一個(gè)指向某個(gè)函數(shù)的指針,該函數(shù)接受一個(gè)對(duì)象、一個(gè)選擇器和一個(gè)可變長(zhǎng)參數(shù)列表(varargs),返回一個(gè)對(duì)象 -
unsigned
signed和unsigned不會(huì)改變類型長(zhǎng)度,僅表示最高位是否為符號(hào)位,其中unsigned表示大于等于0的正數(shù)
受限于內(nèi)存分配的機(jī)制,一個(gè)NSObject對(duì)象會(huì)分配16byte的內(nèi)存空間,但在64位系統(tǒng)下只使用了8byte,在32位系統(tǒng)下只使用了4byte.
獲取Obj-C指針?biāo)赶虻膬?nèi)存的大小
malloc_size((__brige const void *)obj);
一個(gè)NSObject實(shí)例對(duì)象成員變量所占的大小是8byte。
獲取Object對(duì)象大小方法
Class_getInstanceSize([NSObject class])
//其本質(zhì)是
size_t class_getInstanceSize(Class cls)
{
if(!cls)return 0;
return cls->alignedInstanceSize();
}
看到這里。你應(yīng)該對(duì)Runtime有一個(gè)初步的了解了。建議先看看Runtime的方法,在看下面的內(nèi)容。這樣才會(huì)容易明白實(shí)例代碼的用意。請(qǐng)查看Runtime方法說(shuō)明
使用Runtime
1.導(dǎo)入頭文件 #import <objc/message.h>
2.OC 解決消息機(jī)制方法提示步驟[查找build setting-> 搜索msg->objc_msgSend(YES --> NO]
Runtime最主要是消息機(jī)制,OC在編譯時(shí)會(huì)轉(zhuǎn)化為C,轉(zhuǎn)化為: objc_msgSend(receiver, selector),何以證明?新建一個(gè)類 MyClass,其.m文件如下:
#import "MyClass.h"
@implementation MyClass
-(instancetype)init{
if (self = [super init]) {
[self showUserName];
}
return self;
}
-(void)showUserName{
NSLog(@"Dave Ping");
}
然后使用 clang 重寫命令:
$ clang -rewrite-objc MyClass.m
然后在同一目錄下會(huì)多出一個(gè) MyClass.cpp 文件,雙擊打開(kāi),可以看到 init 方法已經(jīng)被編譯器轉(zhuǎn)化為下面這樣:
static instancetype _I_MyClass_init(MyClass * self, SEL _cmd) {
if (self = ((MyClass *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MyClass"))}, sel_registerName("init"))) {
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"));
}
return self;
}
我們要找的就是它:
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"))
objc_msgSend 函數(shù)被定義在 objc/message.h 目錄下,其函數(shù)原型是醬紫滴:
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )
該函數(shù)有兩個(gè)參數(shù),一個(gè) id 類型,一個(gè) SEL 類型。
SEL
SEL 被定義在 objc/objc.h 目錄下:
typedef struct objc_selector *SEL;
其實(shí)它就是個(gè)映射到方法的C字符串,你可以用 Objective-C 編譯器命令 @selector() 或者 Runtime 系統(tǒng)的 sel_registerName 函數(shù)來(lái)獲得一個(gè) SEL 類型的方法選擇器。簡(jiǎn)單來(lái)說(shuō)就是方法名稱
id
與 SEL 一樣,id 也被定義在 objc/objc.h 目錄下:
typedef struct objc_object *id;
id 是一個(gè)結(jié)構(gòu)體指針類型,它可以指向 Objective-C 中的任何對(duì)象。objc_object 結(jié)構(gòu)體定義如下:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};
我們通常所說(shuō)的對(duì)象,就長(zhǎng)這個(gè)樣子,這個(gè)結(jié)構(gòu)體只有一個(gè)成員變量 isa,對(duì)象可以通過(guò) isa 指針找到其所屬的類。isa 是一個(gè) Class 類型的成員變量,那么 Class 又是什么呢?
isa 等價(jià)于 isKindOf方法
- 實(shí)例對(duì)象 isa 指向類對(duì)象
- 類對(duì)象指 isa 向元類對(duì)象
- 元類對(duì)象的 isa 指向元類的基類
isa 有兩種類型
- 純指針:指向內(nèi)存地址
- NON_POINTER_ISA:除了內(nèi)存地址,還存有一些其他信息
Class
Class 也是一個(gè)結(jié)構(gòu)體指針類型:
typedef struct objc_class *Class;
objc_class 結(jié)構(gòu)體是醬紫滴:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
我們通常說(shuō)的類就長(zhǎng)這樣子:
-
Class
: Objective-C類是由Class類型來(lái)表示的,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針 -
super_class
:指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。 -
name
:是類名。 -
version
:我們可以使用這個(gè)字段來(lái)提供類的版本信息。這對(duì)于對(duì)象的序列化非常有用,它可以讓我們識(shí)別出不同類定義版本中實(shí)例變量布局的改變。 -
info
:是類的詳情。 -
instance_size
:是該類的實(shí)例對(duì)象的大小。 -
ivars
:指向該類的成員變量列表。 -
methodLists
:指向該類的實(shí)例方法列表,它將方法選擇器和方法實(shí)現(xiàn)地址聯(lián)系起來(lái)。methodLists 是指向 ·objc_method_list 指針的指針,也就是說(shuō)可以動(dòng)態(tài)修改 *methodLists 的值來(lái)添加成員方法,這也是 Category 實(shí)現(xiàn)的原理,同樣解釋了 Category 不能添加屬性的原因。 -
cache
:用于緩存最近使用的方法。一個(gè)接收者對(duì)象接收到一個(gè)消息時(shí),它會(huì)根據(jù)isa指針去查找能夠響應(yīng)這個(gè)消息的對(duì)象。在實(shí)際使用中,這個(gè)對(duì)象只有一部分方法是常用的,很多方法其實(shí)很少用或者根本用不上。這種情況下,如果每次消息來(lái)時(shí),我們都是methodLists中遍歷一遍,性能勢(shì)必很差。這時(shí),cache就派上用場(chǎng)了。在我們每次調(diào)用過(guò)一個(gè)方法后,這個(gè)方法就會(huì)被緩存到cache列表中,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找,如果cache沒(méi)有,才去methodLists中查找方法。這樣,對(duì)于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率。 -
protocols
:指向該類的協(xié)議列表。
objc_object與id
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
可以看到,這個(gè)結(jié)構(gòu)體只有一個(gè)字體,即指向其類的isa指針。這樣,當(dāng)我們向一個(gè)Objective-C對(duì)象發(fā)送消息時(shí),運(yùn)行時(shí)庫(kù)會(huì)根據(jù)實(shí)例對(duì)象的isa指針找到這個(gè)實(shí)例對(duì)象所屬的類。Runtime庫(kù)會(huì)在類的方法列表及父類的方法列表中去尋找與消息對(duì)應(yīng)的selector指向的方法。找到后即運(yùn)行這個(gè)方法。
當(dāng)創(chuàng)建一個(gè)特定類的實(shí)例對(duì)象時(shí),分配的內(nèi)存包含一個(gè)objc_object數(shù)據(jù)結(jié)構(gòu),然后是類的實(shí)例變量的數(shù)據(jù)。NSObject類的alloc和allocWithZone:方法使用函數(shù)class_createInstance來(lái)創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu)。
另外還有我們常見(jiàn)的id,它是一個(gè)objc_object結(jié)構(gòu)類型的指針。它的存在可以讓我們實(shí)現(xiàn)類似于C++中泛型的一些操作。該類型的對(duì)象可以轉(zhuǎn)換為任何一種對(duì)象,有點(diǎn)類似于C語(yǔ)言中void *指針類型的作用。
objc_cache
上面提到了objc_class結(jié)構(gòu)體中的cache字段,它用于緩存調(diào)用過(guò)的方法。這個(gè)字段是一個(gè)指向objc_cache結(jié)構(gòu)體的指針,其定義如下:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
mask
:一個(gè)整數(shù),指定分配的緩存bucket的總數(shù)。在方法查找過(guò)程中,Objective-C runtime使用這個(gè)字段來(lái)確定開(kāi)始線性查找數(shù)組的索引位置。指向方法selector的指針與該字段做一個(gè)AND位操作(index = (mask & selector))。這可以作為一個(gè)簡(jiǎn)單的hash散列算法。occupied
:一個(gè)整數(shù),指定實(shí)際占用的緩存bucket的總數(shù)。buckets
:指向Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個(gè)數(shù)組可能包含不超過(guò)mask+1個(gè)元素。需要注意的是,指針可能是NULL,表示這個(gè)緩存bucket沒(méi)有被占用,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長(zhǎng)
元類(Meta Class)
例:
NSArray *array = [NSArray array];
這個(gè)例子中,+array消息發(fā)送給了NSArray類,而這個(gè)NSArray也是一個(gè)對(duì)象。既然是對(duì)象,那么它也是一個(gè)objc_object指針,它包含一個(gè)指向其類的一個(gè)isa指針。那么這些就有一個(gè)問(wèn)題了,這個(gè)isa指針指向什么呢?為了調(diào)用+array方法,這個(gè)類的isa指針必須指向一個(gè)包含這些類方法的一個(gè)objc_class結(jié)構(gòu)體。這就引出了meta-class的概念:
meta-class是一個(gè)類對(duì)象的類。
當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí),runtime會(huì)在這個(gè)對(duì)象所屬的這個(gè)類的方法列表中查找方法;而向一個(gè)類發(fā)送消息時(shí),會(huì)在這個(gè)類的meta-class的方法列表中查找。
meta-class之所以重要,是因?yàn)樗鎯?chǔ)著一個(gè)類的所有類方法。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class,因?yàn)槊總€(gè)類的類方法基本不可能完全相同。
再深入一下,meta-class也是一個(gè)類,也可以向它發(fā)送一個(gè)消息,那么它的isa又是指向什么呢?為了不讓這種結(jié)構(gòu)無(wú)限延伸下去,Objective-C的設(shè)計(jì)者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。這樣就形成了一個(gè)完美的閉環(huán)。
IMP
IMP就是Implementation的縮寫,顧名思義,它是指向一個(gè)方法實(shí)現(xiàn)的指針,每一個(gè)方法都有一個(gè)對(duì)應(yīng)的IMP,所以,我們可以直接調(diào)用方法的IMP指針,來(lái)避免方法調(diào)用死循環(huán)的問(wèn)題。
IMP的定義如下:
if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
else
typedef id (*IMP)(id, SEL, ...);
endif
實(shí)際上直接調(diào)用一個(gè)方法的IMP指針的效率是高于調(diào)用方法本身的,所以,如果你有一個(gè)合適的時(shí)機(jī)獲取到方法的IMP的話,你可以試著調(diào)用它。
objc_msgSend說(shuō)明
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )
首先,Runtime 系統(tǒng)會(huì)把方法調(diào)用轉(zhuǎn)化為消息發(fā)送,即 objc_msgSend,并且把方法的調(diào)用者,和方法選擇器,當(dāng)做參數(shù)傳遞過(guò)去.
此時(shí),方法的調(diào)用者會(huì)通過(guò) isa 指針來(lái)找到其所屬的類,然后在 cache 或者 methodLists 中查找該方法,找得到就跳到對(duì)應(yīng)的方法去執(zhí)行。
如果在類中沒(méi)有找到該方法,則通過(guò) super_class 往上一級(jí)超類查找(如果一直找到 NSObject 都沒(méi)有找到該方法的話,這種情況,我們放到后面消息轉(zhuǎn)發(fā)的時(shí)候再說(shuō))。
前面我們說(shuō) methodLists 指向該類的實(shí)例方法列表,實(shí)例方法即-方法,那么類方法(+方法)存儲(chǔ)在哪兒呢?類方法被存儲(chǔ)在元類中,Class 通過(guò) isa 指針即可找到其所屬的元類。
上圖實(shí)線是 super_class 指針,虛線是 isa 指針。根元類的超類是NSObject,而 isa 指向了自己。NSObject 的超類為 nil,也就是它沒(méi)有超類。
前面我們使用 clang 重寫命令,看到 Runtime 是如何將方法調(diào)用轉(zhuǎn)化為消息發(fā)送的。我們也可以依樣畫葫蘆,來(lái)學(xué)習(xí)使用一下 objc_msgSend。新建一個(gè)類 TestClass,添加如下方法:
-(void)showAge{
NSLog(@"24");
}
-(void)showName:(NSString *)aName{
NSLog(@"name is %@",aName);
}
-(void)showSizeWithWidth:(float)aWidth andHeight:(float)aHeight{
NSLog(@"size is %.2f * %.2f",aWidth, aHeight);
}
-(float)getHeight{
return 187.5f;
}
-(NSString *)getInfo{
return @"Hi, my name is Dave Ping, I'm twenty-four years old in the year, I like apple, nice to meet you.";
}
我們可以像下面這樣,使用 objc_msgSend 依次調(diào)用這些方法:
Class *objct = NSClassFromStrin[@"TestClass"];
((void (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("showAge"));
((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("showName:"), @"Dave Ping");
((void (*) (id, SEL, float, float)) objc_msgSend) (objct, sel_registerName("showSizeWithWidth:andHeight:"), 110.5f, 200.0f);
float f = ((float (*) (id, SEL)) objc_msgSend_fpret) (objct, sel_registerName("getHeight"));
NSLog(@"height is %.2f",f);
NSString *info = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getInfo"));
NSLog(@"%@",info);
也許你已經(jīng)注意到,objc_msgSend 在使用時(shí)都被強(qiáng)制轉(zhuǎn)換了一下,這是因?yàn)?objc_msgSend 函數(shù)可以hold住各種不同的返回值以及多個(gè)參數(shù),但默認(rèn)情況下是沒(méi)有參數(shù)和返回值的。如果我們把調(diào)用 showAge 方法改成這樣:
objc_msgSend(objct, sel_registerName("showAge"));
Xcode 就會(huì)報(bào)錯(cuò):
Too many arguments to function call, expected 0, have 2.
objc_msgSendSuper
編譯器會(huì)根據(jù)情況在 objc_msgSend,objc_msgSend_stret,objc_msgSendSuper,objc_msgSendSuper_stret 或 objc_msgSend_fpret 五個(gè)方法中選擇一個(gè)來(lái)調(diào)用。如果消息是傳遞給超類,那么會(huì)調(diào)用 objc_msgSendSuper 方法,如果消息返回值是數(shù)據(jù)結(jié)構(gòu),就會(huì)調(diào)用 objc_msgSendSuper_stret 方法,如果返回值是浮點(diǎn)數(shù),則調(diào)用 objc_msgSend_fpret 方法。
這里我們重點(diǎn)說(shuō)一下 objc_msgSendSuper,objc_msgSendSuper 函數(shù)原型如下:
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
當(dāng)我們調(diào)用 [super selector] 時(shí),Runtime 會(huì)調(diào)用 objc_msgSendSuper 方法,objc_msgSendSuper 方法有兩個(gè)參數(shù),super 和 op,Runtime 會(huì)把 selector 方法選擇器賦值給 op。而 super 是一個(gè) objc_super 結(jié)構(gòu)體指針,objc_super 結(jié)構(gòu)體定義如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
Runtime 會(huì)創(chuàng)建一個(gè) objc_spuer 結(jié)構(gòu)體變量,將其地址作為參數(shù)(super)傳遞給 objc_msgSendSuper,并且將 self 賦值給 receiver:super—>receiver=self.
舉個(gè)栗子,問(wèn)下面的代碼輸出什么:
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
答案是全部輸出 Son。
使用 clang 重寫命令,發(fā)現(xiàn)上述代碼被轉(zhuǎn)化為:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));
當(dāng)調(diào)用 [super class] 時(shí),會(huì)轉(zhuǎn)換成 objc_msgSendSuper 函數(shù):
- 第一步先構(gòu)造 objc_super 結(jié)構(gòu)體,結(jié)構(gòu)體第一個(gè)成員就是 self。第二個(gè)成員是 (id)class_getSuperclass(objc_getClass(“Son”)).
第二步是去 Father 這個(gè)類里去找 - (Class)class,沒(méi)有,然后去 NSObject 類去找,找到了。最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class)) 去調(diào)用,此時(shí)已經(jīng)和 [self class] 調(diào)用相同了,所以兩個(gè)輸出結(jié)果都是 Son。
Associated對(duì)象關(guān)聯(lián)
對(duì)象關(guān)聯(lián)允許開(kāi)發(fā)者對(duì)已經(jīng)存在的類在 Category 中添加自定義的屬性:
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
object 是源對(duì)象
value 是被關(guān)聯(lián)的對(duì)象
key 是關(guān)聯(lián)的鍵,objc_getAssociatedObject 方法通過(guò)不同的 key 即可取出對(duì)應(yīng)的被關(guān)聯(lián)對(duì)象
policy 是一個(gè)枚舉值,表示關(guān)聯(lián)對(duì)象的行為,從命名就能看出各個(gè)枚舉值的含義:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /** 指定對(duì)關(guān)聯(lián)對(duì)象的弱引用. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /** 指定對(duì)關(guān)聯(lián)對(duì)象的強(qiáng)引.不是atomically */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /** 指定復(fù)制的關(guān)聯(lián)對(duì)象.* 不是 atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /** 指定對(duì)關(guān)聯(lián)對(duì)象的強(qiáng)引用.* 是 atomically. */
OBJC_ASSOCIATION_COPY = 01403 /** 指定復(fù)制的關(guān)聯(lián)對(duì)象. * 是 atomically. */
};
要取出被關(guān)聯(lián)的對(duì)象使用 objc_getAssociatedObject 方法即可,要?jiǎng)h除一個(gè)被關(guān)聯(lián)的對(duì)象,使用 objc_setAssociatedObject 方法將對(duì)應(yīng)的 key 設(shè)置成 nil 即可:
objc_setAssociatedObject(self, associatedKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_removeAssociatedObjects 方法將會(huì)移除源對(duì)象中所有的關(guān)聯(lián)對(duì)象.
舉個(gè)栗子,假如我們要給 UIButton 添加一個(gè)監(jiān)聽(tīng)單擊事件的 block 屬性,新建 UIButton 的 Category,其.m文件如下:
#import "UIButton+ClickBlock.h"
static const void *associatedKey = "associatedKey";
@implementation UIButton (ClickBlock)
//Category中的屬性,只會(huì)生成setter和getter方法,不會(huì)生成成員變量
-(void)setClick:(clickBlock)click{
/* 建立click的關(guān)聯(lián),暗號(hào)為associatedKey */
objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
/* 先移除監(jiān)聽(tīng),防止重復(fù)監(jiān)聽(tīng) */
[self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
/* 判斷是否實(shí)現(xiàn)了click,沒(méi)有實(shí)現(xiàn)就不用注冊(cè) */
if (click) {
/* 注冊(cè)監(jiān)聽(tīng) */
[self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
}
}
-(clickBlock)click{
/* 當(dāng)使用click屬性時(shí),直接獲取關(guān)聯(lián)對(duì)象當(dāng)click,相當(dāng)于button.click使用時(shí)獲取當(dāng)對(duì)象就關(guān)聯(lián)對(duì)象 */
return objc_getAssociatedObject(self, associatedKey);
}
-(void)buttonClick{
/* 收到監(jiān)聽(tīng)消息后回調(diào)到click對(duì)象中 */
if (self.click) {
self.click();
}
}
@end
然后在代碼中,就可以使用 UIButton 的屬性來(lái)監(jiān)聽(tīng)單擊事件了:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.view.bounds;
[self.view addSubview:button];
button.click = ^{
NSLog(@"buttonClicked");
};
Runtime案例使用
自動(dòng)歸檔
一般到歸檔寫法時(shí)這樣的:
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.ID forKey:@"ID"];
}
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self.ID = [aDecoder decodeObjectForKey:@"ID"];
self.name = [aDecoder decodeObjectForKey:@"name"];
}
return self;
}
那么問(wèn)題來(lái)了,如果當(dāng)前 Model 有100個(gè)屬性的話,就需要寫100行這種代碼:
[aCoder encodeObject:self.name forKey:@"name"];
想想都頭疼,通過(guò) Runtime 我們就可以輕松解決這個(gè)問(wèn)題:
1.使用 class_copyIvarList 方法獲取當(dāng)前 Model 的所有成員變量.
2.使用 ivar_getName 方法獲取成員變量的名稱.
3.通過(guò) KVC 來(lái)讀取 Model 的屬性值(encodeWithCoder:),以及給 Model 的屬性賦值(initWithCoder:).
舉個(gè)栗子,新建一個(gè) Model 類,其.m文件如下
#import "TestModel.h"
#import #import @implementation TestModel
- (void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
// 注意kvc的特性是,如果能找到key這個(gè)屬性的setter方法,則調(diào)用setter方法
// 如果找不到setter方法,則查找成員變量key或者成員變量_key,并且為其賦值
// 所以這里不需要再另外處理成員變量名稱的“_”前綴
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
}
return self;
}
@end
Ivar 定義對(duì)象的實(shí)例變量,包括類型和名字
是objc_ivar的指針,包含變量名,變量類型等成員。ivars是一個(gè)數(shù)組,數(shù)組中每個(gè)元素是指向Ivar(變量信息)的指針
- Ivar定義:
typedef objc_ivar * Ivar;
- Ivar結(jié)構(gòu)體:
struct objc_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
#ifdef __LP64__
int space;
#endif
}
- Ivar的相關(guān)操作
//獲取Ivar的名稱
const char *ivar_getName(Ivar v);
//獲取Ivar的類型編碼,
const char *ivar_getTypeEncoding(Ivar v)
//通過(guò)變量名稱獲取類中的實(shí)例成員變量
Ivar class_getInstanceVariable(Class cls, const char *name)
//通過(guò)變量名稱獲取類中的類成員變量
Ivar class_getClassVariable(Class cls, const char *name)
//獲取指定類的Ivar列表及Ivar個(gè)數(shù)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//獲取實(shí)例對(duì)象中Ivar的值
id object_getIvar(id obj, Ivar ivar)
//設(shè)置實(shí)例對(duì)象中Ivar的值
void object_setIvar(id obj, Ivar ivar, id value)
字典轉(zhuǎn)模型
一般的字典轉(zhuǎn)模型是這樣滴:
-(instancetype)initWithDictionary:(NSDictionary *)dict{
if (self = [super init]) {
self.age = dict[@"age"];
self.name = dict[@"name"];
}
return self;
}
可想而知,遇到的問(wèn)題跟歸檔時(shí)候一樣(后來(lái)使用MJExtension),這里我們稍微來(lái)學(xué)習(xí)一下其中原理,字典轉(zhuǎn)模型的時(shí)候:
1.根據(jù)字典的 key 生成 setter 方法
2.使用 objc_msgSend 調(diào)用 setter 方法為 Model 的屬性賦值(或者 KVC)
模型轉(zhuǎn)字典的時(shí)候:
1.調(diào)用 class_copyPropertyList 方法獲取當(dāng)前 Model 的所有屬性
2.調(diào)用 property_getName 獲取屬性名稱
3.根據(jù)屬性名稱生成 getter 方法
4.使用 objc_msgSend 調(diào)用 getter 方法獲取屬性值(或者 KVC)
代碼如下:
#import "NSObject+KeyValues.h"
#import @implementation NSObject (KeyValues)
//字典轉(zhuǎn)模型
+(id)objectWithKeyValues:(NSDictionary *)aDictionary{
/* 創(chuàng)建model */
id objc = [[self alloc] init];
/* 遍歷字典里所有的key */
for (NSString *key in aDictionary.allKeys) {
/* 取出key里的值 */
id value = aDictionary[key];
/*根據(jù)key在self中獲取該key的屬性指針property,判斷當(dāng)前屬性是不是Model的*/
objc_property_t property = class_getProperty(self, key.UTF8String);
/* outCount存放你定義屬性的個(gè)數(shù) */
unsigned int outCount = 0;
/* 根據(jù)指針動(dòng)態(tài)取出對(duì)象所有屬性 */
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);
/* 取出對(duì)象首個(gè)屬性 */
objc_property_attribute_t attribute = attributeList[0];
/* 取出屬性值 */
NSString *typeString = [NSString stringWithUTF8String:attribute.value];
/* 判斷取出的屬性類型是否是當(dāng)前這個(gè)model類 */
if ([typeString isEqualToString:[NSString stringWithFormat:@"@\"%@\"",self.class]]) {
/* 如果model中有一個(gè)屬性也是model,即嵌套model。就遍歷一次屬性model */
value = [self objectWithKeyValues:value];
}
/**********************/
//生成setter方法,并用objc_msgSend調(diào)用
NSString *methodName = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];
/* 獲取方法名 */
SEL setter = sel_registerName(methodName.UTF8String);
/* 判斷是否有該方法名 */
if ([objc respondsToSelector:setter]) {
/* 如果有就發(fā)送消息到該方法 */
((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
}
}
return objc;
}
//模型轉(zhuǎn)字典
-(NSDictionary *)keyValuesWithObject{
/* outCount存放你定義屬性的個(gè)數(shù) */
unsigned int outCount = 0;
/* 取出模型中所有到屬性指針,并獲取屬性個(gè)數(shù) */
objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
/* 創(chuàng)建一個(gè)字典 */
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
/* 根據(jù)屬性數(shù)量遍歷 */
for (int i = 0; i < outCount; i ++) {
/* 取出每個(gè)屬性指針 */
objc_property_t property = propertyList[i];
//生成getter方法,并用objc_msgSend調(diào)用
const char *propertyName = property_getName(property);
/* 獲取方法名 */
SEL getter = sel_registerName(propertyName);
/* 判斷是否存在該方法 */
if ([self respondsToSelector:getter]) {
/* 如果存在就從該方法獲取值 */
id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);
/*判斷當(dāng)前屬性是不是Model*/
if ([value isKindOfClass:[self class]] && value) {
/* 如果是就在遍歷一次屬性model */
value = [value keyValuesWithObject];
}
/**********************/
/* 判斷是否有值 */
if (value) {
/* 獲取屬性名稱到字符串 */
NSString *key = [NSString stringWithUTF8String:propertyName];
/* 存值 */
[dict setObject:value forKey:key];
}
}
}
return dict;
}
@end
字典轉(zhuǎn)模型中的知識(shí)點(diǎn)補(bǔ)充
-
objc_property_t :屬性指針
類型:
typedef struct property_t *objc_property_t;
@property
標(biāo)記了類中的屬性,這個(gè)不必多說(shuō)大家都很熟悉,它是一個(gè)指向objc_property 結(jié)構(gòu)體的指針.可以通過(guò) class_copyPropertyList 和 protocol_copyPropertyList 方法來(lái)獲取類和協(xié)議中的屬性
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
返回類型為指向指針的指針,哈哈,因?yàn)閷傩粤斜硎莻€(gè)數(shù)組,每個(gè)元素內(nèi)容都是一個(gè) objc_property_t 指針,而這兩個(gè)函數(shù)返回的值是指向這個(gè)數(shù)組的指針。
可以用 property_getName 函數(shù)來(lái)查找屬性名稱:
const char *property_getName(objc_property_t property)
可以用class_getProperty 和 protocol_getProperty通過(guò)給出的名稱來(lái)在類和協(xié)議中獲取屬性的引用:
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
可以用property_getAttributes函數(shù)來(lái)發(fā)掘?qū)傩缘拿Q和@encode類型字符串:
const char *property_getAttributes(objc_property_t property)
對(duì)比下 class_copyIvarList 函數(shù),使用 class_copyPropertyList 函數(shù)只能獲取類的屬性,而不包含成員變量。但此時(shí)獲取的屬性名是不帶下劃線的。
-
objc_property_attribute_t:屬性指針對(duì)象的列表
objc_property_attribute_t的value和name
常用attribute | name | value |
---|---|---|
nonatomic | "N" | "" |
strong/retain | "&" | "" |
weak | "W" | "" |
屬性的類型type | "T" | "@TypeName", eg"@"NSString"" |
屬性對(duì)應(yīng)的實(shí)例變量Ivar | "V" | "Ivar_name", eg "_name" |
readonly | "R" | "" |
getter | "G" | "GetterName", eg"isRight" |
setter | "S" | "SetterName", eg"setName" |
assign/atomic | 默認(rèn)即為assign和retain |
動(dòng)態(tài)方法解析
如果某個(gè)對(duì)象調(diào)用了不存在的方法時(shí)會(huì)怎么樣,一般情況下程序會(huì)crash,錯(cuò)誤信息類似下面這樣:
unrecognized selector sent to instance 0x7fd0a141afd0
但是在程序crash之前,Runtime 會(huì)給我們動(dòng)態(tài)方法解析的機(jī)會(huì),消息發(fā)送的步驟大致如下:
1.檢測(cè)這個(gè) selector 是不是要忽略的。比如 Mac OS X 開(kāi)發(fā),有了垃圾回收就不理會(huì) retain,release 這些函數(shù)了
2.檢測(cè)這個(gè) target 是不是 nil 對(duì)象。ObjC 的特性是允許對(duì)一個(gè) nil 對(duì)象執(zhí)行任何一個(gè)方法不會(huì) Crash,因?yàn)闀?huì)被忽略掉
3.如果上面兩個(gè)都過(guò)了,那就開(kāi)始查找這個(gè)類的 IMP,先從 cache 里面找,完了找得到就跳到對(duì)應(yīng)的函數(shù)去執(zhí)行,如果 cache 找不到就找一下方法分發(fā)表
-
4.如果分發(fā)表找不到就到超類的分發(fā)表去找,一直找,直到找到NSObject類為止
如果還找不到就要開(kāi)始進(jìn)入消息轉(zhuǎn)發(fā)了,消息轉(zhuǎn)發(fā)的大致過(guò)程如圖:
消息轉(zhuǎn)發(fā)
- 1.進(jìn)入
resolveInstanceMethod:
方法,指定是否動(dòng)態(tài)添加方法。若返回NO,則進(jìn)入下一步,若返回YES,則通過(guò)class_addMethod
函數(shù)動(dòng)態(tài)地添加方法,消息得到處理,此流程完畢。- 2.
resolveInstanceMethod:
方法返回 NO 時(shí),就會(huì)進(jìn)入forwardingTargetForSelector:
方法,這是 Runtime 給我們的第二次機(jī)會(huì),用于指定哪個(gè)對(duì)象響應(yīng)這個(gè) selector。返回nil,進(jìn)入下一步,返回某個(gè)對(duì)象,則會(huì)調(diào)用該對(duì)象的方法。- 3.若
forwardingTargetForSelector:
返回的是nil,則我們首先要通過(guò)methodSignatureForSelector:
來(lái)指定方法簽名,返回nil,表示不處理,若返回方法簽名,則會(huì)進(jìn)入下一步。- 4.當(dāng)?shù)?
methodSignatureForSelector:
方法返回方法簽名后,就會(huì)調(diào)用forwardInvocation:
方法,我們可以通過(guò) anInvocation 對(duì)象做很多處理,比如修改實(shí)現(xiàn)方法,修改響應(yīng)對(duì)象等。如果到最后,消息還是沒(méi)有得到響應(yīng),程序就會(huì)crash
未完待續(xù)...