Remove println() for release version iOS Swift

45,904

Solution 1

As noted, i am a student and need things defined a little more clearly to follow along. After lots of research, the sequence I needed to follow is:

Click on the project name at the top of the File Navigator at the left of the Xcode project window. This is line that has the name of the project, how many build targets there are, and the iOS SDK version.

Choose the Build Settings tab and scroll down to the "Swift Compiler - Custom Flags" section near the bottom. Click the Down Arrow next to Other Flags to expand the section.

Click on the Debug line to select it. Place your mouse cursor over the right side of the line and double-click. A list view will appear. Click the + button at the lower left of the list view to add a value. A text field will become active.

In the text field, enter the text -D DEBUG and press Return to commit the line.

Add a new Swift file to your project. You are going to want to make a custom class for the file, so enter text along the lines of the following:

class Log {

  var intFor : Int

  init() {
    intFor = 42
   }

  func DLog(message: String, function: String = __FUNCTION__) {
    #if DEBUG
      println("\(function): \(message)")
    #endif
  }
}

I was having trouble getting the class to be accepted by Xcode today, so the init may be a bit more heavyweight than necessary.

Now you will need to reference your custom class in any class in which you intend to use the new custom function in place of println() Add this as a property in every applicable class:

   let logFor = Log()

Now you can replace any instances of println() with logFor.DLog(). The output also includes the name of the function in which the line was called.

Note that inside class functions I couldn't call the function unless I made a copy of the function as a class function in that class, and println() is also a bit more flexible with the input, so I couldn't use this in every instance in my code.

Solution 2

The simplest way is to put your own global function in front of Swift's println:

func println(object: Any) {
    Swift.println(object)
}

When it's time to stop logging, just comment out the body of that function:

func println(object: Any) {
    // Swift.println(object)
}

Or you can make it automatic by using a conditional:

func println(object: Any) {
    #if DEBUG
        Swift.println(object)
    #endif
}

EDIT In Swift 2.0 println is changed to print. Unfortunately it now has a variadic first parameter; this is cool, but it means you can't easily override it because Swift has no "splat" operator so you can't pass a variadic in code (it can only be created literally). But you can make a reduced version that works if, as will usually be the case, you are printing just one value:

func print(items: Any..., separator: String = " ", terminator: String = "\n") {
    Swift.print(items[0], separator:separator, terminator: terminator)
}

In Swift 3, you need to suppress the external label of the first parameter:

func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
    Swift.print(items[0], separator:separator, terminator: terminator)
}

Solution 3

Updated for Swift 4.x:

With Swift 2.0/3.0 and Xcode 7/8 now out of beta, there have been some changes to how you disable the print function in release builds.

There are some important points mentioned by @matt and @Nate Birkholz above that are still valid.

  1. The println() function has been replaced by print()

  2. To use the #if DEBUG macro then you have to define the "Swift Compiler - Custom Flags -Other Flags" to contain the value -D DEBUG

  3. I would recommend overriding the Swift.print() function in the global scope so that you can use the print() function as normal in your code, but it will remove output for non-debug builds. Here is a function signature that you can add at the global scope to do this in Swift 2.0/3.0:

    func print(items: Any..., separator: String = " ", terminator: String = "\n") {
    
        #if DEBUG
    
        var idx = items.startIndex
        let endIdx = items.endIndex
    
        repeat {
            Swift.print(items[idx], separator: separator, terminator: idx == (endIdx - 1) ? terminator : separator)
            idx += 1
        }
        while idx < endIdx
    
        #endif
    }
    

Note: We have set the default separator to be a space here, and the default terminator to be a newline. You can configure this differently in your project if you would like.

Hope this helps.

Update:

It is usually preferable to put this function at the global scope, so that it sits in front of Swift's print function. I find that the best way to organize this is to add a utility file to your project (like DebugOptions.Swift) where you can place this function at the global scope.

As of Swift 3 the ++ operator will be deprecated. I have updated the snippet above to reflect this change.

Solution 4

The problem with all these approaches, including mine, is that they do not remove the overhead of evaluating the print arguments. No matter which of them you use, this is going to be expensive:

print(myExpensiveFunction())

The only decent solution is to wrap the actual print call in conditional compilation (let's assume that DEBUG is defined only for debug builds):

#if DEBUG
print(myExpensiveFunction())
#endif

That, and only that, prevents myExpensiveFunction from being called in a release build.

However, you can push back evaluation one level by using autoclosure. Thus, you could rewrite my solution (this is Swift 3) like this:

func print(_ item: @autoclosure () -> Any, separator: String = " ", terminator: String = "\n") {
    #if DEBUG
    Swift.print(item(), separator: separator, terminator: terminator)
    #endif
}

This solves the problem just in the case where you are printing just one thing, which is usually true. That's because item() is not called in release mode. print(myExpensiveFunction()) thus ceases to be expensive, because the call is wrapped in a closure without being evaluated, and in release mode, it won't be evaluated at all.

Solution 5

Swift 5

Simply make a new file in your project and paste this code in:

func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
    #if DEBUG
    items.forEach {
        Swift.print($0, separator: separator, terminator: terminator)        
    }
    #endif
}

