2

Is there a way to have the app crash and burn at runtime on print()? Override print() in Swift runtime with an implementation that'd preconditionFailure("STOP using print()").

Basically it's a part of the team Pavlov's dog training in progress: I want people to use debugPrint rather than print to pollute console in debug builds only.

UPD20180525: matt is right: print output does not go to a live console of a real device, it somehow only ends up on lldb console.

NSLog output thought DOES go to the device's console so what needs to be killed at runtime or compile time for non debug builds is NSLog

This is what was actually needed:

#if DEBUG
#else
public func NSLog(_ format: String, _ args: CVarArg...)
{

}
#endif

(cause there is no real need to get rid of print which is harmless in release builds)

Anton Tropashko
  • 5,486
  • 5
  • 41
  • 66
  • 1
    Why don't you define a linting (e.g. swiftlint) rule for that? You can easily setup your project to throw compilation warnings/errors on `print`. – Sulthan May 24 '18 at 18:24
  • 3
    And don't replace `print` with `debugPrint`. Create a logger, and use that (that way you can control where the logs go and put them in reasonable formats). Don't try to break Swift. (There is no wise way to implement what you're describing.) With a logger in place, you can even just grep for "print\(" in a git commit hook if it's critical to you. But the right way to address all of this is code review, not tricks to crash the program. When you say "I want people to…" do you mean "my team has agreed we all want…" If not, then you'll need to fix that piece first, and the rest will come naturally – Rob Napier May 24 '18 at 18:25
  • 2
    Also it's hard to see what the harm in `print` would be, since it does nothing when the release-built app runs off the device. – matt May 24 '18 at 18:32
  • right, I have a debug facility that takes debug domain to spam only as needed by the current debug scenario. Just needed to track and kill print() statements that bypassed both that facility and debugprint – Anton Tropashko May 25 '18 at 13:05

4 Answers4

14

While I'm not a fan of this, please do not make this a crashing operation. Break the build, not the runtime.

@available(*, unavailable, message: "Our team has agreed not to use print.")
func print(_ items: Any..., separator: String = "", terminator: String = "\n") {}

This will turn references to print into a compile-time error:

error: 'print(_:separator:terminator:)' is unavailable: Our team has agreed not to use print.
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • your compile compile-time way of solving this is what i ended up using, but given that I have not dreamed of a compile time option and asked for a runtime solution which matt gave I had to grudgingly end up giving accepted answer to matt. The rightfully deserved upvote it yours though. Just felt I owe you an explanation. – Anton Tropashko May 25 '18 at 13:10
4

In general, the way people call print is with no namespace — they just say print. So if you declare a global print function with the same signature as the standard library print, it will effectively "override" the standard library print:

func print(_ items: Any..., separator: String = ", ", terminator: String = "\n") {
    preconditionFailure("STOP using print()")
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
3

I would probably do it using swift lint with a simple custom rule. The following should do the trick

custom_rules:
  disable_print:
    included: ".*\\.swift"
    name: "print usage"
    regex: "((\\bprint)|(Swift\\.print))\\s*\\("
    message: "Prefer debugPrint over print"
    severity: error

That is anything that matches word boundary + print + spaces + ( or anything that matches Swift.print.

If you have your own functions/methods called print, this would report them as false positive though.

Adapted for NSLog:

disable_nslog:
  included: ".*\\.swift"
  name: "NSLog verbotten"
  regex: "((\\bNSLog))\\s*\\("
  message: "NSLog prohibited"
  severity: error
Sulthan
  • 128,090
  • 22
  • 218
  • 270
0

SwiftLint could be very helpful for the purposes of "Pavlov's dog training" for team. Yet, if you consider Lint an overkill, you may add a building phase to the project to run additional checks. Select Project -> Build Phases, press + button to add another phase. Choose Run Script on creation. Add as a body of script (make sure Shell is /bin/sh):

RESTRICTEDFUNCS="((\\bprint)|(Swift\\.print))\\s*\\(|((\\bNSLog))\\s*\\("
find "${SRCROOT}" \( -name "*.swift" \) -print0 | \
xargs -0 egrep --with-filename --line-number --only-matching "($RESTRICTEDFUNCS).*\$" | \
perl -p -e "s/($RESTRICTEDFUNCS)/ warning: Use of this func is undesireable: \$1/"

This approach can be easily fine-tuned to your needs and is helpful for teams because it brings the power of regexp, while requiring no dependencies, no installations. Purely in Xcode.

Paul B
  • 3,989
  • 33
  • 46