Objective-C Runtime實戰(zhàn)應用(二)

這是OC運行時實戰(zhàn)應用系列的第二篇盘榨,你可以在這里找到實戰(zhàn)應用1,這一片主要從消息發(fā)送解总,消息轉(zhuǎn)發(fā)宇葱,消息交換的角度講解相關(guān)應用筷黔。

Objective-C 是一門動態(tài)語言往史,它的動態(tài)性體現(xiàn)在它將很多編譯和鏈接時做的事推延到運行時處理,而這一機制主要依賴系統(tǒng)提供的 runtime 庫佛舱。利用 runtime 庫椎例,我們能在運行時做很多事,例如 objc_setAssociatedObject 動態(tài)綁定屬性请祖、method swizzling订歪、class_copyIvarList 動態(tài)獲取屬性實現(xiàn) ORM(Object Relational Mapping)、消息轉(zhuǎn)發(fā)等肆捕,本文先解析消息轉(zhuǎn)發(fā)機制刷晋。

幾個概念

  1. Class
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

作為面向?qū)ο缶幊陶Z言的最重要的數(shù)據(jù)結(jié)構(gòu)--類(Class),其實是一個C語言的結(jié)構(gòu)體慎陵,這個結(jié)構(gòu)體里面封裝了描繪這個類所有信息眼虱。
//參數(shù)說明:
Class _Nonnull isa              一個指向類的結(jié)構(gòu)體的指針,在objc中席纽,根據(jù)對象的定義捏悬,凡是首地址是*isa的結(jié)構(gòu)體指針,都可以認為是對象(id)润梯,所以類本身也是對象过牙,它的isa指針指向它的源類,源類的isa指向根類纺铭,根類的isa指向本身寇钉。
Class _Nullable super_class         這也是一個指向類的結(jié)構(gòu)體的指針,不過它指向這個類的父類舶赔,通過這個字段類之間形成了繼承關(guān)系
const char * _Nonnull name          類名
long version                    類的版本信息扫倡,默認為0
long info                   供運行期使用的一些位標識
long instance_size              該類的實例變量大小
struct objc_ivar_list * _Nullable ivars     成員變量的數(shù)組的指針
struct objc_method_list * _Nullable * _Nullable methodLists     方法定義的數(shù)組的二級指針
struct objc_cache * _Nonnull cache      指向最近使用的方法.用于方法調(diào)用的優(yōu)化.
struct objc_protocol_list * _Nullable protocols 指向協(xié)議的數(shù)組的指針

總之這個結(jié)構(gòu)體里面包含了面向?qū)ο蟪绦虻膸状笠兀瑢ο笈c類的關(guān)系顿痪,繼承體系镊辕,成員變量,成員方法蚁袭,接口征懈,以及用于函數(shù)調(diào)用緩存的cache。

2.Object

OC的對象其實也是包含一個isa指針的結(jié)構(gòu)體

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

3.SEL

可以理解為將方法名揩悄,參數(shù)列表卖哎,返回值進行hash化了的,在一個類里唯一存在的字符串鍵值,用來唯一標識一個函數(shù)

typedef struct objc_selector *SEL;

4.IMP

函數(shù)指針亏娜,可以用來調(diào)取函數(shù)體

typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 

5.Other

以下都是對一個結(jié)構(gòu)體類型的封裝焕窝,用在runtime庫里的數(shù)據(jù)類型,我們在調(diào)用運行時API的時候會用到

typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;
typedef struct objc_object Protocol;
typedef struct objc_cache *Cache
typedef struct objc_module *Module

消息派發(fā)

[receiver message];
這是OC調(diào)用方法的寫法,向receiver發(fā)送message消息。

clang -rewrite-objc MyClass.m
用 clang 的命令將 OC 的語法裝換成 C 的語法维贺,是這樣的:

((void (*)(id, SEL))(void *)objc_msgSend)((id)receiver, sel_registerName("message"));
簡化之后變成了下面的 C 語言的調(diào)用:

objc_msgSend(receiver, @selector(message));
所以說它掂,objc發(fā)送消息,最終大都會轉(zhuǎn)換為objc_msgSend的方法調(diào)用溯泣。

所以O(shè)C方法的調(diào)用過程大致是這樣的:首先在Class中的緩存查找imp(沒緩存則初始化緩存)虐秋,如果沒找到,則向父類的Class查找垃沦。如果一直查找到根類仍舊沒有實現(xiàn)客给,則用_objc_msgForward函數(shù)指針代替imp。最后肢簿,執(zhí)行這個imp靶剑。

消息轉(zhuǎn)發(fā)

當向一個對象發(fā)送一條消息,但它并沒有實現(xiàn)的時候池充,_objc_msgForward會嘗試做消息轉(zhuǎn)發(fā)桩引。

1.在本類中找其他方法

調(diào)用resolveInstanceMethod:方法,允許用戶在此時為該Class動態(tài)添加實現(xiàn)纵菌。如果有實現(xiàn)了阐污,則調(diào)用并返回。如果仍沒實現(xiàn)咱圆,繼續(xù)下面的動作笛辟。