This function signature matches the default Swift one so it "overwrites" the function in your project. If needed you can still access the original by using Swift.print().

Once you've added the code above, keep using print() the as usual and it will only print in debug builds.

Note: Doing the forEach to print each item gets rid of annoying array brackets around the print statements that show up if you just pass items straight into Swift.print().

For anyone relatively new to Swift you may wonder what the heck $0 is. It just represents the first argument passed into the forEach block. The forEach statement could also be written like this:

items.forEach { item in
    Swift.print(item, separator: separator, terminator: terminator)        
}

Lastly if you're interested, the Swift declaration of print looks like this:

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

My answer above mirrors the exact Swift implementation - although I never print more than one thing or change separator/terminators. But who knows, you may want to.

Share:
45,904
Nate Birkholz
Author by

Nate Birkholz

iOS developer with an extensive background in project management and product ownership. I thrive when solving problems and making ideas come to life. My deep experience as a designer and team leader on production software products and SAAS leads me to approach development challenges with a focus on results, collaboration, and the customer experience.

Updated on October 03, 2020

Comments

  • Nate Birkholz
    Nate Birkholz over 3 years

    I would like to globally ignore all println() calls in my Swift code if I am not in a Debug build. I can't find any robust step by step instructions for this and would appreciate guidance. is there a way to do this globally, or do I need to surround every println() with #IF DEBUG/#ENDIF statements?

  • Nate Birkholz
    Nate Birkholz over 9 years
    Thanks, where might I best define it? I am a student I am afraid and I greatly appreciate the help but require more explicit context.
  • Ian MacDonald
    Ian MacDonald over 9 years
    You would declare it in a header file that you would import to anywhere you wanted to use it.
  • Nate Birkholz
    Nate Birkholz over 9 years
    Nice solution, thanks. I have been told that in iOS (but not OS X), println() isn't executed in release mode.
  • matt
    matt over 9 years
    @NateBirkholz No, that's nonsense. (Said he, after testing to make sure!)
  • Vojtech Vrbka
    Vojtech Vrbka about 9 years
    There is no need to create a custom class for debug log. It's easier and more practical to use a global function.
  • Stephen J
    Stephen J over 8 years
    I suspect this may require a "where" since printable objects conform to one of those system protocols you see mentioned rarely in vids for wwdc, and I think at the end of the swift guide(s) 1.2 vs 2, forgot the difference if there is one with the system one
  • Rivera
    Rivera over 8 years
    So far it this works with Swift 1.2. Haven't tried 2.0.
  • Charlie
    Charlie over 8 years
    In swift 2, with the function renamed to print, would you just chance the func to match? Also, how would you define this globally? I tried putting it outside my class in AppDelegate and it just isn't ever called even though I have a million print() calls. Here's what I tried: func print(object: Any) { Swift.print(object) }
  • matt
    matt over 8 years
    @Charlie Yes, I'm still using it with println changed to print. The reason it isn't working for you is that your print definition doesn't match Swift's, so you are not overriding it. There is a slight issue because, as has been remarked many times, Swift has no splat operator, so you can't pass the variadic. But it works fine for one item, which you can pass as items[0].
  • DàChún
    DàChún over 8 years
    I am sorry, but where to put the function?
  • Glavid
    Glavid over 8 years
    @User9527 Likely you want to put this somewhere in the global scope, so that it is accessible throughout your project. In my projects, I add a utility swift file (DebugOptions.swift or something similar) and place this function in the global scope (i.e. not in an enclosed class).
  • Pat Niemeyer
    Pat Niemeyer over 8 years
    One caveat here if you are inserting these log statements into high performance sections of code: Swift will still spend time doing string interpolation and rendering parameters to pass to the function, even if they aren't going to be used. The only way that I see to really conditionally remove the statements is to predicate them on a flag. e.g. if ( log ) { print(..) } in each location where they are used.
  • Abdul Yasin
    Abdul Yasin about 8 years
    This should be the acccepted answer. or we could simply use Swift.debugPrint() instead to turn off all the prints in release mode.
  • tadija
    tadija about 8 years
    Nice one! I took it from here and made AELog and AEConsole eventually.
  • user523234
    user523234 about 8 years
    Can you confirm as of current version of Swift-Xcode, the print statement will no longer output to device console without any need to set the -D Debug flat? At least that is what I have tested today.
  • Glavid
    Glavid about 8 years
    @user523234 As of Xcode 7.3, if you want the Debug output to print when *in Debug mode, you still need to set the debug flag per my testing..
  • matt
    matt almost 8 years
    @PatNiemeyer Okay, I've solved that by using @autoclosure. That's far enough from my original answer that I've given it as a separate answer.
  • Jonathan Zhan
    Jonathan Zhan almost 8 years
    As of Swift 3, one can get a little more brevity by adding an underscore at the start of the list of arguments: "print (_ items..."
  • Nitesh
    Nitesh over 7 years
    Is it necessary to remove all "print()" before releasing app on AppStore ?
  • Jayprakash Dubey
    Jayprakash Dubey over 7 years
    This is working fine in DEBUG mode. Now, I've changed to RELEASE mode from Edit Scheme. It shows the log in console window for Release mode too. Why so?
  • kelin
    kelin about 7 years
    What the use of @autoclosure?
  • Jonny
    Jonny about 7 years
    I'm new to Swift (3), and I put the print above into its own swift file, but it does nothing.
  • Jonny
    Jonny about 7 years
    So I looked up the reference of the print (used in didFinishLaunching...) and it pointed me to the original print function Swift. Putting that and @JonathanZhan's comment together, I adjusted the function to look like this and voila it works: public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
  • iCyberPaul
    iCyberPaul almost 7 years
    For Swift 3.2 in Xcode 9 I needed to change NSLog to print and call using log(message:"hello"), also I had to put the flags in as "-D" "kLOG_ENABLE", with the quotes. All other swift version updates were picked up by the compiler with suggested fixes.
  • omarojo
    omarojo over 6 years
    can this be put into a separate file to be used all across the app ? I tried putting it in a separate class file, as a class method. But it crashes with libMobileGestalt MobileGestaltSupport.m:153: pid 2574 (Demo) does not have sandbox access for frZQaeyWLUvLjeuEK43hmg and IS NOT appropriately entitled libMobileGestalt MobileGestalt.c:550: no access to InverseDeviceID (see <rdar://problem/11744455>) Message from debugger: Terminated due to memory issue
  • Gene Loparco
    Gene Loparco over 6 years
    omarojo, I use this as a global function throughout my app. No class is necessary. I have a file named utils.swift, that contain all of my utility functions, such as this. You just need to make sure to import Foundation - perhaps that is the step you have missed? By the way, for more info on declaring your functions within classes, as static functions within classes, or as global functions, see this SO question and answers: stackoverflow.com/questions/30197548/…
  • omarojo
    omarojo over 6 years
    Yeah, got it working by just creating a new file with the function inside. For some reason having it as a Class Function would crash the app with no clear debug message.
  • zumzum
    zumzum over 6 years
    Here you state "empty functions are removed by the Swift compiler", where in the docs do we find that? How do you know that is the case? @ronny-webers
  • beowulf
    beowulf almost 6 years
    Thanks for this man, shame I did not discovered it before. Saved me a lot of debugging headache.
  • Gene Loparco
    Gene Loparco over 5 years
    My pleasure @beowulf!
  • hidden-username
    hidden-username over 5 years
    @matt in your book, you mention "An important feature of print is that it is effectively suppressed when the app is launched independently of Xcode", does that mean we can leave our print statements in our submitted apps these days, or am I misunderstanding something?
  • matt
    matt over 5 years
    @hidden-username Yes, I tend to leave my print statements in my shipping code, but that's different from what my answer here is about. A print statement output is not sent to the console in your Xcode-independent release build, but it is still evaluated, so it remains useful to know how to suppress that evaluation just in case it is expensive or has unwanted side effects.
  • hidden-username
    hidden-username over 5 years
    @matt oh ok...Yeah I misunderstood it. I'll comment them out. Thanks
  • Matrosov Oleksandr
    Matrosov Oleksandr over 4 years
    where to declare this function, is this is some extension or smth like this? I just don't want to declare it in each file)
  • Trev14
    Trev14 over 4 years
    @MatrosovAlexander You can just create a swift file anywhere in your app project and put this code in. The compiler is smart enough to make it globally accessible.
  • Luke
    Luke over 4 years
    What is the use of intFor = 42?
  • Leszek Szary
    Leszek Szary over 4 years
    Will this approach remove the printed string from binary file? For example if I used that method and somewhere in my app I put "print("user logged in")" and then if someone tries to reverse engineer my app will he find this string somewhere or it won't be there at all?
  • Darshan Mothreja
    Darshan Mothreja about 4 years
    Add it anywhere in the project but outside of a class and beware of the parameters to override print function as above it little different try func print(_ items: Any..., separator: String = " ", terminator: String = "\n") Add underscore in items parameter.
  • nr5
    nr5 over 3 years
    What about iOS SDK? Will the i#f DEBUG work there as well ?
  • deaton.dg
    deaton.dg almost 3 years
    Why are you accepting Any... for items but only using the first one? Shouldn't you just accept Any? Or is your code just meant as an example of how you could pass through some number of items?
  • Drew
    Drew almost 3 years
    The caveat to this is items[0] will only print the first item in the series of items passed into the custom print function. You could pass items to Swift.print instead to print all of them. However, the line will be surrounded in array brackets. E.g. print("Hello", "there") would output [Hello there]
  • Kazikal
    Kazikal over 2 years
    I suggest to also add @available(*, unavailable, message: "Use single argument print(item: Any) instead") func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { } to avoid using the multi argument print by mistake
  • matt
    matt over 2 years
    @Kazikal nice idea!