OC及其動(dòng)態(tài)性
Objective-C是基于C封裝的一門面向?qū)ο蟮恼Z言笛辟,底層實(shí)現(xiàn)是通過C/C++代碼實(shí)現(xiàn)的功氨。OC語言最大的特點(diǎn)就是其動(dòng)態(tài)性,它會(huì)盡可能地把決策從編譯時(shí)和連接時(shí)推遲到運(yùn)行時(shí)(簡單來說手幢,就是編譯后的文件不全是機(jī)器指令捷凄,還有一部分中間代碼,在運(yùn)行的時(shí)候围来,通過Runtime再把需要轉(zhuǎn)換的中間代碼再翻譯成機(jī)器指令)跺涤。
Runtime與消息機(jī)制
Runtime是OC的一套由C和匯編編寫的庫(一些調(diào)用頻率較高的方法是由匯編編寫的),它是OC具有動(dòng)態(tài)的的最主要條件监透。當(dāng)程序執(zhí)行[object doSomething]時(shí)桶错,會(huì)向消息接收者(object)發(fā)送一條消息(doSomething),runtime會(huì)根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)才漆。因此OC方法在運(yùn)行時(shí)牛曹,都是作為消息在傳遞的佛点,我們甚至可以把方法叫做消息醇滥,甚至可以說OC就是一門消息語言。
OC對象
在我們講消息機(jī)制前首先要了解OC的對象超营,才能了解對象的方法調(diào)用過程鸳玩。
OC的的底層其實(shí)就結(jié)構(gòu)體
,長這個(gè)樣子。
這個(gè)就是結(jié)構(gòu)體的內(nèi)部演闭;
由上而下不跟,objc_class
這個(gè)結(jié)構(gòu)體就是一個(gè)對象,它由三部分組成米碰,
- isa (指向?qū)ο蟾割惖闹羔?
- superclass(它的父類)
- cache_t(對象的調(diào)用過的方法列表)
- bits(對象的更多信息)
class_rw_t
是將bit通過位運(yùn)算的結(jié)果取其[3, 47]位窝革,轉(zhuǎn)換而成。這里包含了類的方法方法列表吕座,屬性列表及協(xié)議列表等虐译。ro
就是rootclass的意思他其中包含了類的成員變量等。
OC類的繼承體系
NSString *str = [NSString string]
str
是一個(gè)事例對象吴趴,它內(nèi)部的isa指針指向它的類NSString
,
NSString
也是一個(gè)OC類漆诽,它內(nèi)部也有isa,isa指向它的元類對象NSString meta-class
(NSString的元類對象是NSString)
NSString meta-class````也是個(gè)OC類锣枝,它的isa指向它的元類
meta-class厢拭。
meta-class也是一個(gè)對象,它的isa指向哪里撇叁?為了防止它無限延伸下去供鸠,設(shè)計(jì)出了
meta-class指向基類的
meta-class以此作為它們的所屬類。即陨闹,任何NSObject繼承體系下的
meta-class都使用
NSObject的
meta-class```作為自己的所屬類楞捂,而基類的meta-class的isa指針是指向它自己家制。這樣就形成了一個(gè)完美的閉環(huán)。
事例如下圖泡一。
消息機(jī)制
已經(jīng)介紹類OC對象鼻忠,現(xiàn)在可以runtime消息機(jī)制的主題了涵但。我們的調(diào)用類方法也好,對象方法也好帖蔓,都會(huì)被轉(zhuǎn)成 objc_msgSend
的消息矮瘟。由于OC的底層是由C和C++實(shí)現(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
//定義一個(gè)Dog類澈侠,它有兩個(gè)方法一個(gè)是eat對象方法,另一個(gè)是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方法實(shí)現(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
消息機(jī)制--動(dòng)態(tài)方法解析
現(xiàn)在調(diào)用eat方法它會(huì)出錯(cuò),其實(shí)OC在找不到方法實(shí)現(xiàn)的時(shí)候写妥,它會(huì)動(dòng)態(tài)調(diào)用runtime的這個(gè)方法+ (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動(dòng)態(tài)方法解析
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(eat)) {
// 獲取其他方法
Method method = class_getInstanceMethod(self, @selector(bark)); //調(diào)用Dog類的bark方法拳球, 打印輸出的結(jié)果是dog--bark
// 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有動(dòng)態(tài)添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
這樣我們就實(shí)現(xiàn)了動(dòng)態(tài)給OC對象尋找實(shí)現(xiàn),防止崩潰的方法
消息機(jī)制--消息轉(zhuǎn)發(fā)
如果我們不實(shí)現(xiàn)resolveInstanceMethod
程序必然會(huì)崩潰嗎珍特?別急runtime還有第二個(gè)機(jī)制防止奔潰- (id)forwardingTargetForSelector:(SEL)aSelector
消息轉(zhuǎn)發(fā)機(jī)制祝峻,你不是處理不了嗎?那你吧消息轉(zhuǎn)給別人扎筒,讓有能力的類處理莱找。
我們定義一個(gè)處理這eat方法的Cat類,.h的聲明寫不寫都成嗜桌,因?yàn)樗鼤?huì)直接在方法實(shí)現(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類會(huì)被調(diào)用,打印出Cat--eat
當(dāng)然消息轉(zhuǎn)發(fā)的時(shí)候也不知道轉(zhuǎn)給誰(即- (id)forwardingTargetForSelector:(SEL)aSelector
返回的是空對象nil),可以在- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法里自己生成個(gè)簽名症脂,然后實(shí)現(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:"@@:*"];//手動(dòng)創(chuàng)建一個(gè)方法簽名
}
return [super methodSignatureForSelector:aSelector];
}
/*
3.1.1消息轉(zhuǎn)發(fā)
*/
//自定義的方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"調(diào)用的方法找不到實(shí)現(xiàn)");
}
如果你這會(huì)兒知道誰能處理這個(gè)消息诱篷,也可以這樣處理壶唤,
#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)用的方法找不到實(shí)現(xiàn)");
if (anInvocation.selector == @selector(eat)) {
[anInvocation invokeWithTarget:self.miCat];
}
}
@end
/// Cat類的eat方法同樣會(huì)被調(diào)用棕所,打印出Cat--eat
消息鏈總計(jì)起來如下:
- 1.查找
1.本類查找方法闸盔,若有響應(yīng),若無去父類查找琳省;
- 父類查找迎吵,若有響應(yīng)躲撰,如無去父類查找,直至元類;
- 元類有響應(yīng)击费,元類無拢蛋,走消息分發(fā)機(jī)制
- 2.消息轉(zhuǎn)發(fā)
1.消息重新交給被掉用類,被掉用類可以讓自己別的方法替代響應(yīng)
- 3.動(dòng)態(tài)方法解析
- 被掉用類將方法拋給指定的類蔫巩, 讓它響應(yīng)該方法
- 4.消息轉(zhuǎn)發(fā)
1.方法重新回到被掉用類自身谆棱,被掉用類,手動(dòng)生成方法簽名
- 將改消息交給指定的類圆仔,讓它體自己響應(yīng)
方法查找不到時(shí)垃瞧,只有(2、3坪郭、4)三層保護(hù)全沒有處理才會(huì)報(bào)錯(cuò)个从。