Remove println() for release version iOS Swift

限于喜欢 提交于 2019-12-17 07:00:32

问题


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?


回答1:


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)
}



回答2:


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.




回答3:


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.




回答4:


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.




回答5:


Here is a function that I use, which works perfectly in Swift 3:

func gLog<T>( _ object: @autoclosure() -> T, _ file: String = #file, _ function: String = #function, _ line: Int = #line)
    {
    #if DEBUG
        let value = object()
        let stringRepresentation: String

        if let value = value as? CustomDebugStringConvertible
            {
            stringRepresentation = value.debugDescription
            }
        else if let value = value as? CustomStringConvertible
            {
            stringRepresentation = value.description
            }
        else
            {
            fatalError("gLog only works for values that conform to CustomDebugStringConvertible or CustomStringConvertible")
            }

        let fileURL = NSURL(string: file)?.lastPathComponent ?? "Unknown file"
        let queue = Thread.isMainThread ? "UI" : "BG"
    let gFormatter = DateFormatter()
    gFormatter.dateFormat = "HH:mm:ss:SSS"
        let timestamp = gFormatter.string(from: Date())

        print("✅ \(timestamp) {\(queue)} \(fileURL) > \(function)[\(line)]: " + stringRepresentation + "\n")
    #endif
    }

Here is an example of the output it generates:

Explanation:

  • the green checkmark is used to enable you to quickly see your print (gLog) messages in the console, where they can sometimes get lost in a sea of other messages

  • the time/date stamp

  • the thread it is being run on -- in my case it is either the MainThread (which I call UI), or not the MainThread (which I call BG, for background thread)

  • the name of the file that the gLog message resides in

  • the function within the file that the gLog message resides in

  • the line number of the gLog message

  • the actual gLog message you would like to print out

Hope this is useful to someone else!




回答6:


Swift 4.2

The code below is working perfectly for me:

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

This function mirrors the default Swift print so you can use it the exact same way, like print("hello world") (no need to put in the separator or terminator parameters). Also, printing each item like this 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 = default, terminator: String = default)

The docs also say that the default separator is a single space (" ") and the default terminator is a newline ("\n") so 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.




回答7:


Tested with Swift 2.1 & Xcode 7.1.1

There's an easy way to exclude all print statements from release versions, once you know that empty functions are removed by the Swift compiler.

Side note : In the era of Objective-C, there was a pre-parser which could be used to remove NSLog statements before the compiler kicked in, like described in my answer here. But since Swift no longer has a pre-parser this approach is no longer valid.

Here's what I use today as an advanced and easily configurable log function, without ever having to worry about removing it in release builds. Also by setting different compiler flags, you can tweak the information that is logged as needed.

You can tweak the function as needed, any suggestion to improve it is welcome!

// Gobal log() function
//
// note that empty functions are removed by the Swift compiler -> use #if $endif to enclose all the code inside the log()
// these log() statements therefore do not need to be removed in the release build !
//
// to enable logging
//
// Project -> Build Settings -> Swift Compiler - Custom flags -> Other Swift flags -> Debug
// add one of these 3 possible combinations :
//
//      -D kLOG_ENABLE
//      -D kLOG_ENABLE -D kLOG_DETAILS
//      -D kLOG_ENABLE -D kLOG_DETAILS -D kLOG_THREADS
//
// you can just call log() anywhere in the code, or add a message like log("hello")
//
func log(message: String = "", filePath: String = #file, line: Int = #line, function: String = #function) {
            #if kLOG_ENABLE

            #if kLOG_DETAILS

            var threadName = ""
            #if kLOG_THREADS
                threadName = NSThread.currentThread().isMainThread ? "MAIN THREAD" : (NSThread.currentThread().name ?? "UNKNOWN THREAD")
                threadName = "[" + threadName + "] "
            #endif

            let fileName = NSURL(fileURLWithPath: filePath).URLByDeletingPathExtension?.lastPathComponent ?? "???"

            var msg = ""
            if message != "" {
                msg = " - \(message)"
            }

            NSLog("-- " + threadName + fileName + "(\(line))" + " -> " + function + msg)
        #else
            NSLog(message)
        #endif
    #endif
}

Here's where you set the compiler flags :

An example output with all flags on looks like this :

   2016-01-13 23:48:38.026 FoodTracker[48735:4147607] -- [MAIN THREAD] ViewController(19) -> viewDidLoad() - hello

The code with the log() looks like this :

    override func viewDidLoad() { log("hello")
    super.viewDidLoad()

   // Handle the text field's user input through delegate callbacks
   nameTextField.delegate = self
}



回答8:


XCode 8 introduced a few new build settings.
In particular one referred to Active Compilation Conditions does in a similar way what Other Flags settings did.

"Active Compilation Conditions" is a new build setting for passing conditional compilation flags to the Swift compiler.

As per XCode 8 (tested in 8.3.2) you will get this by default:

So without any config you can write the following:

#if DEBUG
    print("⚠️ Something weird happened")
#endif

I strongly recommend you that if you use this approach extensively create a class/struct/function that wraps this logging logic. You may want to extend this further down the road.




回答9:


Even simpler, after making sure -D DEBUG is set for the OTHER_SWIFT_FLAGS Debug build settings:

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



回答10:


Swift 4 Xcode 10.0

maybe you could use this

func dPrint(_ message: @autoclosure () -> Any) {
    #if DEBUG
    print(message())
    #endif
}

The reason of using @autoclosure is that if you pass a function as the message parameter, the function will be called only in debug mode, it will cause a performance hit.

unlike the Swift.print(_ items: Any..., separator: String = default, terminator: String = default) function, my solution has only one parameter, because in most cases, we don't pass multiple parameters as the print function only shows information in console, we can just convert the parameters to String: "\(param1)"+"\(param2)", right? hope u like my solution




回答11:


Varun Naharia has the better solution so far. I would combine his answer with Rivera's ...

  1. create a -D DEBUG flag on the compiler directives, build settings.
  2. then add this code:

    #if !DEBUG
     public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
    }
    #endif
    

This code will convert every print into nothing for release.




回答12:


You could define debug_println whose contents would be roughly:

#if DEBUG
  println()
#endif



回答13:


My Solution is use this code in AppDelegate before class

// Disable console log in live app
#if !arch(x86_64) && !arch(i386)
    public func debugPrint(items: Any..., separator: String = " ", terminator: String = "\n") {

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

    }
#endif

class AppDelegate: UIResponder, UIApplicationDelegate {
// App Delegate Code 

}



回答14:


for my solution i make it simple

import UIKit

class DLog: NSObject {

   init(title:String, log:Any) {
       #if DEBUG
           print(title, log)
       #endif

   }

}

then to show it just call

_ = DLog(title:"any title", log:Any)


来源:https://stackoverflow.com/questions/26913799/remove-println-for-release-version-ios-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!