How do I swizzle main bundle with test bundle - ios

I use to swizzle main bundle with test bundle like follow in obj c
#import "NSBundle+Bundle.h"
#import <objc/runtime.h>
#implementation NSBundle (Bundle)
+(void)loadSwizzler {
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
Method originalMethod = class_getClassMethod(self, #selector(mainBundle));
Method extendedMethod = class_getClassMethod(self, #selector(bundleForTestTarget));
//swizzling mainBundle method with our own custom method
method_exchangeImplementations(originalMethod, extendedMethod);
});
}
//method for returning app Test target
+(NSBundle *)bundleForTestTarget {
NSBundle * bundle = [NSBundle bundleWithIdentifier:#"Philips.AppInfraTests"];
return bundle;
}
#end
But I tried the following for the same in swift
extension Bundle {
class func swizzle() {
let originalSelector = #selector(mainBundle)
let swizzledSelector = #selector(testBundle)
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
func mainBundle() -> Bundle
{
return Bundle.main
}
func testBundle() -> Bundle
{
return Bundle(for: self.classNamed("swizzler")!)
}
}
But this is throwing some errors "Argument of '#selector' cannot refer to variable 'testBundle'"
could some one help me how do I do it

This answer has been tested in Swift 3 & 4 Playground, any other version and YMMV.
Your Objective-C code swizzles two class methods, your Swift version attempts to swizzle two instance methods - so they are not doing the same thing.
You (probably) cannot swizzle a (pure) Swift function, you can swizzle Objective-C methods, this is due to the differences in how functions/methods are dispatched. So in Swift the replacement function must be marked #objc in Swift 4 (it is optional and apparently harmless in Swift 3).
Swift renames mainBundle to main and surfaces it as a property, so to get the selector for mainBundle you need to use getter: main.
Combine the above and you get the following Playground code:
extension Bundle
{
class func swizzle()
{
let originalSelector = #selector(getter: main)
let swizzledSelector = #selector(testBundle)
let originalMethod = class_getClassMethod(self, originalSelector)!
let swizzledMethod = class_getClassMethod(self, swizzledSelector)!
method_exchangeImplementations(originalMethod, swizzledMethod)
}
#objc class func testBundle() -> Bundle
{
// just for testing in Playground
return Bundle(path: "/Applications/TextEdit.app")!
}
}
let b = Bundle.main
print(b)
Bundle.swizzle()
let c = Bundle.main
print(c)
which prints:
NSBundle </Applications/Xcode.app> (not yet loaded)
NSBundle </Applications/TextEdit.app> (not yet loaded)
Note that class_getClassMethod() returns a Method? and the above code forces this without any checks, those checks should exist in real code!
Finally note that your swizzle code assumes mainBundle is implemented directly by NSBundle and not one of its ancestors, that is probably a safe assumption in this case but is not always. See for example this question on doing swizzling safely.
HTH

Related

Change modalPresentationStyle on iOS13 on all UIViewController instances at once using method swizzling

[Q&A] Is it possible to change UIViewController.modalPresentationStyle value globally on iOS 13 so it behaves like it used to on iOS 12 (or earlier)?
Why?
In iOS 13 SDK the default value of UIViewController.modalPresentationStyle property has been changed from UIModalPresentationFullScreen to UIModalPresentationAutomatic which is, as far as I know, resolved to UIModalPresentationPageSheet on iOS devices or at least on iPhones.
Since the project I've been working for several years has become quite big, there are tens of places where a view controller is presented. The new presentation style doesn't always match our app designs and sometimes it causes the UI to fall apart. Which is why, we deciced to change UIViewController.modalPresentationStyle back to UIModalPresentationFullScreen as it was is pre-iOS13 SDK's versions.
But adding viewController.modalPresentationStyle = UIModalPresentationFullScreen before calling presentViewController:animated:completion: in every single place where a controller is presented seemed like an overkill. Moreover, we had more serious matters to deal with at that time, which is why, for the time being or at least until we update our designs and fix all the UI issues, we decided to go with method swizzling approach.
The working solution is presented in my answer, but I would appreciate any feedback telling me what downsides or consequences of such an approach might be.
Here's how we achieved that by using method swizzling:
Objective-C
UIViewController+iOS13Fixes.h
#import <Foundation/Foundation.h>
#interface UIViewController (iOS13Fixes)
#end
UIViewController+ iOS13Fixes.m
#import <objc/runtime.h>
#implementation UIViewController (iOS13Fixes)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(presentViewController:animated:completion:);
SEL swizzledSelector = #selector(swizzled_presentViewController:animated:completion:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL methodExists = !class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (methodExists) {
method_exchangeImplementations(originalMethod, swizzledMethod);
} else {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
});
}
- (void)swizzled_presentViewController:(nonnull UIViewController *)viewController animated:(BOOL)animated completion:(void (^)())completion {
if (#available(iOS 13.0, *)) {
if (viewController.modalPresentationStyle == UIModalPresentationAutomatic || viewController.modalPresentationStyle == UIModalPresentationPageSheet) {
viewController.modalPresentationStyle = UIModalPresentationFullScreen;
}
}
[self swizzled_presentViewController:viewController animated:animated completion:completion];
}
#end
Swift
UIViewController+iOS13Fixes.swift
import UIKit
#objc public extension UIViewController {
private func swizzled_present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?) {
if #available(iOS 13.0, *) {
if viewControllerToPresent.modalPresentationStyle == .automatic || viewControllerToPresent.modalPresentationStyle == .pageSheet {
viewControllerToPresent.modalPresentationStyle = .fullScreen
}
}
self.swizzled_present(viewControllerToPresent, animated: animated, completion: completion)
}
#nonobjc private static let _swizzlePresentationStyle: Void = {
let instance: UIViewController = UIViewController()
let aClass: AnyClass! = object_getClass(instance)
let originalSelector = #selector(UIViewController.present(_:animated:completion:))
let swizzledSelector = #selector(UIViewController.swizzled_present(_:animated:completion:))
let originalMethod = class_getInstanceMethod(aClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector)
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
if !class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) {
method_exchangeImplementations(originalMethod, swizzledMethod)
} else {
class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
}
}()
#objc static func swizzlePresentationStyle() {
_ = self._swizzlePresentationStyle
}
}
and in AppDelegate, in application:didFinishLaunchingWithOptions: invoke the swizzling by calling (swift version only):
UIViewController.swizzlePresentationStyle()
Make sure it's called only once (use dispatch_once or some equivalent).
More on method swizzling here:
https://nshipster.com/method-swizzling/
https://nshipster.com/swift-objc-runtime/
https://medium.com/rocknnull/ios-to-swizzle-or-not-to-swizzle-f8b0ed4a1ce6

