Objective-C Runtime實(shí)戰(zhàn)應(yīng)用(二)

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

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

幾個概念

  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),其實(shí)是一個C語言的結(jié)構(gòu)體,這個結(jié)構(gòu)體里面封裝了描繪這個類所有信息。
//參數(shù)說明:
Class _Nonnull isa              一個指向類的結(jié)構(gòu)體的指針,在objc中,根據(jù)對象的定義,凡是首地址是*isa的結(jié)構(gòu)體指針,都可以認(rèn)為是對象(id),所以類本身也是對象,它的isa指針指向它的源類,源類的isa指向根類,根類的isa指向本身。
Class _Nullable super_class         這也是一個指向類的結(jié)構(gòu)體的指針,不過它指向這個類的父類,通過這個字段類之間形成了繼承關(guān)系
const char * _Nonnull name          類名
long version                    類的版本信息,默認(rèn)為0
long info                   供運(yùn)行期使用的一些位標(biāo)識
long instance_size              該類的實(shí)例變量大小
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的對象其實(shí)也是包含一個isa指針的結(jié)構(gòu)體

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

3.SEL

可以理解為將方法名,參數(shù)列表,返回值進(jìn)行hash化了的,在一個類里唯一存在的字符串鍵值,用來唯一標(biāo)識一個函數(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)用運(yùn)行時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查找。如果一直查找到根類仍舊沒有實(shí)現(xiàn),則用_objc_msgForward函數(shù)指針代替imp。最后,執(zhí)行這個imp。

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

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

1.在本類中找其他方法

調(diào)用resolveInstanceMethod:方法,允許用戶在此時為該Class動態(tài)添加實(shí)現(xiàn)。如果有實(shí)現(xiàn)了,則調(diào)用并返回。如果仍沒實(shí)現(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)添加實(shí)例方法
  • (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//動態(tài)添加類方法

2.嘗試找到一個能響應(yīng)該消息的對象

調(diào)用forwardingTargetForSelector:方法,嘗試找到一個能響應(yīng)該消息的對象。如果獲取到,則直接轉(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)用。最常見的實(shí)現(xiàn)消息轉(zhuǎn)發(fā),就是重寫方法3和4,吞掉一個消息或者代理給其他對象都是沒問題的。

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

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


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容