How to swizzle Swift.print(items:separator:terminator)

1.1k Views Asked by At

I am looking for ways to swizzle Swift.print function. Overriding it is not an option, as it may get bypasses if you use Swift.print(:)

The selector does not recognise the identifier:

@objc class func printSwizzle() {
    guard let instance = class_getInstanceMethod(self, #selector(print(separator:terminator:))),
    let swizzleInstance = class_getInstanceMethod(self, #selector(swizzlePrint(separator:terminator:))) else { return }
    method_exchangeImplementations(instance, swizzleInstance)
}

Is that even possible? As swizzling is an obj-c runtime feature.

2

There are 2 best solutions below

3
Sulthan On BEST ANSWER

Method swizzling is an Objective-C feature that enables you to exchange implementation of a method at runtime. For that, you need an @objc object that inherits from NSObject. And you need a method.

Swift.print is not a method. It's a function declared in the Swift module. We can say it's global but it's not really global. It's defined inside module Swift which is imported automatically to every Swift code, therefore you can use it without the Swift. prefix.

In summary, there is no way to swizzle Swift.print.

What you can do is to hide that function using your own implementation, that is, if you declare a function with the same name in your own module, then when print is used, the compiler will prefer your function instead because functions in the current module are preferred over functions in other modules (including Swift. module).

public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
    let output = items.map { "\($0)" }.joined(separator: separator)
    Swift.print(output, terminator: terminator)
}

You can any logic you want in there.

It's actually very common to use this to remove logging from production, e.g.:

#if !DEBUG

func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {}
func debugPrint(_ items: Any..., separator: String = " ", terminator: String = "\n") {}

#endif

See Remove println() for release version iOS Swift for more details.

Essentially, you could hide the whole Swift module by redeclaring it inside your module, e.g. as an enum, therefore disabling calls to Swift.print:

enum Swift {
    public static func print(_ items: Any..., separator: String = " ", terminator: String = " ") {
        // do something
    }
}

However, I would generally advise against this because it will be hard to solve any naming conflicts with the standard library inside the Swift. module.

In general, I would advise to implement your custom logging system and enforce its usage by other means, e.g. code reviews or linting rules (e.g. swiftlint).

2
Md. Ibrahim Hassan On

Adding to the comments by Alexander and Carpsen90

Method Swizzling is an Objective-C runtime feature and it is inherently not available in Swift as Swift is not a dynamic language. However, you can have a global function as in this SO Post. Below is the updated code for Swift 4.2. But unfortunately, it calls the original Swift.print function if the function name is print. So I changed the function name to logs

public func logs(items: Any..., separator: String = " ", terminator: String = "\n") {
    let output = items.map { "*\($0)"}.joined(separator: " ")
    Swift.print(output, terminator: terminator)
}

Another Possible option is to put it into a protocol

public protocol CustomPrintable {
    func print(_ items: Any...)
}

extension CustomPrintable {
    func print(_ items: Any...) {
        let output = items.map { "*\($0)"}.joined(separator: " ")
        Swift.print("****" + output)
    }
}

Usage

class SampleClass : CustomPrintable {

  func printValue() {
      print ("Hello World")
  }
}

But this way the function ceases to be a global function and while using print you have to select the correct methodenter image description here

Output

*****Hello World