Swift - method swizzling

This is the method swizzling code written in Objective-C. I am having a hard time converting this in Swift.
void MPApplicationDidRegisterForRemoteNotificationsWithDeviceToken(id self, SEL _cmd, UIApplication *application, NSData *deviceToken) {
[[MPPush shared] appRegisteredForRemoteNotificationsWithDeviceToken:deviceToken];
IMP original = [MPAppDelegateProxy originalImplementation:_cmd class:[self class]];
if (original)
((void(*)(id, SEL, UIApplication *, NSData*))original)(self, _cmd, application, deviceToken);
}
Swiftify isn't converting the above code correctly.
I tried to do this but I am not sure how to pass parameters and use the exact above parameters in Swift swizzling method. This is my fail attempt to convert the above in Swift (code doesn't even compile) :
var MPApplicationDidRegisterForRemoteNotificationsWithDeviceToken: Void {
// TODO: MPPush.shared.app
let original = MPAppDelegateProxy.proxyAppDelegate.originalImplementation(selector: cmd, forClass: type(of: self))
}(self: Any, _cmd: Selector, application: UIApplication, deviceToken: Data)
Extend your class:
extension YourClassName {
static let classInit: () -> () = {
let originalSelector = #selector(originalFunction)
let swizzledSelector = #selector(swizzledFunction)
swizzle(YourClassName.self, originalSelector, swizzledSelector)
}
#objc func swizzledFunction() {
//Your new implementation
}
}
Your class (YourClassName) should inherit from NSObject and originalSelector should be a dynamic method.
swizzle is a closure that exchanges the implementations:
private let swizzle: (AnyClass, Selector, Selector) -> () = { fromClass, originalSelector, swizzledSelector in
guard
let originalMethod = class_getInstanceMethod(fromClass, originalSelector),
let swizzledMethod = class_getInstanceMethod(fromClass, swizzledSelector)
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
You could define swizzle in the class where you are going to do the swizzling. For example the AppDelegate class definition. And then do the swizzling in AppDelegate.init():
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
super.init()
YourClassName.classInit()
}
}
Example
Here is a concrete example of swizzling, it exchanges the implementation of two methods that take one argument:
class Example: NSObject {
#objc dynamic func sayHi(to name: String) {
print("Hi", name)
}
}
extension Example {
public class func swizzleMethod() {
guard
let originalMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHi(to:))),
let swizzledMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHello(to:)))
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
#objc func sayHello(to name: String) {
print("Hello", name)
}
}
Example.swizzleMethod()
let a = Example()
a.sayHi(to: "Nitish") //Hello Nitish
Swift Native swizzling
As of Swift 5.1, there is a native version of method swizzling that does not rely on Objective-C’s message passing. The #_dynamicReplacement modifier can be used upon a replacement function, and takes as an argument the name of the function that it should replace. The function that is being replaced must be marked with the #dynamic modifier, unless -enable-implicit-dynamic compilation flag is used, which makes the compiler assume that every eligible entity has been marked with the modifier.
For example:
dynamic func original() {
print("I am the original")
}
#_dynamicReplacement(for: original)
func replacement() {
print("I am the replacement")
}
original() // prints "I am the replacement"
For more details on dynamic method replacement, visit this Swift forum page.

