Runtime - 基於isa-swizzling實現消息監聽,擴展響應式框架

前言

在上一篇文章《函數式編程 - 實現響應式框架》中,我實現了一個很是簡單小巧的函數式響應式框架,並對它作了與Cocoa相關的一些擴展,好比支持將UIControl的用戶觸發事件以及Notification轉換成響應式的流,供咱們進行流轉換以及訂閱。在其中有一個比較重要的擴展我尚未去實現,那就是對Runtime的適配。經過對Runtime的適配,咱們就能監聽某個方法的調用,包括協議的方法(儘管此時方法尚未被實現)。因爲此部分技術更可能是偏向於Runtime,因此這篇文章並不概括於「函數式編程」範疇。本文的重點將放在對Objective-CRuntime的探討上,在最後纔將響應式框架與適配好的Runtime結合起來。git

這篇文章的主要思想及實現,參考自ReactiveCocoagithub

目標

咱們的目標就是要完成一件事:監聽,而且針對的是方法的調用(消息發送)監聽:每次方法被調用時,咱們就能收到監聽的回調,而且獲得當時傳入方法中的參數值。其能帶給咱們的價值是很是大的,咱們能在方法不改變其原有的工做流程、返回數據的基礎上,對方法進行特定的擴展。編程

這是一種很是暗黑的魔法,它能給方法動態提供了一種二次的實現,不只只是單純地增添方法的功能,還能作到從實現埋點統計、Log輸出到AOP(面向切面編程)的運用,甚至它還能實現咱們本身的KVO(監聽setter方法)。數組

另外,咱們還須要考慮的一點是代理模式。Cocoa中代理模式使用得很是頻繁,不過這種模式使用起來並非十分簡便:咱們須要讓特定類去實現代理接口,並提供相應抽象方法的實現,而經過對Runtime進行適配後,咱們不須要代理類去作相關實現就能對相應的代理抽象方法進行調用監聽。緩存

最終效果

咱們來看下適配後的最終效果。這裏咱們展現的是經過閉包回調的狀況,而關聯了響應式框架的效果在後面才提到。閉包

Objective-C

[self listen: @selector(touchesBegan:withEvent:) in: nil with: ^(NSArray * _Nonnull parameters) {
    NSLog(@"Touches began");
}];

[self listen: @selector(tableView:didSelectRowAtIndexPath:) in: @protocol(UITableViewDelegate) with: ^(NSArray * _Nonnull parameters) {
    if (parameters.count != 2) return;
    NSIndexPath *indexPath = parameters[1];
    NSLog(@"Did selected row %ld", (long)indexPath.row);
}];複製代碼

Swift

// 普通方法調用監聽
listen(#selector(ViewController.touchesBegan(_:with:)), in: nil) { _ in
    print("Touches began")
}

// 代理方法調用監聽
// 注:此時self所屬類並不須要實現`tableView(_:didSelectRowAt:)`方法
listen(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:)), in: UITableViewDelegate.self) { parameters in
    // parameters則爲調用特定方法時所傳入的參數,以`[Any]`數組的形式呈現
    guard
        parameters.count == 2,
        let indexPath = parameters[1] as? IndexPath
    else { return }
    print("Did selected row \(indexPath.row)")
}
// 設置`TableView`代理
_tableView.delegate = self複製代碼

原理

監聽基本原理

首先來講下監聽回調的基本原理,下面是一張原理示意圖:框架

監聽基本原理
監聽基本原理

咱們自頂向下看,首先咱們能夠經過performSelector或者[obj message]的形式用特定的Selector向對象發送消息,此時Runtime系統會進入消息派發的流程中,若是咱們什麼都不作,消息派發流程最終就會找到相應的方法實現,從而調用實現獲得結果返回,若咱們要監聽方法的調用,則須要在消息派發的過程當中動點手腳,將方法調用的事件從裏面回調出來。函數式編程

消息派發原理

當消息發送時,在消息派發的流程中咱們不只須要調用原來相應的方法實現,還須要回調信息來通知外界。要實現這個過程,咱們用到了一個十分巧妙的方法。函數

