How to Swizzle Init in Swift

How to swizzle init in Swift

When creating the selector for a method, you should base it off the Obj C method signature since swizzling is done using the Obj C runtime.

So the original selector should be

initWithIdentifier:source:destination:

Now this gets a little weird, since your init method is defined so that the label on the first argument is required (by having identifier twice), the selector you want to use is actually

ftg_initWithIdentifier:source:destination:

The only documentation I could find about it talk about the translation from Obj C to Swift but it looks like the reverse is happening from Swift to Obj C.

Next, init... is an instance method so you'll need to make two changes. You need to change class_getClassMethod to class_getInstanceMethod and you need to remove class from your ft_init... method.

So when all is said and done, your code should look like this (which worked for me)

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())
}

Swift swizzling init function

From the InputStream documentation:

NSInputStream is an abstract superclass of a class cluster consisting of concrete subclasses of NSStream that provide standard read-only access to stream data.

The key here: when you create an InputStream, the object you get back will not be of type InputStream, but a (private) subclass of InputStream. 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 +alloc one 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 private NSCFInputStream class, 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:

guard let class = NSClassFromString("NSCFInputStream") else {
// Handle the fact that the class is absent / has changed.
return
}

swizzling(class, #selector(InputStream.init(url:)), #selector(swizzledInit(url:)))

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.

Swizzle `init` and invoke actual implementation inside, Swift 4

Putting aside that this is a very dangerous and ill-advised swizzle if used for anything but exploration and debugging, the point of method_exchangeImplementations is that it exchanges implementations. This means that the old implementation becomes the swizzled method. So to call the old implementation you call yourself:

@objc func swizzledFrameInit(frame: NSRect) {
self.swizzledFrameInit(frame: frame)
self.wantsLayer = true
}

Generally the correct way to implement what you're doing here is to set wantsLayer on the top-level view. This property applies to all subviews.

Creating a layer-backed view implicitly causes the entire view hierarchy under that view to become layer-backed. Thus, the view and all of its subviews (including subviews of subviews) become layer-backed

How to swizzle init method of NSURLConnection class

extension NSURLConnection{
public override class func initialize() {
struct Static {
static var token: dispatch_once_t = 0
}

if self !== NSURLConnection.self {
return
}

dispatch_once(&Static.token) {
let originalSelector = Selector("initWithRequest:delegate:startImmediately:")
let swizzledSelector = Selector("initWithTest:delegate:startImmediately:")

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)
}
}
}

// MARK: - Method Swizzling
convenience init(test: NSURLRequest, delegate: AnyObject?, startImmediately: Bool){
print("Inside Swizzled Method")
self.init()
}
}

How to swizzle initialization method?

(Starting with the required caveat: this is incredibly dangerous and should never be used in production code. Swizzling initializers is particularly dangerous given designated initializer chaining, and should definitely never be done for anything but exploration and debugging without first confirming the implementation of the swizzled initializer. OK, got that out of the way.)

I can't reproduce your issue. And initializer should always start with with init, so your second approach is correct. I suspect you've just made a small mistake, perhaps in your @selector (which has a typo in your question, which suggests maybe there's a mistake in your actual code). Here is code that does what you're describing.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface MyOldController: NSObject
- (instancetype)initWithInt:(NSInteger)x
@end

@implementation MyOldController
- (instancetype)initWithInt:(NSInteger)x
{
self = [super init];
if (self) {
NSLog(@"init");
}
return self;
}
@end

@implementation MyOldController(Swizzle)

+ (void)load {
[MyOldController swizzleMethods];
}

+ (void)swizzleMethods {
method_exchangeImplementations(class_getInstanceMethod(self, @selector(initWithInt:)), class_getInstanceMethod(self, @selector(initWithInt_swizzle:)));
}

- (instancetype)initWithInt_swizzle:(NSInteger)x
{
self = [super init];
if (self) {
NSLog(@"init_swizzle");
}
return self;
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
MyOldController *controller = [[MyOldController alloc] initWithInt:1];
NSLog(@"%@", controller);
}
return 0;
}

This prints, as expected:

2018-06-21 12:23:14.431936-0400 test[30981:401466] init_swizzle
2018-06-21 12:23:14.432172-0400 test[30981:401466] <MyOldController: 0x10051ee10>

Swizzling UIImage init not working iOS Swift

