老樣子..先看文檔 知道 runtime 到底是個什么東西呢.....
The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.
runtime 使objc變成了真正的動態(tài)語言,是objc 的操作系統(tǒng).objc 延遲了許多決定,直到運行時才做出.runtime 給了 objc 真正的動態(tài)性
runtime 運行時有兩個版本,一般手機和64位機器的用的是現(xiàn)代版本,一般32位的機器用的是古老版本..我們接下來都是以現(xiàn)代版本進行解釋的
和 objc-runtime 交互有三種方式
- 通過 objc 源代碼 我們在寫 objc 源代碼的時候,在編譯期間有的就已經(jīng)被翻譯優(yōu)化了.背后變成了調(diào)用運行時的代碼
- 就是通過 nsobject 中的一些方法 nsobject 不規(guī)定類的具體行為.只定義了它們的基本必須結(jié)構(gòu)
- 最后的,自然就是通過runtime函數(shù)庫直接調(diào)用
how the message expressions are converted into objc_msgSend function calls, and how you can refer to methods by name. It then explains how you can take advantage of objc_msgSend, and how—if you need to—you can circumvent dynamic binding.
如果你對objc消息機制掌握的很好,完全可以繞過動態(tài)綁定實現(xiàn)黑魔法
消息的最終接收者都是在運行時才確定的
[receiver message]
編譯器轉(zhuǎn)換為:
objc_msgSend(receiver, selector) //兩個必須參數(shù)
objc_msgSend(receiver, selector, arg1, arg2, ...)//之后可選參數(shù)
The messaging function does everything necessary for dynamic binding:
It first finds the procedure (method implementation) that the selector refers to. Since the same method can be implemented differently by separate classes, the precise procedure that it finds depends on the class of the receiver.
It then calls the procedure, passing it the receiving object (a pointer to its data), along with any arguments that were specified for the method.
Finally, it passes on the return value of the procedure as its own return value.
作用:根據(jù)message 和 receiver的類的找到唯一的方法
我們先來看看類是什么 objc-class
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..//協(xié)議鏈表 OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
typedef struct objc_class *Class;
這是需要了解的東西 一個實例中都有一個 isa 指針,指向一個 Class(objc_class*)的結(jié)構(gòu)體的指針,也就是實例指向自己的類,類中也有一個 Class(isa)指向元類.一切皆對象中,類也是對象
可以看到很清楚顯示了實例和類之間的關系
When a message is sent to an object, the messaging function follows the object’s isa pointer to the class structure where it looks up the method selector in the dispatch table. If it can’t find the selector there, objc_msgSend follows the pointer to the superclass and tries to find the selector in its dispatch table. Successive failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class. Once it locates the selector, the function calls the method entered in the table and passes it the receiving object’s data structure.
當一個消息被發(fā)送到一個對象時,函數(shù)調(diào)度機制根據(jù)這個對象的 isa 指針找到類的繼承結(jié)構(gòu)在類中的 dispatch_table 中找到這個方法,循環(huán)向上,找到之后,才開始進行執(zhí)行 行話就是動態(tài)綁定.....
為了加速動態(tài)綁定的速度,每個類都有一個調(diào)度緩存,一些多次被使用的方法指針都在這里,在進行調(diào)用表查詢之前,所有的方法都會先去查詢這個 cache 中的方法.
isa:需要注意的是在Objective-C中,所有的類自身也是一個對象,這個對象的Class里面也有一個isa指針,它指向metaClass(元類),
super_class:指向該類的父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。
cache:用于緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據(jù)isa指針去查找能夠響應這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差。這時,cache就派上用場了。在我們每次調(diào)用過一個方法后,這個方法就會被緩存到cache列表中,下次調(diào)用的時候runtime就會優(yōu)先去cache中查找,如果cache沒有,才去methodLists中查找方法。這樣,對于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率。就是緩存方法
現(xiàn)在看看對象的定義 (所有的都是經(jīng)過c包裝后的)
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY; //實例對象擁有指向類的指針
};
typedef struct objc_object *id;
當創(chuàng)建一個特定類的實例對象時,分配的內(nèi)存包含一個objc_object數(shù)據(jù)結(jié)構(gòu),NSObject類的alloc和allocWithZone:方法底層調(diào)用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu).
常見的id,它是一個objc_object結(jié)構(gòu)類型的指針。 代表任何對象
meta-class是一個類對象的類。
當我們向一個對象發(fā)送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而向一個類發(fā)送消息時,會在這個類的meta-class的方法列表中查找。
meta-class存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class.所有meta-class類的isa 指向的都是 NSObject的meta-class類..也就是當我們調(diào)用類方法的時候,實際是去元類中查找.....

