如何在Swift中交换init方法

5

我正在跟随 Swift 和 Objective-C 运行时,对于普通方法它是有效的。

我想要交换 init 方法,以我的理解,init 就像一个类方法。所以我尝试了将 init 作为实例和类方法进行交换。但它似乎并不起作用。

我可以使用 Objective C 让它工作,只是想知道如何在 Swift 中让它工作

摘自我的 gist

dispatch_once(&Static.token) {
            let originalSelector = Selector("init:source:destination:")
            let swizzledSelector = Selector("ftg_init:source:destination:")

            let originalMethod = class_getClassMethod(self, originalSelector)
            let swizzledMethod = class_getClassMethod(self, swizzledSelector)

            let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

            if didAddMethod {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }

你的类继承自哪个类?我相信交换方法只适用于继承自NSObject或使用@objc定义为Objective-C类的类。 - Craig Siemens
@CleverError,我在问题中链接了我的Gist,你能看一下吗? - onmyway133
2个回答

6

当创建方法的选择器时,你应该以Obj C方法签名为基础,因为交换方法是使用Obj C运行时实现的。

因此,原始选择器应该是

initWithIdentifier:source:destination:

现在有点奇怪,因为您定义了init方法,使得第一个参数上的标签是必需的(通过两次使用identifier),因此您想要使用的选择器实际上是

ftg_initWithIdentifier:source:destination:

我找到的唯一文档只谈到了从Obj C翻译成Swift,但看起来正好相反,是从Swift翻译成Obj C。

接下来,init...是一个实例方法,因此您需要进行两个更改。你需要将class_getClassMethod更改为class_getInstanceMethod,并且你需要从ft_init...方法中删除class

因此,最终代码应该像这样(对我有效):

dispatch_once(&Static.token) {
    let originalSelector = Selector("initWithIdentifier:source:destination:")
    let swizzledSelector = Selector("ftg_initWithIdentifier:source:destination:")

    let originalMethod = class_getInstanceMethod(self, originalSelector)
    let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

    let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

    if didAddMethod {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

func ftg_init(identifier identifier: String!,
    source: UIViewController,
    destination: UIViewController) -> UIStoryboardSegue {

    return ftg_init(identifier: identifier,
        source: source,
        destination: destination.ftg_resolve())
}

5

以下是使用Swift 4.2进行UIImage初始化的方法:

@objc static func ftg_imageNamed(_ name: String) -> UIImage? {
    ...
}

private static func swizzleInitImplementation() {
    let originalSelector = #selector(UIImage.init(named:))
    let swizzledSelector = #selector(UIImage.ftg_imageNamed(_:))

    guard let originalMethod = class_getClassMethod(self, originalSelector), let swizzledMethod = class_getClassMethod(self, swizzledSelector) else {
        assertionFailure("The methods are not found!")
        return
    }

    let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
    if didAddMethod {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

我投了一票,因为在你的帖子之前我无法理解#selector(UIImage.init(named:))。谢谢!还有一件事:我在全局范围内得到了“静态方法只能在类型上声明”的错误提示。 - ScottyBlades
另外,我在将self强制转换为AnyClass时遇到了问题,因为这些方法要求这些方法应该存在于uiimage扩展中... - ScottyBlades

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接