這裏我列出方法的步驟:ui

  1. 建立一個新的方法,指定一個新的Selector給它,並將原始方法(被監聽的方法)的Implementation(實現)賦予給這個新的方法。

    補充:在OC中,方法由Selector(選擇器)以及Implementation(實現)構成。在OC中發送消息,首先是利用選擇器找到對應的方法,將其中的方法實現提取出來,而後再調用方法實現從而獲得最終結果。

  2. 將原始方法的方法實現替換成_objc_msgForward

    補充:OC中方法實現的類型其實都是函數指針,而_objc_msgForward的類型也是函數指針,它的做用就是觸發完整的消息轉發過程。當咱們利用方法選擇器往對象發送消息時,Runtime會前後在方法緩存列表、類對象方法列表、派生類對象及上層若干派生類對象的方法列表中查找方法,若找到方法,便可提取方法實現進行調用,若最終依舊找不到方法,則運行時會直接調用_objc_msgForward,此時就進入消息的轉發流程中。將原始方法的方法實現替換成_objc_msgForward,當咱們用原始方法的Selector發送消息時,Runtime會直接進入消息轉發流程。

  3. 重寫類對象的forwardInvocation:方法,在裏面作兩件事情:①提取方法調用時傳入的參數,向外界回調。②將消息轉發給在第一步建立的新方法。

    補充:由於在第二步中,咱們已經將原始方法的實現替換成了_objc_msgForward,當咱們經過原始方法的選擇器發送消息時,會走方法轉發的流程,因爲咱們並無重寫resolveInstanceMethod:forwardingTargetForSelector:方法,因此最終咱們會進入forwardInvocation:方法,而在裏面咱們須要作的,是向外界回調信息以及將消息轉發到新方法中,從而調用原始的方法實現。

消息派發原理1
消息派發原理1

消息派發原理2
消息派發原理2

方法重寫原理

咱們要實現對消息派發流程的修改,則須要對forwardInvocation:方法進行重寫,然而,這種重寫並非像平時同樣簡單地在類或擴展中提供本身重寫實現後的方法,咱們須要使用到一個技術:isa-swizzling

咱們知道,Runtime是利用isa-swizzling技術來實現KVO的,在運行時重寫相應屬性的setter方法,而咱們這裏也是利用isa-swizzling去重寫forwardInvocation:方法。

isa-swizzling
isa-swizzling

如上圖所示,isa-swizzling其實就是在運行時動態建立了一箇中間層的類對象,這個類對象繼承自舊的類對象(isa),而後重寫相應的方法,最後,運行時將實例中的類對象(isa)替換成這個中間層類對象。通過isa-swizzling處理過的實例,它的isa已經替換了,因此此時向它發送消息,方法首先是在新的中間層類對象的方法列表中進行查找,若中間層類對象重寫了方法,Runtime則會調用這個重寫方法的實現。

這個拋出一個問題:

爲何監聽方法調用不直接使用method-swizzling(方法交換)?

爲了監聽方法的調用,上面所述的各類原理略爲複雜,而使用method-swizzling依然可以向方法提供二次的實現、監聽方法調用,且這樣子寫起來更爲簡便,爲何不直接使用方法交換,還要進行isa-swizzling跟方法重寫?這裏有兩個緣由:

  1. 方法交換實現起來其實並不簡便,對於每個須要二次實現的方法,咱們都要編寫一套交換的代碼(包括新的交換方法),若方法量多,代碼則會變得冗長(固然能夠考慮使用宏)。而基於上述的isa-swizzling原理,咱們能夠將一系列操做封裝起來,最終只需經過一個方法便可完成對方法的調用監聽。
  2. 防止類對象被污染:咱們使用方法交換,須要在原始的類對象中進行操做,如通常來講咱們會重寫類對象的load方法,在裏面實現方法交換的相關邏輯,這樣作會對原始類對象形成污染。舉個例子,若咱們經過方法交換重寫了類A中的方法α,那麼在整個項目中,當咱們向全部屬於類A的實例發送方法α的消息,最終都會走重寫後的實現。假如咱們只須要監聽某個指定實例的方法調用時,咱們對這個方法進行方法交換,那麼最終改變行爲的不僅是這個指定的實例,而是全部與此同類的實例。爲此,咱們須要利用isa-swizzling,它作的,就是修改某個指定實例的類對象(isa),到最後,改變行爲的只是這個實例,由於此時它已經不屬於本來所屬舊類對象的實例了。

實現

接口

由於這裏涉及較多Runtime的API,因此整個實現我使用的是Objective-C語言。
整個實現是在NSObject的擴展NSObject+Runtime裏面的,因此這裏我建立了這個擴展,並提供如下的接口:

typedef void (^MessageDidSendCallback) (NSArray * _Nonnull);

@interface NSObject (Runtime)

- (void)listen:(nonnull SEL)selector in:(nullable Protocol *)protocol with:(nonnull MessageDidSendCallback)callback;