Swizzle Tap Gesture Action in iOS Cocoa Touch Framework

I am developing an Cocoa Touch Framework or a dynamic Library which would capture user actions using Swift.
E.g. When a user taps on a label, my framework should know about it.
For achieving this behaviour, I am using method swizzling.
I am creating an extension for the UITapGestureRecognizer.In this extension, in initialize method , I am swizzling the init :
open override class func initialize() {
guard self === UITapGestureRecognizer.self else { return }
let originalSelector = #selector(self.init(target:action:))
let swizzledSelector = #selector(swizzled_init(target:action:))
swizzling(self, originalSelector, swizzledSelector)
}
func swizzled_init(target: Any, action: Selector) {
swizzled_init(target : target,action: action)
print("swizzled_init"+action.description)
//This part is for swizzling the the action which is being provided by the developer at the time of init.
guard self === UITapGestureRecognizer.self else { return }
let originalSelector = action
let swizzledSelector = #selector(swizzled_action(sender:))
swizzling(self.classForCoder, originalSelector, swizzledSelector)
}
func swizzled_action(sender : UITapGestureRecognizer? = nil){
print("swizzled_action"+(sender?.description)!)
}
After swizzling init, the instance of tap is nil in my ViewController.
let tap = UITapGestureRecognizer.init(target: self, action: #selector(buttonTapped(sender:)))
And the second issue is this in the swizzled_init method:
guard self === UITapGestureRecognizer.self else { return }
which is getting failed.
Could you please direct me in resolving these issues?

Swift override function in extension

If I have a class:
class Spaceship<FuelType> {
function prepareForLiftoff() throws {
//Start the countdown!
}
}
I originally assumed that I would be able to override prepareForLiftoff without subclassing by adding an extension:
extension Spaceship where FuelType: CollectionType {
func prepareForLiftoff() throws {}
}
This code doesn't compile though, the error says invalid redeclaration of the function, which makes sense.
My question is: Is there anyway to override a function of a particular class? In other words can I replace the functionality under certain conditions like the example above where FuelType: CollectionType. If not, is there any other workaround or way to achieve that behavior (maybe declaring another protocol, idk)
Now that I think about it more, I would have to say that's not possible because what's to stop someone from overriding any of the standard library functions?
From the documentation:
Extensions can add new functionality to a type, but they cannot override existing functionality.
The documentation lists carefully and precisely what an extension is allowed to do.
As to your question:
Is there anyway to override a function of a particular class
Yes, it's called subclassing.
Instead of overriding, you may like to try swizzling. For example the following code allows you to run your own viewWillAppear. Swift 3:
extension UIViewController {
open override class func initialize() {
// make sure this isn't a subclass
guard self === UIViewController.self else { return }
DispatchQueue.once(token: "viewWillAppear") {
let originalSelector = #selector(self.viewWillAppear(_:))
let swizzledSelector = #selector(self.proj_viewWillAppear1(animated:))
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
func proj_viewWillAppear1(animated: Bool) {
self.proj_viewWillAppear1(animated: animated)
let viewControllerName = NSStringFromClass(type(of: self))
print("viewWillAppear: \(viewControllerName)")
}
}
Update 20170621
public extension DispatchQueue {
private static var _onceTracker = [String]()
public class func once(file: String = #file, function: String = #function, line: Int = #line, block:(Void)->Void) {
let token = file + ":" + function + ":" + String(line)
once(token: token, block: block)
}
public class func once(token: String, block:(Void)->Void) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}

Method Swizzling does not work

I would like to make use of method swizzling, but am unable to get even simple examples to work for me. It is possible that I am misunderstanding what the concept is, but as far as I know it allows for method implementations to be swapped.
Given two methods, A and B, I would like to swap their implementations such that calling A would execute B instead. I came across a few examples of swizzling (example1 and example2). I created a new project with a class to test this.
class Swizzle: NSObject
{
func method()
{
print("A");
}
}
extension Swizzle
{
override class func initialize()
{
struct Static
{
static var token: dispatch_once_t = 0;
}
// make sure this isn't a subclass
if (self !== Swizzle.self)
{
return;
}
dispatch_once(&Static.token)
{
let originalSelector = Selector("method");
let swizzledSelector = Selector("methodExt");
let originalMethod = class_getInstanceMethod(self, originalSelector);
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
print(method_getImplementation(originalMethod));
print(method_getImplementation(swizzledMethod));
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);
}
print(method_getImplementation(originalMethod));
print(method_getImplementation(swizzledMethod));
}
}
func methodExt()
{
print("B");
}
}
I then try to execute it with
var s = Swizzle();
s.method();
The expected output is "B", but "A" is still being printed. As you can see from my code, I've included prints of each IMP before and after the swizzle operation. These prints show that the exchange does take place, yet the output remains the same.
Output:
0x000000010251a920
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A
Is there anything I am missing when it comes to getting these changes to take effect?
PS. Currently using XCode 7.0.1
The issue is that your method() lacks the dynamic directive:
class Swizzle: NSObject
{
dynamic func method()
{
print("A")
}
}
Modify the declaration and it should work.
When using method swizzling in Swift there are two requirements that your classes/methods must comply with:
Your class must extend NSObject
The functions you want to swizzle must have the dynamic attribute
For a complete explanation of why this is required, check out Using Swift with Cocoa and Objective-C:
Requiring Dynamic Dispatch
While the #objc attribute exposes your Swift API to the Objective-C
runtime, it does not guarantee dynamic dispatch of a property, method,
subscript, or initializer. The Swift compiler may still devirtualize
or inline member access to optimize the performance of your code,
bypassing the Objective-C runtime. When you mark a member declaration
with the dynamic modifier, access to that member is always dynamically
dispatched. Because declarations marked with the dynamic modifier are
dispatched using the Objective-C runtime, they’re implicitly marked
with the #objc attribute.
Requiring dynamic dispatch is rarely necessary. However, you must use
the dynamic modifier when you know that the implementation of an API
is replaced at runtime. For example, you can use the
method_exchangeImplementations function in the Objective-C runtime to
swap out the implementation of a method while an app is running. If
the Swift compiler inlined the implementation of the method or
devirtualized access to it, the new implementation would not be used.
Swift 3 Update:
There have been a few changes in regard to GCD and dispatch_once is not available anymore. To perform the same one time operation, we can enclose the code in the initialization block of a global static class constant.
The Swift language guarantees that this code will be executed only once during the lifetime of the application.
class TestSwizzling : NSObject {
dynamic func methodOne()->Int{
return 1
}
}
extension TestSwizzling {
//In Objective-C you'd perform the swizzling in load(),
//but this method is not permitted in Swift
override class func initialize()
{
struct Inner {
static let i: () = {
let originalSelector = #selector(TestSwizzling.methodOne)
let swizzledSelector = #selector(TestSwizzling.methodTwo)
let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
let _ = Inner.i
}
func methodTwo()->Int{
// It will not be a recursive call anymore after the swizzling
return methodTwo()+1
}
}
var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())
Swift 2.2 Update:
I've updated the original example for the new #selector attribute:
class TestSwizzling : NSObject {
dynamic func methodOne()->Int{
return 1
}
}
extension TestSwizzling {
//In Objective-C you'd perform the swizzling in load(),
//but this method is not permitted in Swift
override class func initialize()
{
struct Static
{
static var token: dispatch_once_t = 0
}
// Perform this one time only
dispatch_once(&Static.token)
{
let originalSelector = #selector(TestSwizzling.methodOne)
let swizzledSelector = #selector(TestSwizzling.methodTwo)
let originalMethod = class_getInstanceMethod(self, originalSelector);
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
func methodTwo()->Int{
// It will not be a recursive call anymore after the swizzling
return methodTwo()+1
}
}
var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())
If you need an example to play with, check out this sample project on github.

Resources