Test *t = [[Test alloc] init];
[t performSelector:@selector(xxx)];

void additionalMethod_01(id self, SEL _cmd) {
    NSLog(@"%@, %p", self, _cmd);
}

//動態(tài)添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"xxx"]) {
        class_addMethod(self.class, @selector(xxx), (IMP)additionalMethod_01, "@:");
    }
    return [super resolveInstanceMethod:sel];
}
  • (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//動態(tài)添加實例方法
  • (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//動態(tài)添加類方法

2.嘗試找到一個能響應該消息的對象

調(diào)用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象序苏。如果獲取到手幢,則直接轉(zhuǎn)發(fā)給它。如果返回了nil忱详,繼續(xù)下面的動作围来。

@interface MethodHelper : NSObject
- (void)xxx;
@end

#import "MethodHelper.h"
@implementation MethodHelper
- (void)xxx {
    NSLog(@"%s",__func__);
}
@end

@interface Test : NSObject
@property (strong, nonatomic) MethodHelper *helper;
@end

@implementation Test
- (instancetype)init {
    self = [super init];
    if (self != nil) {
        _helper = [[MethodHelper alloc] init];
    }
    return self;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s",__func__);
    
    if ([NSStringFromSelector(aSelector) isEqualToString:@"xxx"]) {
        return _helper;
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

Test *t = [[Test alloc] init];
[t performSelector:@selector(xxx)];

這樣就把消息轉(zhuǎn)發(fā)給另一個能處理這個消息的對象。

3.調(diào)用methodSignatureForSelector:方法匈睁,嘗試獲得一個方法簽名监透。如果獲取不到,則直接調(diào)用doesNotRecognizeSelector拋出異常航唆。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if ([MethodHelper instancesRespondToSelector:aSelector]) {
            signature = [MethodHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}

4.調(diào)用forwardInvocation:方法胀蛮,將地3步獲取到的方法簽名包裝成Invocation傳入,如何處理就在這里面了

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([MethodHelper instanceMethodSignatureForSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:_helper];
    }
}

上面這4個方法均是模板方法糯钙,開發(fā)者可以override粪狼,由runtime來調(diào)用退腥。最常見的實現(xiàn)消息轉(zhuǎn)發(fā),就是重寫方法3和4再榄,吞掉一個消息或者代理給其他對象都是沒問題的狡刘。

NSObject的forwardInvocation:方法實現(xiàn)只是簡單調(diào)用了doesNotRecognizeSelector:方法,它不會轉(zhuǎn)發(fā)任何消息困鸥。這樣嗅蔬,如果不在以上所述的三個步驟中處理未知消息,則會引發(fā)一個異常窝革。

最后上一張圖表示消息的派發(fā)和轉(zhuǎn)發(fā):


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末购城,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子虐译,更是在濱河造成了極大的恐慌,老刑警劉巖吴趴,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漆诽,死亡現(xiàn)場離奇詭異,居然都是意外死亡锣枝,警方通過查閱死者的電腦和手機厢拭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撇叁,“玉大人供鸠,你說我怎么就攤上這事≡赡郑” “怎么了楞捂?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長趋厉。 經(jīng)常有香客問我寨闹,道長,這世上最難降的妖魔是什么君账? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任繁堡,我火速辦了婚禮,結(jié)果婚禮上乡数,老公的妹妹穿的比我還像新娘椭蹄。我一直安慰自己,他們只是感情好净赴,可當我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布绳矩。 她就那樣靜靜地躺著,像睡著了一般劫侧。 火紅的嫁衣襯著肌膚如雪埋酬。 梳的紋絲不亂的頭發(fā)上哨啃,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天,我揣著相機與錄音写妥,去河邊找鬼拳球。 笑死,一個胖子當著我的面吹牛珍特,可吹牛的內(nèi)容都是我干的祝峻。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼扎筒,長吁一口氣:“原來是場噩夢啊……” “哼莱找!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗜桌,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤奥溺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骨宠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浮定,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年层亿,在試婚紗的時候發(fā)現(xiàn)自己被綠了桦卒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡匿又,死狀恐怖方灾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碌更,我是刑警寧澤裕偿,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站针贬,受9級特大地震影響击费,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桦他,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一蔫巩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧快压,春花似錦圆仔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脉幢,卻和暖如春歪沃,著一層夾襖步出監(jiān)牢的瞬間嗦锐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工沪曙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奕污,地道東北人。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓液走,卻偏偏與公主長得像碳默,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缘眶,可洞房花燭夜當晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉嘱根,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,734評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢巷懈?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,199評論 0 7
  • 本文詳細整理了 Cocoa 的 Runtime 系統(tǒng)的知識该抒,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 806評論 0 4
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 736評論 0 2
  • 原文出處:南峰子的技術(shù)博客 Objective-C語言是一門動態(tài)語言砸喻,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了...
    _燴面_閱讀 1,235評論 1 5