Swift中InputStream的URL初始化方法Swizzling后未触发的异常原因排查
Swift中InputStream Swizzling失效问题的排查与解决
我来帮你分析一下这个问题——之前在Objective-C里用NSInputStream做方法交换很顺利,但到Swift里就掉坑里了,这种跨语言的Runtime细节确实容易踩坑。咱们一步步来拆解:
问题根源分析
你当前的代码之所以失效,核心原因是找错了要交换的方法:
- Swift的
InputStream.init(url:)其实是OCNSInputStream initWithURL:的一层包装。当你创建InputStream实例时,系统实际调用的是底层OC的initWithURL:方法,而不是你Swizzle的Swift初始化器。 - 另外,Swift初始化器的方法签名和OC的实例方法在Runtime层面的映射有差异,直接对Swift的
init(url:)做交换,根本碰不到实际的初始化路径,自然不会触发你的自定义逻辑。
还有个小细节:你说交换后直接调用被Swizzle的方法也没反应,其实是因为你交换的是一个“没被系统使用的Swift方法”,交换本身成功了,但这个方法从来不会被调用,所以你看不到效果。
修正后的实现代码
咱们直接针对OC底层的initWithURL:方法做Swizzling,同时确保Swizzle在类被使用前执行:
第一步:调整Swizzling逻辑
private let swizzling: (AnyClass, Selector, Selector) -> () = { forClass, originalSelector, swizzledSelector in guard let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) else { print("Failed to get methods for swizzling") return } method_exchangeImplementations(originalMethod, swizzledMethod) } extension InputStream { // 匹配OC initWithURL:的方法签名 @objc dynamic func swizzledInitWithURL(_ url: URL) -> InputStream? { print("Swizzled constructor triggered for URL: \(url)") // 交换后,这里调用swizzledInitWithURL实际是调用原来的initWithURL:实现 return self.swizzledInitWithURL(url) } static func performSwizzling() { // 用OC的方法选择器,对应initWithURL: let originalSelector = #selector(NSInputStream.init(url:)) let swizzledSelector = #selector(swizzledInitWithURL(_:)) swizzling(InputStream.self, originalSelector, swizzledSelector) } // 用静态属性保证Swizzle只执行一次 static let swizzleOnce: Void = { performSwizzling() }() }
第二步:确保Swizzle时机正确
在App启动初期触发Swizzle,比如在AppDelegate里:
@main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 触发一次Swizzle _ = InputStream.swizzleOnce return true } }
关键注意点
- 方法签名必须严格匹配:自定义的
swizzledInitWithURL(_:)方法的参数、返回值要和OC的initWithURL:完全对应,这样Runtime才能正确交换实现。 - 标记方法为
@objc dynamic:这两个关键字缺一不可——@objc让方法暴露给OC Runtime,dynamic禁止Swift的方法静态派发,强制走Runtime的动态方法查找。 - Swizzle必须提前执行:一定要在任何
InputStream实例被创建前完成Swizzle,否则系统已经缓存了方法实现,交换就不会生效。用静态属性swizzleOnce可以保证只执行一次,避免重复交换。
现在你再测试创建InputStream(url: yourFileURL),应该就能看到控制台打印你的自定义日志了,完美捕获初始化过程~
内容的提问来源于stack exchange,提问作者KeksimvsMaximvs
相关产品推荐
相关产品推荐

