I'm trying to capture initialisation of InputStream with file URL in Swift. I've successfully implemented such capture in Objective-C for NSInputStream class. The issue is that after exchanging initialisers, swizzled method is not triggered. Furthermore, after method exchange, calling swizzled method directly does not lead to its execution, meaning, its implementation was successfully exchanged. I'd like to know, what are the reasons of such odd behaviour, as method is swizzled successfully, but it's not clear, which method was replaced by swizzled one. Provided sample with current implementation of InputStream swizzling in Swift.
private let swizzling: (AnyClass, Selector, Selector) -> () = { forClass, originalSelector, swizzledSelector in
if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
extension InputStream {
@objc dynamic func swizzledInit(url: URL) -> InputStream? {
print("Swizzled constructor")
return self.swizzledInit(url: url)
}
static func Swizzle() {
swizzling(InputStream.self, #selector(InputStream.init(url:)), #selector(swizzledInit(url:)))
}
}
From the
InputStreamdocumentation:The key here: when you create an
InputStream, the object you get back will not be of typeInputStream, but a (private) subclass ofInputStream. Much like the Foundation collection types (NSArray/NSMutableArray,NSDictionary/NSMutableDictionary, etc.), the parent type you interface with is not the effective type you're working on: when you+allocone of these types, the returned object is usually of a private subclass.In most cases, this is irrelevant implementation detail, but in your case, because you're trying to swizzle an initializer, you do actually care about the value returned from
+alloc, since you're swizzling an initializer which is never getting called.In the specific case of
InputStream, the value returned from+[NSInputStream alloc]is of the privateNSCFInputStreamclass, which is the effective toll-free bridged type shared with CoreFoundation. This is private implementation detail that may change at any time, but you can swizzle the initializer on that class instead:Note that if you're submitting an app for App Store Review, it is possible that the inclusion of a private class name may affect the review process.