你能用 Class object 投射 Objective-C object 吗?

Can you cast an Objective-C object using a Class object?

Objective-C object 我的意思是像 MyViewController 和 class object MyViewController.superclass.

例如,在此函数中,您将如何使用 targetClass 转换 self

// This code doesn't compile
- (void) useOtherClassImplementation :(Class) targetClass :(SEL) targetSelector {
    if ([self isKindOfClass: targetClass]) {
            ((void (*)(id, SEL))[((targetClass *) self) methodForSelector:selector])(self, selector);
        }
}

有没有办法像 ((targetClass *) self) 那样不能编译?


案例研究

概览:

当 ViewController 出现时,将调用 ViewController.viewDidAppear 并调用混合实现 运行s。在 ViewController.viewDidAppear swizzled 实现 运行s 之后,调用原始实现。好

ViewController.viewDidAppear原始实现运行s时,UIViewController.viewDidAppear被super.viewDidAppear()调用。 UIViewController.viewDidAppear 的调配实现被调用 运行,并且在该调配实现中 self 用于调用原始实现但是因为 self 是 ViewController 而不是UIViewController 在 运行 时,ViewController.viewDidAppear 再次调用 swizzled 实现,从而开始递归循环。

换句话说,递归循环在 child 的方法(已被调配)调用它的超方法(也已被调配)时开始。在swizzled方法中使用self调用原实现,由于self在运行时最childclass(本例ViewController) super 的 swizzled 方法再次调用 child 的原始方法,如此循环重复。

目标:

找到一种方法调用 swizzled class 的原始实现。

self 在 运行 时可能是一些 child,并且 parent 和 child 都有他们的方法在 child 方法调用 parent 方法,必须有一种方法可以通过使用 运行time 函数 class_getInstanceMethod

尝试失败:

self 转换为另一个 class 因为我不知道如何使用 Class object 进行转换。要在更通用的情况下使用此混合代码,必须使用存储原始 class 的 Class object 而不是显式写入 class 类型。

ViewController.swift

// Child class ViewController inherits from parent class UIViewController
class ViewController: UIViewController {

    override func viewDidLoad() {
        _ = ViewController.swizzleViewDidAppearParentAndChild
    }

    override func viewDidAppear(_ animated: Bool) {
        // NOTICE the call to parent's function
        super.viewDidAppear(animated)
        // never reaches here
        print("In viewcontroller viewdidappear")
    }

