Objective-C Runtime實戰應用(二)

這是OC運行時實戰應用系列的第二篇,你可以在這里找到實戰應用1,這一片主要從消息發送,消息轉發,消息交換的角度講解相關應用。

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

幾個概念

  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;

作為面向對象編程語言的最重要的數據結構--類(Class),其實是一個C語言的結構體,這個結構體里面封裝了描繪這個類所有信息。
//參數說明:
Class _Nonnull isa              一個指向類的結構體的指針,在objc中,根據對象的定義,凡是首地址是*isa的結構體指針,都可以認為是對象(id),所以類本身也是對象,它的isa指針指向它的源類,源類的isa指向根類,根類的isa指向本身。
Class _Nullable super_class         這也是一個指向類的結構體的指針,不過它指向這個類的父類,通過這個字段類之間形成了繼承關系
const char * _Nonnull name          類名
long version                    類的版本信息,默認為0
long info                   供運行期使用的一些位標識
long instance_size              該類的實例變量大小
struct objc_ivar_list * _Nullable ivars     成員變量的數組的指針
struct objc_method_list * _Nullable * _Nullable methodLists     方法定義的數組的二級指針
struct objc_cache * _Nonnull cache      指向最近使用的方法.用于方法調用的優化.
struct objc_protocol_list * _Nullable protocols 指向協議的數組的指針

總之這個結構體里面包含了面向對象程序的幾大要素,對象與類的關系,繼承體系,成員變量,成員方法,接口,以及用于函數調用緩存的cache。

2.Object

OC的對象其實也是包含一個isa指針的結構體

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

3.SEL

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

typedef struct objc_selector *SEL;

4.IMP

函數指針,可以用來調取函數體

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

5.Other

以下都是對一個結構體類型的封裝,用在runtime庫里的數據類型,我們在調用運行時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

消息派發

[receiver message];
這是OC調用方法的寫法,向receiver發送message消息。

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

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

objc_msgSend(receiver, @selector(message));
所以說,objc發送消息,最終大都會轉換為objc_msgSend的方法調用。

所以OC方法的調用過程大致是這樣的:首先在Class中的緩存查找imp(沒緩存則初始化緩存),如果沒找到,則向父類的Class查找。如果一直查找到根類仍舊沒有實現,則用_objc_msgForward函數指針代替imp。最后,執行這個imp。

消息轉發

當向一個對象發送一條消息,但它并沒有實現的時候,_objc_msgForward會嘗試做消息轉發。

1.在本類中找其他方法

調用resolveInstanceMethod:方法,允許用戶在此時為該Class動態添加實現。如果有實現了,則調用并返回。如果仍沒實現,繼續下面的動作。

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

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

//動態添加方法
+ (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);//動態添加實例方法
  • (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);//動態添加類方法

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

調用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象。如果獲取到,則直接轉發給它。如果返回了nil,繼續下面的動作。

@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)];

這樣就把消息轉發給另一個能處理這個消息的對象。

3.調用methodSignatureForSelector:方法,嘗試獲得一個方法簽名。如果獲取不到,則直接調用doesNotRecognizeSelector拋出異常。

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

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

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

上面這4個方法均是模板方法,開發者可以override,由runtime來調用。最常見的實現消息轉發,就是重寫方法3和4,吞掉一個消息或者代理給其他對象都是沒問題的。

NSObject的forwardInvocation:方法實現只是簡單調用了doesNotRecognizeSelector:方法,它不會轉發任何消息。這樣,如果不在以上所述的三個步驟中處理未知消息,則會引發一個異常。

最后上一張圖表示消息的派發和轉發:


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

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