你能使用Class对象强制转换Objective-C对象吗?

时间:2018-04-04 21:59:47

标签: objective-c unit-testing swizzling

通过Objective-C对象我的意思是像MyViewController和类对象MyViewController.superclass。

例如,在此函数中,您如何使用self投射targetClass

// 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并运行调配实现。在ViewController.viewDidAppear混合实现运行后,将调用原始实现。好。

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

换句话说,递归循环开始于一个已经被调配的子方法调用它的超级方法,这种方法也被调整了。在混合方法ViewController.viewDidAppear用于调用原始实现,并且由于self在运行时是最多的子类(在此示例中为ViewController),超级的混合方法会调用子进程#39 ; s原始方法,所以循环重复。

目标:

找到一种方法来调用一个混合类的原始实现。

self在运行时可能是某个子节点,并且父节点和子节点的方法在子方法调用父方法的地方进行了调整时,必须有一种方法可以明确选择哪个类#c}使用运行时函数self

运行的实现

尝试并失败:

class_getInstanceMethod转换为另一个类,因为我无法找到如何使用Class对象进行转换。要在更通用的情况下使用这个调配代码,必须使用存储原始类的Class对象,而不是显式编写类类型。

ViewController.swift

self

NSObject的+ Swizzling.h

// 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.m

#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

输出

#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

2 个答案:

答案 0 :(得分:2)

以下是您可能会这样做的示例:

<input type="text" placeholder="From ..." [(ngModel)]=source name="source">

<input type="text" placeholder="To ..." [(ngModel)]=destination name="destination">

<input type="text" class="input datepicker" value="Mm/Dd/Yy" [(ngModel)]=date name="date">

 <button (click)=searchCity() type="submit">Search</button>

您可以使用- (void)useSuperclassImplementation:(Class)targetClass targetSelector:(SEL)targetSelector { if ([self isKindOfClass: targetClass] && [targetClass respondsToSelector:targetSelector]) { ((void (*)(id, SEL))[targetClass methodForSelector:targetSelector])(self, targetSelector); } } 并忽略警告

对此答案的解决方案有详细解释:https://stackoverflow.com/a/20058585/1755720

编辑:

[targetClass performSelector:targetSelector];

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

答案 1 :(得分:1)

将此描述为铸造只是让读者感到困惑,也可能是你自己。类型转换是纯静态的,编译时的事情。 POST _analyze { "tokenizer": "standard", "char_filter": [ "html_strip" ], "text": "<html>It will be raining in yosemite this weekend</html>" } ,作为一个变量,当然是一个动态的,运行时的东西。在运行时,消息接收器表达式的静态类型与代码的行为无关。那时候,这些信息或多或少都消失了。 targetClass[self someMethod...]都将编译为完全相同的代码。

您只是在寻找:

[(AnyType*)self someMethod...]

您目前所在的位置:

[targetClass instanceMethodForSelector:selector]