【问题标题】:Method Swizzling with Swift 5.5Swift 5.5 的方法调配
【发布时间】:2021-12-29 15:37:30
【问题描述】:

我正在尝试覆盖 NSURL 上的 initWithString 方法,我查看了这些过去的问题/帖子。

我尝试了以下代码,但无法固定新的 log_initWithString 方法,swiftc 不会标记任何内容,但在运行时我会收到 index/index.swift:20: Fatal error: Unexpectedly found nil while unwrapping an Optional value

import AppKit
import WebKit

let app = NSApplication.shared

//example: https://github.com/kickstarter/ios-oss/blob/39edeeaefb5cfb26276112e0af5eb6948865cf34/Library/DataSource/UIView-Extensions.swift

private var hasSwizzled = false

extension NSURL {
    public final class func doSwizzle() {
        guard !hasSwizzled else { return }

        hasSwizzled = true
        
        let original = Selector("initWithString:")
        let swizzled = Selector("log_initWithString:")

        let originalMethod = class_getInstanceMethod(NSURL.self, original)!
        let swizzledMethod = class_getInstanceMethod(NSURL.self, swizzled)!

        let didAddMethod = class_addMethod(NSURL.self, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

        if didAddMethod {
            class_replaceMethod(NSURL.self, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
    
    @objc internal func log_initWithString(string URLString: String) {
        NSLog("Hello from initWithString")
        
        return log_initWithString(string: URLString)
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {
    let window = NSWindow.init(contentRect: NSRect(x: 0, y: 0, width: 750, height: 600), styleMask: [
        NSWindow.StyleMask.titled,
        NSWindow.StyleMask.closable,
        NSWindow.StyleMask.resizable,
        NSWindow.StyleMask.miniaturizable
    ], backing: NSWindow.BackingStoreType.buffered, defer: false)
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        NSURL.doSwizzle()
        
        let webview = WKWebView(frame: window.contentView!.frame)
        let request = URLRequest(url: URL(string: "https://google.com")!)

        window.contentView?.addSubview(webview)
        webview.load(request)
        
        window.makeKeyAndOrderFront(nil)
        window.orderFrontRegardless()
        window.center()
    }
}

let delegate = AppDelegate()

app.delegate = delegate

app.run()

【问题讨论】:

  • 值得注意:didAddMethod 应该始终为 false,因为您已经在 NSURL 类中创建了方法。
  • @Alexander 已修复,很好。

标签: swift macos swift5


【解决方案1】:

那是因为

@objc internal func log_initWithString(string URLString: String)

log_initWithStringWithString: 而不是log_initWithString: 的身份向Objective-C 公开。

明显的解决方法是:

...
let swizzled = Selector("log_initWithStringWithString:")
...

为了更好地进行编译时检查,您可以使用以下语法:

let original = #selector(NSURL.init(string:))
let swizzled = #selector(NSURL.log_initWithString(string:))

这将编译,但至少还有一件事需要修复 - 混合方法返回值。在您的示例中:

@objc internal func log_initWithString(string URLString: String) {
    NSLog("Hello from initWithString")

    return log_initWithString(string: URLString)
}

什么都不返回,而 NSURL 的 init 应该返回 NSURL,所以解决方法是:

@objc internal func log_initWithString(string URLString: String) -> NSURL {
...

【讨论】:

  • 谢谢!唯一的问题是现在log_initWithString 似乎捕获了所有inits,大部分都是空字符串,有解决办法吗?
  • @Faustino 它可能被其他初始化程序调用,但很可能来自其他调用站点,因为 swizzling 替换了类的所有实例的方法。我建议永远不要使用 swizzling,除非完全没有其他方法(这在 Objective-C 时代也是如此)。对于像 swizzling 这样强大的工具,充分了解其内部结构非常重要。
  • 有道理,我会小心的。再次感谢。
  • 也可以使用@objc(log_initWithString) internal func internal func log_initWithString(string URLString: String)明确设置目标c名称为log_initWithString
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-05
相关资源
最近更新 更多