@end複製代碼
  • MessageDidSendCallback就是方法調用回調的block類型,它具備一個數組類型的參數,做用是傳遞方法被調用時傳入的參數。
  • 咱們給NSObject添加了監聽方法,方法有三個參數,第一個就是咱們須要監聽的方法的選擇器,第二個則爲可空的協議類型(當指定的方法屬於協議方法,傳入協議對象,反之則傳nil),第三個就是監聽回調的block。當指定的方法被調用時,callback就會被調用,咱們能夠從它的數組參數中獲取到方法被調用時傳入的參數值。

接口實現

- (void)listen:(SEL)selector in:(Protocol *)protocol with:(MessageDidSendCallback)callback {
    SEL runtimeSelector = _modifySelector(selector);
    // 引用閉包
    objc_setAssociatedObject(self, runtimeSelector, callback, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    // isa-swizzling
    Class interlayerClass = _swizzleClass(self);
    Method originalMethod = class_getInstanceMethod(interlayerClass, selector);
    IMP originalImplementation = method_getImplementation(originalMethod);

    // 判斷是否具備該方法
    // 若是沒有,試圖在指定的協議中尋找
    if (!originalMethod) {
        if (!protocol) return;
        struct objc_method_description des = protocol_getMethodDescription(protocol, selector, YES, YES);
        if (!des.name)
            des = protocol_getMethodDescription(protocol, selector, NO, YES);
        if (des.types)
            class_addMethod(interlayerClass, selector, _objc_msgForward, des.types);
    }
    // 若是原始方法沒有作替換
    // 則將原始方法的實現改成_objc_msgForward
    else if (originalImplementation != _objc_msgForward) {
        const char *typeEncoding = method_getTypeEncoding(originalMethod);
        class_addMethod(interlayerClass, runtimeSelector, originalImplementation, typeEncoding);
        class_replaceMethod(interlayerClass, selector, _objc_msgForward, typeEncoding);
    }
}複製代碼

從上往下看,首先調用_modifySelector在原來的方法選擇器基礎上通過修飾,獲得新的方法的方法選擇器,修飾的過程比較簡單:

// 用於在原有的基礎上標示Selector以及中間層類對象的名字,便於區分
static NSString * const _prefixName = @"_Runtime_";

// 修飾Selector,返回通過前綴名拼接的Selector
static SEL _Nonnull _modifySelector(SEL _Nonnull selector) {
    NSString *originalName = NSStringFromSelector(selector);
    return NSSelectorFromString([_prefixName stringByAppendingString:originalName]);
}複製代碼

咱們經過拼接一個修飾字符串到原來選擇器的字符串上,並利用這個拼接後的字符串經過NSSelectorFromString轉換成新的選擇器。

接下來經過使用這個新的方法選擇器做爲key,將listen方法傳入的block對象設置成實例本身的關聯對象,目的是維持callback block的存活,以及便於咱們後期在forwardInvocation:方法中獲取到這個block。

接着,咱們經過函數_swizzleClass進行isa-swizzling操做(後面會說到),這個函數返回的是剛建立好的中間層類對象。

拿到這個中間層類對象後,咱們就可以在裏面以指定的舊方法爲基礎,建立一個新的方法(利用舊的方法實現以及新的方法選擇器),並將舊方法的方法實現替換成_objc_msgForward。此時操做的是中間層類對象,因此不會污染到本來的類對象。在這裏須要注意的有:

  • 若在以前咱們已經爲指定的方法進行實現替換了,咱們就不必再重複操做。
  • 若在這個中間層類對象中找不到舊方法,而listen傳入的協議並不爲空,則在協議裏面查找方法,若協議中確實具備此方法,那咱們就動態往中間層類對象中添加這個協議方法,且方法的實現爲_objc_msgForward。此時這個協議方法並不作任何事,它的做用只爲了在這個方法被調用時發送回調。

isa-swizzling

// 關聯對象Key,是否已經存在中間層類對象
static void *_interlayerClassExist = &_interlayerClassExist;

// isa-swizzling
static Class _Nullable _swizzleClass(id _Nonnull self) {
    Class originalClass = object_getClass(self);
    // 若是在以前已經替換了isa,則只需直接返回
    if ([objc_getAssociatedObject(self, _interlayerClassExist) boolValue])
        return originalClass;

    Class interlayerClass;

    Class presentClass = [self class];
    // 若以前沒有手動替換過isa,可是兩種方式獲取到的Class不一樣
    // 說明此對象在以前被動態地替換isa,(多是涉及到了KVO)
    // 這時候咱們使用的中間層類對象就不須要動態建立一個了,直接使用以前動態建立的就行
    if (presentClass != originalClass) {
        // 重寫方法
        _swizzleForwardInvocation(originalClass);
        _swizzleRespondsToSelector(originalClass);
        _swizzleMethodSignatureForSelector(originalClass);

        interlayerClass = originalClass;
    }
    else {
        const char *interlayerClassName = [_prefixName stringByAppendingString:NSStringFromClass(originalClass)].UTF8String;
        // 首先判斷Runtime中是否已經註冊過此中間層類
        // 若沒有註冊,則動態建立中間層類而且重寫其中的指定方法,最後進行註冊
        interlayerClass = objc_getClass(interlayerClassName);
        if (!interlayerClass) {
            // 基於原始的類對象建立新的中間層類對象
            interlayerClass = objc_allocateClassPair(originalClass, interlayerClassName, 0);
            if (!interlayerClass) return nil;

            // 重寫方法
            _swizzleForwardInvocation(interlayerClass);
            _swizzleRespondsToSelector(interlayerClass);
            _swizzleMethodSignatureForSelector(interlayerClass);
            _swizzleGetClass(interlayerClass, presentClass);

            // 註冊中間層類對象
            objc_registerClassPair(interlayerClass);
        }
    }
    // isa替換
    object_setClass(self, interlayerClass);
    objc_setAssociatedObject(self, _interlayerClassExist, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return interlayerClass;
}複製代碼

咱們以_interlayerClassExist做爲key,爲對象設置了一個布爾類型的關聯對象值,用於標示咱們在以前是否已經爲此對象進行過isa-swizzling,若isa已經替換過,則直接用object_getClass返回中間層類對象。

接下來作的事情比較微妙,咱們比較了使用getClass方法以及object_getClass獲取到的兩個類對象,這樣作的目的是爲了判別這個對象在以前是否也在其餘地方進行過isa-swzzling,由於object_getClass獲取到的類對象是實際的類對象(isa),而getClass方法可能已經被重寫了,獲取到的類對象多是虛假的,其中,最典型的isa-swizzling莫過於KVO了。當咱們判別到對象在以前已經進行過isa-swizzling了,咱們就不必再本身建立一箇中間層類對象了,直接使用現有的就行,反正也不會污染到本來的類對象。當咱們判別到對象在以前沒有進行過isa-swizzling,咱們就須要手動建立一箇中間層類對象,這個類對象繼承自原來的類對象,且名字也是在舊類對象之上進行稍微的修飾。

接着,咱們就對中間層類對象的某些方法進行重寫,這裏主要有四個方法須要重寫:

  • forwardInvocation: 重寫的緣由在上面的原理中已經提到。
  • respondsToSelector: 考慮到中間層類對象中雖然沒有實現指定的方法,可是在咱們傳入的協議中確實找到了它,因此已經在中間層類對象動態添加了,此時咱們就須要重寫responseToSelector,使得咱們經過responseToSelector可以得知對象已經實現了此方法。舉個例子,咱們平時調用代理方法時,總會加上一句判斷:

    if ([_delegate respondsToSelector: @selector(XXX)]) {
         [_delegate XXX];
     }複製代碼

    若咱們只是簡單地爲中間層類對象動態添加協議的方法,卻沒有重寫respondsToSelector,那個此時這個方法也不可能會被調用。

  • methodSignatureForSelector: 爲了能讓動態添加的方法也能經過此方法獲取到方法簽名,咱們須要重寫。
  • getClass: 爲了欺騙表層、欺騙世界,咱們須要重寫此方法,讓這個方法返回的類對象非實際的中間層類對象,而是虛假的舊類對象。

重寫getClass

// 混淆getClass方法
static void _swizzleGetClass(Class _Nonnull class, Class _Nonnull expectedClass) {
    SEL selector = @selector(class);
    Method getClassMethod = class_getInstanceMethod(class, selector);
    id newImp = ^(id self) {
        return expectedClass;
    };
    class_replaceMethod(class, selector, imp_implementationWithBlock(newImp), method_getTypeEncoding(getClassMethod));
}複製代碼

能夠看到,咱們重寫方法使用的不是方法交換技術,而是直接經過class_replaceMethod,將新的方法實現替換進方法中,而新的方法實現咱們將利用block來建立。這裏須要注意的是,經過imp_implementationWithBlock函數,咱們能夠利用block建立方法實現,而這個block的類型有所約束:返回類型跟方法實現的同樣,而在參數中,第一個參數必須爲id類型,表明此時發送消息的實例,後面緊接着的是方法的實際參數。而方法實現的類型中,前兩個參數爲idSEL,表明發送消息的實例以及選擇器,後面才接方法的實際參數。

重寫respondsToSelector

// 混淆respondsToSelector方法
static void _swizzleRespondsToSelector(Class _Nonnull class) {
    SEL originalSelector = @selector(respondsToSelector:);
    Method method = class_getInstanceMethod(class, originalSelector);
    BOOL (*originalImplementation)(id, SEL, SEL) = (void *)method_getImplementation(method);
    id newImp = ^(id self, SEL selector) {
        Method method = class_getInstanceMethod(class, selector);
        if (method && method_getImplementation(method) == _objc_msgForward) {
            if (objc_getAssociatedObject(self, _modifySelector(selector)))
                return YES;
        }
        return originalImplementation(self, originalSelector, selector);
    };
    class_replaceMethod(class, originalSelector, imp_implementationWithBlock(newImp), method_getTypeEncoding(method));
}複製代碼

經過method_getImplementation函數,咱們能夠直接獲取到本來的方法實現,方法實現的類型爲函數指針,這讓咱們能夠在後面直接調用它。

中間判斷的意義是:此方法是中間層類對象動態建立的,由於此時方法多是類沒有實現但協議聲明瞭,若此時實例對這個方法有進行監聽,respondsToSelector則返回YES,反之則返回NO,由於這個動態添加的方法只是爲了實現方法調用的監聽回調,既然實例沒有對其進行監聽,那麼respondsToSelector直接返回NO就行。

重寫methodSignatureForSelector

// 混淆methodSignatureForSelector方法
static void _swizzleMethodSignatureForSelector(Class _Nonnull class) {
    SEL msfsSelector = @selector(methodSignatureForSelector:);
    Method method = class_getInstanceMethod(class, msfsSelector);
    id newIMP = ^(id self, SEL selector) {
        Method method = class_getInstanceMethod(class, selector);
        if (!method) {
            struct objc_super super = {
                self,
                class_getSuperclass(class)
            };
            NSMethodSignature *(*sendToSuper)(struct objc_super *, SEL, SEL) = (void *)objc_msgSendSuper;
            return sendToSuper(&super, msfsSelector, selector);
        }
        return [NSMethodSignature signatureWithObjCTypes: method_getTypeEncoding(method)];
    };
    class_replaceMethod(class, msfsSelector, imp_implementationWithBlock(newIMP), method_getTypeEncoding(method));
}複製代碼

這新的實現中,咱們先經過傳入的方法選擇器找到對應的方法,若此時方法存在,咱們經過方法的類型編碼建立方法簽名並返回,若此時方法不存在,咱們則調用父類的methodSignatureForSelector方法。咱們知道,在平時咱們經過[super XXX]向父類發送消息時,最終都是轉換成objc_msgSendSuper的形式,而此時咱們是使用block來建立新的方法實現,不能使用到[super XXX]這種形式,因此咱們直接經過objc_msgSendSuper來向父類發送消息。

重寫forwardInvocation

// 混淆forwardInvocation方法
static void _swizzleForwardInvocation(Class _Nonnull class) {
    SEL fiSelector = @selector(forwardInvocation:);
    Method fiMethod = class_getInstanceMethod(class, fiSelector);
    void (*originalFiImp)(id, SEL, NSInvocation *) = (void *)method_getImplementation(fiMethod);
    id newFiImp = ^(id self, NSInvocation *invocation) {
        SEL runtimeSelector = _modifySelector(invocation.selector);
        MessageDidSendCallback callback = (MessageDidSendCallback)objc_getAssociatedObject(self, runtimeSelector);
        if (!callback) {
            if (originalFiImp)
                originalFiImp(self, fiSelector, invocation);
            else
                [self doesNotRecognizeSelector: invocation.selector];
        } else {
            if ([self respondsToSelector: runtimeSelector]) {
                invocation.selector = runtimeSelector;
                [invocation invoke];
            }
            callback(_getArguments(invocation));
        }
    };
    class_replaceMethod(class, fiSelector, imp_implementationWithBlock(newFiImp), method_getTypeEncoding(fiMethod));
}複製代碼

在新的實現中,咱們經過NSInvocation轉發消息到新的方法中,方式就是直接設置invocation的selector爲新方法的選擇器。另外,咱們經過_getArguments函數從invocation中把傳入方法的參數提取出來,傳入在以前設置好的callback block關聯對象進行調用,這樣咱們就可以將方法調用回調到外界了。

咱們看下_getArguments函數:

static NSArray * _Nonnull _getArguments(NSInvocation * _Nonnull invocation) {
    NSUInteger count = invocation.methodSignature.numberOfArguments;
    // 除去開頭的兩個參數(id, SEL),表明實例本身以及方法的選擇器
    NSMutableArray *arr = [NSMutableArray arrayWithCapacity:count - 2];
    for (NSUInteger i = 2; i < count; i ++)
        [arr addObject:_getArgument(invocation, i)];
    return arr;
}

// 獲取參數,copy from `ReactiveCocoa`
static id _Nonnull _getArgument(NSInvocation * _Nonnull invocation, NSUInteger index) {
    const char *argumentType = [invocation.methodSignature getArgumentTypeAtIndex:index];

#define RETURN_VALUE(type) \
else if (strcmp(argumentType, @encode(type)) == 0) {\
type val = 0; \
[invocation getArgument:&val atIndex:index]; \
return @(val); \
}

    // Skip const type qualifier.
    if (argumentType[0] == 'r') {
        argumentType++;
    }

    if (strcmp(argumentType, @encode(id)) == 0
        || strcmp(argumentType, @encode(Class)) == 0
        || strcmp(argumentType, @encode(void (^)(void))) == 0
        ) {
        __unsafe_unretained id argument = nil;
        [invocation getArgument:&argument atIndex:index];
        return argument;
    }
    RETURN_VALUE(char)
    RETURN_VALUE(short)
    RETURN_VALUE(int)
    RETURN_VALUE(long)
    RETURN_VALUE(long long)
    RETURN_VALUE(unsigned char)
    RETURN_VALUE(unsigned short)
    RETURN_VALUE(unsigned int)
    RETURN_VALUE(unsigned long)
    RETURN_VALUE(unsigned long long)
    RETURN_VALUE(float)
    RETURN_VALUE(double)
    RETURN_VALUE(BOOL)
    RETURN_VALUE(const char *)
    else {
        NSUInteger size = 0;
        NSGetSizeAndAlignment(argumentType, &size, NULL);
        NSCParameterAssert(size > 0);
        uint8_t data[size];
        [invocation getArgument:&data atIndex:index];

        return [NSValue valueWithBytes:&data objCType:argumentType];
    }
}複製代碼

_getArguments函數中,咱們獲取到參數數量,並過濾掉前面兩個參數(由於前面兩個參數分別表明調用方法的實例以及此方法的選擇器,並非實際傳入方法的參數),再一個個經過_getArgument函數獲取到最終的值。_getArgument函數的機理有些複雜,我直接拷貝自ReactiveCocoa的源碼。

完整代碼

#import "NSObject+Runtime.h"
#import <objc/runtime.h>
#import <objc/message.h>

static SEL _Nonnull _modifySelector(SEL _Nonnull selector);
static Class _Nullable _swizzleClass(id _Nonnull self);

@implementation NSObject (Runtime)

- (void)listen:(SEL)selector in:(Protocol *)protocol with:(MessageDidSendCallback)callback {
    SEL runtimeSelector = _modifySelector(selector);
    // 引用閉包
    objc_setAssociatedObject(self, runtimeSelector, callback, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    // isa-swizzling
    Class interlayerClass = _swizzleClass(self);
    Method originalMethod = class_getInstanceMethod(interlayerClass, selector);
    IMP originalImplementation = method_getImplementation(originalMethod);

    // 判斷是否具備該方法
    // 若是沒有,試圖在指定的協議中尋找
    if (!originalMethod) {
        if (!protocol) return;
        struct objc_method_description des = protocol_getMethodDescription(protocol, selector, YES, YES);
        if (!des.name)
            des = protocol_getMethodDescription(protocol, selector, NO, YES);
        if (des.types)
            class_addMethod(interlayerClass, selector, _objc_msgForward, des.types);
    }
    // 若是原始方法沒有作替換
    // 則將原始方法的實現改成_objc_msgForward
    else if (originalImplementation != _objc_msgForward) {
        const char *typeEncoding = method_getTypeEncoding(originalMethod);
        class_addMethod(interlayerClass, runtimeSelector, originalImplementation, typeEncoding);
        class_replaceMethod(interlayerClass, selector, _objc_msgForward, typeEncoding);
    }
}

@end

#pragma mark - Private 私有
// 用於在原有的基礎上標示Selector以及中間層類對象的名字,便於區分
static NSString * const _prefixName = @"_Runtime_";

// 關聯對象Key,是否已經存在中間層類對象
static void *_interlayerClassExist = &_interlayerClassExist;

// 獲取參數
static id _Nonnull _getArgument(NSInvocation * _Nonnull invocation, NSUInteger index) {
    const char *argumentType = [invocation.methodSignature getArgumentTypeAtIndex:index];

#define RETURN_VALUE(type) \
else if (strcmp(argumentType, @encode(type)) == 0) {\
type val = 0; \
[invocation getArgument:&val atIndex:index]; \
return @(val); \
}

    // Skip const type qualifier.
    if (argumentType[0] == 'r') {
        argumentType++;
    }

    if (strcmp(argumentType, @encode(id)) == 0
        || strcmp(argumentType, @encode(Class)) == 0
        || strcmp(argumentType, @encode(void (^)(void))) == 0
        ) {
        __unsafe_unretained id argument = nil;
        [invocation getArgument:&argument atIndex:index];
        return argument;
    }
    RETURN_VALUE(char)
    RETURN_VALUE(short)
    RETURN_VALUE(int)
    RETURN_VALUE(long)
    RETURN_VALUE(long long)
    RETURN_VALUE(unsigned char)
    RETURN_VALUE(unsigned short)
    RETURN_VALUE(unsigned int)
    RETURN_VALUE(unsigned long)
    RETURN_VALUE(unsigned long long)
    RETURN_VALUE(float)
    RETURN_VALUE(double)
    RETURN_VALUE(BOOL)
    RETURN_VALUE(const char *)
    else {
        NSUInteger size = 0;
        NSGetSizeAndAlignment(argumentType, &size, NULL);
        NSCParameterAssert(size > 0);
        uint8_t data[size];
        [invocation getArgument:&data atIndex:index];

        return [NSValue valueWithBytes:&data objCType:argumentType];
    }
}

static NSArray * _Nonnull _getArguments(NSInvocation * _Nonnull invocation) {
    NSUInteger count = invocation.methodSignature.numberOfArguments;
    // 除去開頭的兩個參數(id, SEL),表明實例本身以及方法的選擇器
    NSMutableArray *arr = [NSMutableArray arrayWithCapacity:count - 2];
    for (NSUInteger i = 2; i < count; i ++)
        [arr addObject:_getArgument(invocation, i)];
    return arr;
}

// 修飾Selector,返回通過前綴名拼接的Selector
static SEL _Nonnull _modifySelector(SEL _Nonnull selector) {
    NSString *originalName = NSStringFromSelector(selector);
    return NSSelectorFromString([_prefixName stringByAppendingString:originalName]);
}

// 混淆forwardInvocation方法
static void _swizzleForwardInvocation(Class _Nonnull class) {
    SEL fiSelector = @selector(forwardInvocation:);
    Method fiMethod = class_getInstanceMethod(class, fiSelector);
    void (*originalFiImp)(id, SEL, NSInvocation *) = (void *)method_getImplementation(fiMethod);
    id newFiImp = ^(id self, NSInvocation *invocation) {
        SEL runtimeSelector = _modifySelector(invocation.selector);
        MessageDidSendCallback callback = (MessageDidSendCallback)objc_getAssociatedObject(self, runtimeSelector);
        if (!callback) {
            if (originalFiImp)
                originalFiImp(self, fiSelector, invocation);
            else
                [self doesNotRecognizeSelector: invocation.selector];
        } else {
            if ([self respondsToSelector: runtimeSelector]) {
                invocation.selector = runtimeSelector;
                [invocation invoke];
            }
            callback(_getArguments(invocation));
        }
    };
    class_replaceMethod(class, fiSelector, imp_implementationWithBlock(newFiImp), method_getTypeEncoding(fiMethod));
}

// 混淆getClass方法
static void _swizzleGetClass(Class _Nonnull class, Class _Nonnull expectedClass) {
    SEL selector = @selector(class);
    Method getClassMethod = class_getInstanceMethod(class, selector);
    id newImp = ^(id self) {
        return expectedClass;
    };
    class_replaceMethod(class, selector, imp_implementationWithBlock(newImp), method_getTypeEncoding(getClassMethod));
}

// 混淆respondsToSelector方法
static void _swizzleRespondsToSelector(Class _Nonnull class) {
    SEL originalSelector = @selector(respondsToSelector:);
    Method method = class_getInstanceMethod(class, originalSelector);
    BOOL (*originalImplementation)(id, SEL, SEL) = (void *)method_getImplementation(method);
    id newImp = ^(id self, SEL selector) {
        Method method = class_getInstanceMethod(class, selector);
        if (method && method_getImplementation(method) == _objc_msgForward) {
            if (objc_getAssociatedObject(self, _modifySelector(selector)))
                return YES;
        }
        return originalImplementation(self, originalSelector, selector);
    };
    class_replaceMethod(class, originalSelector, imp_implementationWithBlock(newImp), method_getTypeEncoding(method));
}

// 混淆methodSignatureForSelector方法
static void _swizzleMethodSignatureForSelector(Class _Nonnull class) {
    SEL msfsSelector = @selector(methodSignatureForSelector:);
    Method method = class_getInstanceMethod(class, msfsSelector);
    id newIMP = ^(id self, SEL selector) {
        Method method = class_getInstanceMethod(class, selector);
        if (!method) {
            struct objc_super super = {
                self,
                class_getSuperclass(class)
            };
            NSMethodSignature *(*sendToSuper)(struct objc_super *, SEL, SEL) = (void *)objc_msgSendSuper;
            return sendToSuper(&super, msfsSelector, selector);
        }
        return [NSMethodSignature signatureWithObjCTypes: method_getTypeEncoding(method)];
    };
    class_replaceMethod(class, msfsSelector, imp_implementationWithBlock(newIMP), method_getTypeEncoding(method));
}

// isa-swizzling
static Class _Nullable _swizzleClass(id _Nonnull self) {
    Class originalClass = object_getClass(self);
    // 若是在以前已經替換了isa,則只需直接返回
    if ([objc_getAssociatedObject(self, _interlayerClassExist) boolValue])
        return originalClass;

    Class interlayerClass;

    Class presentClass = [self class];
    // 若以前沒有手動替換過isa,可是兩種方式獲取到的Class不一樣
    // 說明此對象在以前被動態地替換isa,(多是涉及到了KVO)
    // 這時候咱們使用的中間層類對象就不須要動態建立一個了,直接使用以前動態建立的就行
    if (presentClass != originalClass) {
        // 重寫方法
        _swizzleForwardInvocation(originalClass);
        _swizzleRespondsToSelector(originalClass);
        _swizzleMethodSignatureForSelector(originalClass);

        interlayerClass = originalClass;
    }
    else {
        const char *interlayerClassName = [_prefixName stringByAppendingString:NSStringFromClass(originalClass)].UTF8String;
        // 首先判斷Runtime中是否已經註冊過此中間層類
        // 若沒有註冊,則動態建立中間層類而且重寫其中的指定方法,最後進行註冊
        interlayerClass = objc_getClass(interlayerClassName);
        if (!interlayerClass) {
            // 基於原始的類對象建立新的中間層類對象
            interlayerClass = objc_allocateClassPair(originalClass, interlayerClassName, 0);
            if (!interlayerClass) return nil;

            // 重寫方法
            _swizzleForwardInvocation(interlayerClass);
            _swizzleRespondsToSelector(interlayerClass);
            _swizzleMethodSignatureForSelector(interlayerClass);
            _swizzleGetClass(interlayerClass, presentClass);

            // 註冊中間層類對象
            objc_registerClassPair(interlayerClass);
        }
    }
    // isa替換
    object_setClass(self, interlayerClass);
    objc_setAssociatedObject(self, _interlayerClassExist, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return interlayerClass;
}複製代碼

自此咱們就完成了對Runtime的適配,如今咱們能夠直接利用listen方法來實現對指定方法的監聽了。

擴展響應式框架

有了前面的基礎,將響應式框架向Runtime擴展起來就十分簡單了。

因爲前面我編寫響應式框架是使用的是Swift語言,因此這裏我也是經過Swift語言進行擴展:

extension NSObject {
    func listen(_ selector: Selector, in proto: Protocol? = nil) -> Signal<[Any]> {
        return Signal { [weak self] observer in
            self?.listen(selector, in: proto, with: observer.sendNext)
        }
    }
}複製代碼

如今,咱們能夠把玩一下通過Runtime擴展後的響應式框架了:

listen(#selector(UITableViewDelegate.tableView(_:didSelectRowAt:)), in: UITableViewDelegate.self)
     .map { $0[1] as! IndexPath }
     .map { [weak self] in self?._data[$0.row] }
     .subscribe(next: { [weak self] in
         guard let uid = $0 else { return }
         self?.navigationController?.pushViewController(MyViewController(uid: uid), animated: true)
     })

_tableView.delegate = self複製代碼

參考

文章主要思想及實現參考自ReactiveCocoa,實現的代碼可能存在某些缺漏或不足,若你們有興趣可直接查看ReactiveCocoa的源碼:ReactiveCocoa

本文純屬我的看法,若你們發現文章部分有誤,歡迎在評論區提出。

相關文章
相關標籤/搜索