類與對象操作函數(shù)
runtime提供了大量的函數(shù)來操作類與對象。類的操作方法大部分是以class為前綴的,而對象的操作方法大部分是以objc或object_為前綴。
When objc_msgSend finds the procedure that implements a method, it calls the procedure and passes it all the arguments in the message. It also passes the procedure two hidden arguments:
The receiving object
The selector for the method
These arguments give every method implementation explicit information about the two halves of the message expression that invoked it. They’re said to be “hidden” because they aren’t declared in the source code that defines the method. They’re inserted into the implementation when the code is compiled.
每底層objc_msgsend的方法在運行時都會有兩個隱含的參數(shù) 在編譯時被自動添加 一個是 self.指向receiver 一個是_cmd..代表的是方法本身
下面官方代碼測試這兩個參數(shù)
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
self 很有用啊,我們不是經(jīng)常通過 self 獲得消息和成員變量嘛
每次執(zhí)行方法的時候,都需要在調(diào)度表中查詢找到函數(shù)的入口地址,這對于一般方法沒問題.但是對于需要超高效率和執(zhí)行次數(shù)多的方法,我們可以直接跳過動態(tài)綁定
sing methodForSelector: to circumvent dynamic binding saves most of the time required by messaging. However, the savings will be significant only where a particular message is repeated many times, as in the for loop shown above.
我們一般用 methodForSelector 來跳過動態(tài)綁定這]法是得到 selector 的實際函數(shù)
| SEL1 | SEL2 | SEL3 |
| IMP1 | IMP2 | IMP3 |
sel 只是一個方法的標志符,真正的執(zhí)行代碼地址是 imp 指向的地方 methodforselector可以的某個方法標識符的地址,我們也可以在程序中之直接使用
IMP imp = [ NSObject methodForSelector:@selector(class)];
imp();
typedef id (*IMP)(id, SEL,... );
IMP is a C type referring to the implementation of a method, also known as an implementation pointer. It's a pointer to a function returning id, and with self and a method selector (available inside method definitions as the variable _cmd) as the first arguments
可以看到 id 是一種數(shù)據(jù)類型,imp 是一個指向返回數(shù)據(jù)類型是 id 的函數(shù)的指針,這個函數(shù)的第一個參數(shù)和第二個參數(shù)是self 和這個函數(shù)名_cmd;
objc 將許多需要編譯和鏈接時確定的延遲到運行時確定,這樣就發(fā)生了許多有趣的東西.比如.我們可以動態(tài)的給類添加屬性使用 @dynamic propertyName;
動態(tài)的告訴編譯器消息使用的屬性
我們也可以動態(tài)的給類添加方法.(所有的這些和 java 中因為有虛擬機而有的反射有異曲同工之妙)
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
class_addMethod 接收的參數(shù):要添加的類 ,方法標識符 sel;方法執(zhí)行入口地址;描述符
我們一定要有一個意識(runtime 是 一個函數(shù)庫 幫助我們更好的處理 objc 和 java 虛擬機做的事情是很類似的 這是動態(tài)語言的一個大特性)
消息轉(zhuǎn)發(fā)
當一個對象能接收一個消息時,就會走正常的方法調(diào)用流程。但如果一個對象無法接收指定消息時,又會發(fā)生什么事呢?默認情況下,如果是以 [object message]的方式調(diào)用方法,如果object無法響應message消息時,編譯器會報錯。但如果是以perform…的形式來調(diào)用,則需要等到運 行時才能確定object是否能接收message消息。如果不能,則程序崩潰。
所以我們不能確定一個對象是否可以接受一個消息的時候,會先進行判斷
if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}
當一個對象無法接收某一消息時,就會啟動所謂”消息轉(zhuǎn)發(fā)(message forwarding)“機制,通過這一機制,我們可以告訴對象如何處理未知的消息。默認情況下,對象接收到未知的消息,會導致程序崩潰,報錯
untime的強大之處在于它能在運行時創(chuàng)建類和對象。
動態(tài)創(chuàng)建類
動態(tài)創(chuàng)建類涉及到以下幾個函數(shù):
// 創(chuàng)建一個新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
// 銷毀一個類及其相關聯(lián)的類
void objc_disposeClassPair ( Class cls );
// 在應用中注冊由objc_allocateClassPair創(chuàng)建的類
void objc_registerClassPair ( Class cls );
和 java 中的反射有異曲同工之妙
息轉(zhuǎn)發(fā)機制基本上分為三個步驟:
動態(tài)方法解析
備用接收者
完整轉(zhuǎn)發(fā)
下面我們詳細討論一下這三個步驟。
動態(tài)方法解析
對象在接收到未知的消息時,首先會調(diào)用所屬類的類方法+resolveInstanceMethod:(實例方法)或 者+resolveClassMethod:(類方法)。在這個方法中,我們有機會為該未知消息新增一個”處理方法”“。不過使用該方法的前提是我們已經(jīng) 實現(xiàn)了該”處理方法”,只需要在運行時通過class_addMethod函數(shù)動態(tài)添加到類里面就可以了。如下代碼所示:
void functionForMethod1(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}
return [super resolveInstanceMethod:sel];
}
不過這種方案更多的是為了實現(xiàn)@dynamic屬性。
備用接收者
如果在上一步無法處理消息,則Runtime會繼續(xù)調(diào)以下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
如果一個對象實現(xiàn)了這個方法,并返回一個非nil的結(jié)果,則這個對象會作為消息的新接收者,且消息會被分發(fā)到這個對象。當然這個對象不能是self自身,否則就是出現(xiàn)無限循環(huán)。當然,如果我們沒有指定相應的對象來處理aSelector,則應該調(diào)用父類的實現(xiàn)來返回結(jié)果。
使用這個方法通常是在對象內(nèi)部,可能還有一系列其它對象能處理該消息,我們便可借這些對象來處理消息并返回,這樣在對象外部看來,還是由該對象親自處理了這一消息。如下代碼所示:
@interface SUTRuntimeMethodHelper : NSObject
- (void)method2;
@end
@implementation SUTRuntimeMethodHelper
- (void)method2 {
NSLog(@"%@, %p", self, _cmd);
}
@end
#pragma mark -
@interface SUTRuntimeMethod () {
SUTRuntimeMethodHelper *_helper;
}
@end
@implementation SUTRuntimeMethod
+ (instancetype)object {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (self != nil) {
_helper = [[SUTRuntimeMethodHelper alloc] init];
}
return self;
}
- (void)test {
[self performSelector:@selector(method2)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector");
NSString *selectorString = NSStringFromSelector(aSelector);
// 將消息轉(zhuǎn)發(fā)給_helper來處理
if ([selectorString isEqualToString:@"method2"]) {
return _helper;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個能處理該消息的對象上。但這一步無法對消息進行處理,如操作消息的參數(shù)和返回值。
完整消息轉(zhuǎn)發(fā)
如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機制了。此時會調(diào)用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
運行時系統(tǒng)會在這一步給消息接收者最后一次機會將消息轉(zhuǎn)發(fā)給其它對象。對象會創(chuàng)建一個表示消息的NSInvocation對象,把與尚未處理的消息 有關的全部細節(jié)都封裝在anInvocation中,包括selector,目標(target)和參數(shù)。我們可以在forwardInvocation 方法中選擇將消息轉(zhuǎn)發(fā)給其它對象。
forwardInvocation:方法的實現(xiàn)有兩個任務:
定位可以響應封裝在anInvocation中的消息的對象。這個對象不需要能處理所有未知消息。
使用anInvocation作為參數(shù),將消息發(fā)送到選中的對象。anInvocation將會保留調(diào)用結(jié)果,運行時系統(tǒng)會提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者。
不過,在這個方法中我們可以實現(xiàn)一些更復雜的功能,我們可以對消息的內(nèi)容進行修改,比如追回一個參數(shù)等,然后再去觸發(fā)消息。另外,若發(fā)現(xiàn)某個消息不應由本類處理,則應調(diào)用父類的同名方法,以便繼承體系中的每個類都有機會處理此調(diào)用請求。
還有一個很重要的問題,我們必須重寫以下方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息轉(zhuǎn)發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象。因此我們必須重寫這個方法,為給定的selector提供一個合適的方法簽名。
完整的示例如下所示:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}
NSObject的forwardInvocation:方法實現(xiàn)只是簡單調(diào)用了doesNotRecognizeSelector:方法,它不會轉(zhuǎn)發(fā)任何消息。這樣,如果不在以上所述的三個步驟中處理未知消息,則會引發(fā)一個異常。
從某種意義上來講,forwardInvocation:就像一個未知消息的分發(fā)中心,將這些未知的消息轉(zhuǎn)發(fā)給其它對象。或者也可以像一個運輸站一樣將所有未知消息都發(fā)送給同一個接收對象。這取決于具體的實現(xiàn)。
消息轉(zhuǎn)發(fā)與多重繼承
回過頭來看第二和第三步,通過這兩個方法我們可以允許一個對象與其它對象建立關系,以處理某些未知消息,而表面上看仍然是該對象在處理消息。通過這 種關系,我們可以模擬“多重繼承”的某些特性,讓對象可以“繼承”其它對象的特性來處理一些事情。不過,這兩者間有一個重要的區(qū)別:多重繼承將不同的功能 集成到一個對象中,它會讓對象變得過大,涉及的東西過多;而消息轉(zhuǎn)發(fā)將功能分解到獨立的小的對象中,并通過某種方式將這些對象連接起來,并做相應的消息轉(zhuǎn) 發(fā)。
不過消息轉(zhuǎn)發(fā)雖然類似于繼承,但NSObject的一些方法還是能區(qū)分兩者。如respondsToSelector:和isKindOfClass:只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來像是繼承,則可以重寫這些方法,如以下代碼所示:
- (BOOL)respondsToSelector:(SEL)aSelector {
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can
*
* be forwarded to another object and whether that
*
* object can respond to it. Return YES if it can.
*/
}
return NO;
}
這些東西我們只要了解就好,在開發(fā)中用到的不多
順便在這里說一下 objc中前面文章提到的數(shù)據(jù)類型
typedef struct objc_category *Category;
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分類名
char *class_name OBJC2_UNAVAILABLE; // 分類所 屬的類名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 實例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 類方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分類所實現(xiàn)的協(xié)議列表
}
這是分類的結(jié)構(gòu)體 和類是不是很相似啊?哈哈
我們再來看看協(xié)議的struct
typedef struct objc_object Protocol;
簡單明了 大家一看,這不就是對象嘛
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY; //實例對象擁有指向類的指針
};
class isa 指向了實現(xiàn)協(xié)議的類..(自然也將協(xié)議中的方法在實現(xiàn)的類中可以找到)
runtime.h 提供了很多接口讓開發(fā)者靈活使用,有對類的,對對象的,對屬性,方法的.(分類的東西已經(jīng)包含在類中),對協(xié)議的
我們可以在程序中動態(tài)產(chǎn)生 繼承 改變 注冊 獲得 銷毀 各個我門想要操作
最有趣又著名的莫過于sclector swizzing
這就是非常有用的的hook....
下一篇再講