OC及其動態(tài)性
Objective-C是基于C封裝的一門面向?qū)ο蟮恼Z言,底層實現(xiàn)是通過C/C++代碼實現(xiàn)的。OC語言最大的特點就是其動態(tài)性,它會盡可能地把決策從編譯時和連接時推遲到運行時(簡單來說,就是編譯后的文件不全是機器指令,還有一部分中間代碼,在運行的時候,通過Runtime再把需要轉(zhuǎn)換的中間代碼再翻譯成機器指令)。
Runtime與消息機制
Runtime是OC的一套由C和匯編編寫的庫(一些調(diào)用頻率較高的方法是由匯編編寫的),它是OC具有動態(tài)的的最主要條件。當程序執(zhí)行[object doSomething]時,會向消息接收者(object)發(fā)送一條消息(doSomething),runtime會根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)。因此OC方法在運行時,都是作為消息在傳遞的,我們甚至可以把方法叫做消息,甚至可以說OC就是一門消息語言。
OC對象
在我們講消息機制前首先要了解OC的對象,才能了解對象的方法調(diào)用過程。
OC的的底層其實就結(jié)構(gòu)體,長這個樣子。

這個就是結(jié)構(gòu)體的內(nèi)部;
由上而下,objc_class這個結(jié)構(gòu)體就是一個對象,它由三部分組成,
- isa (指向?qū)ο蟾割惖闹羔?
- superclass(它的父類)
- cache_t(對象的調(diào)用過的方法列表)
- bits(對象的更多信息)
class_rw_t是將bit通過位運算的結(jié)果取其[3, 47]位,轉(zhuǎn)換而成。這里包含了類的方法方法列表,屬性列表及協(xié)議列表等。ro就是rootclass的意思他其中包含了類的成員變量等。
OC類的繼承體系
NSString *str = [NSString string]
str是一個事例對象,它內(nèi)部的isa指針指向它的類NSString,
NSString也是一個OC類,它內(nèi)部也有isa,isa指向它的元類對象NSString meta-class (NSString的元類對象是NSString)
NSString meta-class````也是個OC類,它的isa指向它的元類meta-class。meta-class也是一個對象,它的isa指向哪里?為了防止它無限延伸下去,設(shè)計出了meta-class指向基類的meta-class以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class```作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。這樣就形成了一個完美的閉環(huán)。
事例如下圖。


消息機制
已經(jīng)介紹類OC對象,現(xiàn)在可以runtime消息機制的主題了。我們的調(diào)用類方法也好,對象方法也好,都會被轉(zhuǎn)成 objc_msgSend的消息。由于OC的底層是由C和C++實現(xiàn)的,我們就在OC文件目錄下把它轉(zhuǎn)成C++文件。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc +要轉(zhuǎn)的OC文件
例子:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
//定義一個Dog類,它有兩個方法一個是eat對象方法,另一個是eat類方法。
Dog *wangCai = [[Dog alloc]init];
[wangCai eat];
[Dog eat];
// (objc_msgSend)(wangCai, sel_registerName("eat")); //對象方法
// (objc_msgSend)(objc_getClass("Dog"), sel_registerName("eat")); //類方法
這是的Dog類的.h文件
@interface Dog : NSObject
- (void)eat;
- (void)bark;
+ (void)play;
@end
我們注釋掉它的eat方法實現(xiàn),
#import "Dog.h"
#import <objc/runtime.h>
#import "Cat.h"
@implementation Dog
//- (void)eat{
// NSLog(@"dog--eat");
//}
- (void)bark{
NSLog(@"dog--bark");
}
+ (void)play{
NSLog(@"classfunc-dog--play");
}
@end

消息機制--動態(tài)方法解析
現(xiàn)在調(diào)用eat方法它會出錯,其實OC在找不到方法實現(xiàn)的時候,它會動態(tài)調(diào)用runtime的這個方法+ (BOOL)resolveInstanceMethod:(SEL)sel
#import "Dog.h"
#import <objc/runtime.h>
#import "Cat.h"
@implementation Dog
//- (void)eat
//{
// NSLog(@"dog--eat");
//}
- (void)bark{
NSLog(@"dog--bark");
}
+ (void)play{
NSLog(@"classfunc-dog--play");
}
/*
2.0動態(tài)方法解析
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(eat)) {
// 獲取其他方法
Method method = class_getInstanceMethod(self, @selector(bark)); //調(diào)用Dog類的bark方法, 打印輸出的結(jié)果是dog--bark
// 動態(tài)添加test方法的實現(xiàn)
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有動態(tài)添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
這樣我們就實現(xiàn)了動態(tài)給OC對象尋找實現(xiàn),防止崩潰的方法

消息機制--消息轉(zhuǎn)發(fā)
如果我們不實現(xiàn)resolveInstanceMethod程序必然會崩潰嗎?別急runtime還有第二個機制防止奔潰- (id)forwardingTargetForSelector:(SEL)aSelector消息轉(zhuǎn)發(fā)機制,你不是處理不了嗎?那你吧消息轉(zhuǎn)給別人,讓有能力的類處理。
我們定義一個處理這eat方法的Cat類,.h的聲明寫不寫都成,因為它會直接在方法實現(xiàn)中搜取
#import "Cat.h"
@implementation Cat
- (void)eat{
NSLog(@"Cat--eat");
}
@end
我們在Dog的類中需要做如下處理,把消息轉(zhuǎn)發(fā)給Cat讓Cat幫它去處理
///Dog.m類
/*
3.0 消息轉(zhuǎn)發(fā)
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(eat)) {
return [[Cat alloc] init]; //返回空否
}
return [super forwardingTargetForSelector:aSelector];
}
//這樣處理Cat的eat類會被調(diào)用,打印出Cat--eat
當然消息轉(zhuǎn)發(fā)的時候也不知道轉(zhuǎn)給誰(即- (id)forwardingTargetForSelector:(SEL)aSelector返回的是空對象nil),可以在- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法里自己生成個簽名,然后實現(xiàn)
- (void)forwardInvocation:(NSInvocation *)anInvocation方法,收集日志防止程序崩潰
///Dog.m類
/*
3.0 消息轉(zhuǎn)發(fā)
*/
- (id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
/*
3.1消息轉(zhuǎn)發(fā)
*/
// 方法簽名:返回值類型、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(eat)) {
return [NSMethodSignature signatureWithObjCTypes:"@@:*"];//手動創(chuàng)建一個方法簽名
}
return [super methodSignatureForSelector:aSelector];
}
/*
3.1.1消息轉(zhuǎn)發(fā)
*/
//自定義的方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"調(diào)用的方法找不到實現(xiàn)");
}
如果你這會兒知道誰能處理這個消息,也可以這樣處理,
#import "Dog.h"
#import <objc/runtime.h>
#import "Cat.h"
@interface Dog ()
@property (nonatomic,strong) Cat *miCat;
@end
@implementation Dog
//- (void)eat{
// NSLog(@"dog--eat");
//}
- (void)bark{
NSLog(@"dog--bark");
}
+ (void)play{
NSLog(@"classfunc-dog--play");
}
/*
3.0 消息轉(zhuǎn)發(fā)
*/
- (id)forwardingTargetForSelector:(SEL)aSelector{
return nil;
}
/*
3.1消息轉(zhuǎn)發(fā)
*/
// 方法簽名:返回值類型、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(eat)) {
self.miCat = [Cat new];
return [self.miCat methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
/*
3.1.1消息轉(zhuǎn)發(fā)
*/
//自定義的方法
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// NSLog(@"調(diào)用的方法找不到實現(xiàn)");
if (anInvocation.selector == @selector(eat)) {
[anInvocation invokeWithTarget:self.miCat];
}
}
@end
/// Cat類的eat方法同樣會被調(diào)用,打印出Cat--eat

消息鏈總計起來如下:
- 1.查找
1.本類查找方法,若有響應(yīng),若無去父類查找;
- 父類查找,若有響應(yīng),如無去父類查找,直至元類;
- 元類有響應(yīng),元類無,走消息分發(fā)機制
- 2.消息轉(zhuǎn)發(fā)
1.消息重新交給被掉用類,被掉用類可以讓自己別的方法替代響應(yīng)
- 3.動態(tài)方法解析
- 被掉用類將方法拋給指定的類, 讓它響應(yīng)該方法
- 4.消息轉(zhuǎn)發(fā)
1.方法重新回到被掉用類自身,被掉用類,手動生成方法簽名
- 將改消息交給指定的類,讓它體自己響應(yīng)
方法查找不到時,只有(2、3、4)三層保護全沒有處理才會報錯。