I too was having trouble swizzling these init methods for UIImage. The only way I found was to instead use class methods, which seem to work fine (I first tried with static methods, but that didn't work, so then I tried class methods, which did work):

func swizzle(originalClass: AnyClass,
originalSelector: Selector,
isOriginalSelectorClassMethod: Bool,
swizzledClass: AnyClass,
swizzledSelector: Selector,
isSwizzledSelectorClassMethod: Bool) {
guard let originalMethod = isOriginalSelectorClassMethod ?
class_getClassMethod(originalClass, originalSelector) :
class_getInstanceMethod(originalClass, originalSelector) else {
return
}

guard let swizzledMethod = isSwizzledSelectorClassMethod ?
class_getClassMethod(swizzledClass, swizzledSelector) :
class_getInstanceMethod(swizzledClass, swizzledSelector) else {
return
}

let didAddMethod = class_addMethod(isOriginalSelectorClassMethod ? object_getClass(originalClass)! : originalClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod))

if didAddMethod {
class_replaceMethod(isSwizzledSelectorClassMethod ? object_getClass(swizzledClass)! : swizzledClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}

extension UIImage {
static func swizzleInitializersIfNeeded() {
guard !areInitializersSwizzled else {
return
}

areInitializersSwizzled = true

swizzle(originalClass: self,
originalSelector: #selector(UIImage.init(named:)),
isOriginalSelectorClassMethod: true,
swizzledClass: self,
swizzledSelector: #selector(UIImage.image(named:)),
isSwizzledSelectorClassMethod: true)

swizzle(originalClass: self,
originalSelector: #selector(UIImage.init(named:in:with:)),
isOriginalSelectorClassMethod: true,
swizzledClass: self,
swizzledSelector: #selector(UIImage.image(named:in:with:)),
isSwizzledSelectorClassMethod: true)
}

private static var areInitializersSwizzled = false

@objc fileprivate class func image(named name: String) -> UIImage? {
let image = self.image(named: name)
image?.name = name

return image
}

@objc fileprivate class func image(named name: String,
in bundle: Bundle,
with config: UIImage.Configuration) -> UIImage? {
let image = self.image(named: name, in: bundle, with: config)
image?.name = name
image?.bundle = bundle

return image
}

private static var nameKey = 0
private static var bundleKey = 0

private(set) var name: String? {
get { objc_getAssociatedObject(self, &UIImage.nameKey) as? String }
set { objc_setAssociatedObject(self, &UIImage.nameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}

private(set) var bundle: Bundle? {
get { objc_getAssociatedObject(self, &UIImage.bundleKey) as? Bundle }
set { objc_setAssociatedObject(self, &UIImage.bundleKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}

class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

UIImage.swizzleInitializersIfNeeded()

let image = UIImage(named: "test_image")

print(image?.name as Any) // prints Optional("test_image")
}
}

Method Swizzling with Swift 5.5

That's because

@objc internal func log_initWithString(string URLString: String)

is exposed to Objective-C as log_initWithStringWithString: and not as log_initWithString:.

Obvious fix is:

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

To have better compile time checks on that you can use this syntax:

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

This will compile, but there is at least one thing left to fix - swizzled method return value. In your example:

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

return log_initWithString(string: URLString)
}

returns nothing, while NSURL's init is supposed to return NSURL, so the fix is:

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

How to swizzle NSError init without hitting an infinite loop

You have exchanged the implementations, that means you have to call:

@objc convenience init(swizzleDomain: String, code: Int, info: [String : Any]?) {
self.init(swizzleDomain: swizzleDomain, code: code, info: info)
}

Because self.init(swizzleDomain:...) will contain the original initializer.

How can I swizzle UIView.init

I've never tried swizzling before but fancied trying this out - a quick google found this article that explains how to do it with an extension.

extension UIView {

static let classInit: Void = {
guard let originalMethod = class_getInstanceMethod(
UIView.self,
#selector(layoutSubviews)
), let swizzledMethod = class_getInstanceMethod(
UIView.self,
#selector(swizzled_layoutSubviews)
) else {
return
}
method_exchangeImplementations(originalMethod, swizzledMethod)
}()

@objc func swizzled_layoutSubviews() {
swizzled_layoutSubviews()
self.layer.borderColor = UIColor.red.cgColor
self.layer.borderWidth = 2
}

}

Then you just need to make sure you run the swizzle in your AppDelegate didFinishLaunchingWithOptions method or somewhere at the start of your app

UIView.classInit

Sample Image

The above code works for me on an iOS 13.7 simulator, tho from what I have read you really should not be trying to override methods like this especially from such a core object, it'd be safer to subclass UIView if possible.



Related Topics



Leave a reply



Submit