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 ofNSStream
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
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
Drawing a Polygon with One Color for Stroke, and a Different One for Fill
How to Code the Launchscreen Programmatically
How to Add Firebase to Today Extension iOS
Uisplitviewcontroller on iPad with Storyboards
Facebook Sdk - iOS - Fail to Share Url (Error 102)
Googleutilities/Appdelegateswizzler/Private/Gulapplication.H' File > Not Found
Convert Spelled Out Number to Number
Rails: Redirect_To 'Myapp://' to Call iOS App from Mobile Safari
iOS Calculate Text Height in Tableview Cell
Avassetresourceloaderdelegate Methods Not Working on Device
Segues Initiated Directly from View Controllers Warning in Storyboard Xcode
How to Create a Uiimage with Uibezierpath
Will iOS Awake My App When I Receive Silent Push Notification(When App Is Not in Running State)
How to Hide "Back to Safari" from Status Bar in iOS9
How We Can Set the Light Content Style of Status Bar in iOS 9 for Whole Application
Implementing Autocomplete in iOS
Determine Percentage of Visibility of Horizontal Collectionview Cells on Screen