    // swizzles in the block for both UIViewController and ViewController
    // recursively prints
    //    TestApp.ViewController is about to perform viewDidAppear:
    //
    static var swizzleViewDidAppearParentAndChild: Void = {
        SwizzledObject.createTrampoline(for: UIViewController.self, selector: #selector(UIViewController.viewDidAppear(_:)), with: printBeforePerforming)
        SwizzledObject.createTrampoline(for: ViewController.self, selector: #selector(ViewController.viewDidAppear(_:)), with: printBeforePerforming)
    }()

    // a block to be used before a method call
    static var printBeforePerforming: SelectorTrampolineBlock {
        return { target, selector in
            print("\(NSStringFromClass(type(of: target as AnyObject))) is about to perform \(NSStringFromSelector(selector!))")
        }
    }

}

NSObject+Swizzling.h

#import <Foundation/Foundation.h>

@interface SwizzledObject : NSObject

typedef void (^ SelectorTrampolineBlock)(id target, SEL targetSelector);

+ (SEL) createTrampolineForClass:(Class)targetClass selector:(SEL)targetSelector withBlock:(SelectorTrampolineBlock) block;

@end

NSObject+Swizzling.m

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

@implementation SwizzledObject

// creates a method at runtime that calls the trampolineBlock, and then performs original method
+ (SEL) createTrampolineForClass:(Class)targetClass selector:(SEL)targetSelector withBlock:(SelectorTrampolineBlock) block {
    SEL trampolineSelector = NSSelectorFromString([NSString stringWithFormat:@"performBefore__%@", NSStringFromSelector(targetSelector)]);

    Method originalMethod = class_getInstanceMethod(targetClass, targetSelector);
    if (originalMethod == nil) {
        return nil;
    }

    IMP dynamicImp = imp_implementationWithBlock(^(id self, bool param) {
        block(self, targetSelector);
        if (!self || ![self respondsToSelector:trampolineSelector]) {return;}
        ((void (*)(id, SEL, bool))[self methodForSelector:trampolineSelector])(self, trampolineSelector, param);
    });

    class_addMethod(targetClass, trampolineSelector, dynamicImp, method_getTypeEncoding(originalMethod));

    Method newMethod = class_getInstanceMethod(targetClass, targetSelector);
    if (newMethod == nil) {
        return nil;
    }

    [SwizzledObject injectSelector:targetClass :trampolineSelector :targetClass :targetSelector];

    return trampolineSelector;
}

// Switches/swizzles method
+ (BOOL) injectSelector:(Class) swizzledClass :(SEL) swizzledSelector :(Class) originalClass :(SEL) orignalSelector {
    NSLog(@"Injecting selector %@ for class %@ with %@", NSStringFromSelector(orignalSelector), NSStringFromClass(originalClass), NSStringFromSelector(swizzledSelector));
    Method newMeth = class_getInstanceMethod(swizzledClass, swizzledSelector);
    IMP imp = method_getImplementation(newMeth);
    const char* methodTypeEncoding = method_getTypeEncoding(newMeth);

    BOOL existing = class_getInstanceMethod(originalClass, orignalSelector) != NULL;

    if (existing) {
        class_addMethod(originalClass, swizzledSelector, imp, methodTypeEncoding);
        newMeth = class_getInstanceMethod(originalClass, swizzledSelector);
        Method orgMeth = class_getInstanceMethod(originalClass, orignalSelector);
        method_exchangeImplementations(orgMeth, newMeth);
    }
    else {
        class_addMethod(originalClass, orignalSelector, imp, methodTypeEncoding);
    }

    return existing;
}

@end

输出

2018-04-04 17:50:43.201458-0700 TestApp[26612:6527489] Injecting selector viewDidAppear: for class UIViewController with performBefore__viewDidAppear:
2018-04-04 17:50:43.202641-0700 TestApp[26612:6527489] Injecting selector viewDidAppear: for class TestApp.ViewController with performBefore__viewDidAppear:
TestApp.ViewController is about to perform viewDidAppear:
TestApp.ViewController is about to perform viewDidAppear:
TestApp.ViewController is about to perform viewDidAppear:
(infinitely prints previous line)

以下是您可以如何操作的示例:

- (void)useSuperclassImplementation:(Class)targetClass targetSelector:(SEL)targetSelector {
    if ([self isKindOfClass: targetClass] && [targetClass respondsToSelector:targetSelector]) {
        ((void (*)(id, SEL))[targetClass methodForSelector:targetSelector])(self, targetSelector);
    }
}

您可以使用 [targetClass performSelector:targetSelector]; 并忽略警告

这个答案有详细的解说:


编辑:

        struct objc_super superInfo = {
            .receiver = [self class],
            .super_class = targetClass
        };

        id (*objc_superAllocTyped)(struct objc_super *, SEL) = (void *)&objc_msgSendSuper;
        (*objc_superAllocTyped)(&superInfo, targetSelector);

^ 也是直接调用 super 的另一种选择,但它不太安全,因为你真的需要确定目标 class 是 superclass - 我需要问,你为什么做这个?可能有更简单的解决方案。

对于 reader 和您自己来说,将其表述为演员只是令人困惑。类型转换是纯静态的,compile-time。 targetClass,作为一个变量,当然是动态的,run-time。在 run-time,消息接收者表达式的静态类型与代码的行为无关。该信息或多或少在这一点上消失了。 [self someMethod...][(AnyType*)self someMethod...] 都将被编译为完全相同的代码。

您是否正在寻找:

[targetClass instanceMethodForSelector:selector]

您目前所在的位置:

[((targetClass *) self) methodForSelector:selector]

?