删除发行版iOS Swift的println()


84

println()如果我不在Debug版本中,我想全局忽略Swift代码中的所有调用。为此,我找不到任何可靠的逐步说明,希望能提供一些指导。有没有办法做到这一点从全球来看,还是我需要围绕每一个println()#IF DEBUG/#ENDIF报表?



6
不再在设备控制台中打印输出,而在调试器控制台中打印。因此无需删除发行版本。
阿尼

1
从Xcode 8和swift 3开始,我在发行版模式下的控制台中看不到打印内容。
CiNN

Answers:


101

最简单的方法是将自己的全局函数放在Swift的前面println

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

当需要停止记录时,只需注释掉该函数的主体即可:

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

或者您可以通过使用条件使其自动:

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

编辑在Swift 2.0println中更改为print。不幸的是,它现在具有可变参数的第一个参数。这很酷,但是这意味着您不能轻易覆盖它,因为Swift没有“ splat”运算符,因此您不能在代码中传递可变参数(只能按字面意义创建)。但是您可以制作一个简化版本,该版本在仅打印一个值的情况下(通常是这样)起作用:

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

在Swift 3中,您需要隐藏第一个参数的外部标签:

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

3
好的解决方案,谢谢。有人告诉我,在iOS(而不是OS X)中,println()它不是在发布模式下执行的。
Nate Birkholz

13
@NateBirkholz不,这是胡扯。(在测试后确定
要说

在Swift 2中,将功能重命名为print,您是否会希望功能匹配?另外,您将如何在全局范围内定义它?我尝试将其放在AppDelegate的类之外,即使我有百万次print()调用,它也从未被调用过。这是我尝试的方法:func print(object:Any){Swift.print(object)}
Charlie

@Charlie是的,我仍将其println更改为使用print。它对您print不起作用的原因是您的定义与Swift的定义不匹配,因此您不会覆盖它。这是一个小问题,因为正如很多次提到的那样,Swift没有splat运算符,因此您不能传递可变参数。但这对一项有效,您可以通过传递items[0]
马特2015年

2
如果将这些日志语句插入代码的高性能部分,请注意以下几点:Swift仍然会花时间进行字符串插值和渲染参数以传递给函数,即使它们不会被使用。我看到真正有条件地删除语句的唯一方法是将它们作为标记的基础。例如if(log){print(..)}在每个使用它们的位置。
Pat Niemeyer

46

为Swift 4.x更新:

随着Swift 2.0 / 3.0和Xcode 7/8现已脱离Beta版,对在发行版本中禁用打印功能的方式进行了一些更改。

上面@matt和@Nate Birkholz提到的一些重要点仍然有效。

  1. println()功能已被替换为print()

  2. 要使用该 #if DEBUG 宏,则必须定义“ Swift Compiler-自定义标志-Other标志”以包含该值-D DEBUG

  3. 我建议Swift.print()在全局范围内覆盖该函数,以便您可以print()在代码中正常使用该函数,但是它将删除非调试版本的输出。您可以在全局范围内添加以下函数签名,以在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
    }
    

注意:我们在此处将默认分隔符设置为空格,将默认终止符设置为换行符。您可以根据需要在项目中进行不同的配置。

希望这可以帮助。

更新:

通常最好将此函数放在全局范围内,以便它位于Swift的 print函数的。我发现最好的组织方式是在您的项目中添加一个实用程序文件(例如DebugOptions.Swift),您可以在其中将该函数放置在全局范围内。

从Swift 3开始,该++运算符将被弃用。我已经更新了上面的代码片段以反映此更改。


1
对不起,该功能放在哪里?
2015年

@ User9527您可能希望将其放置在全局范围内的某个位置,以便可以在整个项目中对其进行访问。在我的项目中,我添加了一个实用程序swift文件(DebugOptions.swift或类似的文件),然后将此函数放置在全局范围内(即不在封闭的类中)。
Glavid 2015年

您是否可以确认从Swift-Xcode的当前版本开始,无需设置-D Debug flat,print语句将不再输出到设备控制台?至少那是我今天测试过的。
user523234 '16

1
从Swift 3开始,可以通过在参数列表的开头添加下划线来使代码简洁一些:“ print(_ items ...”
Jonathan Zhan 2016年

7
因此,我查找了打印的参考(在didFinishLaunching ...中使用了),它为我指向了原始打印功能Swift。将其与@JonathanZhan的注释放在一起,我将函数调整为如下所示,并且它起作用了:public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
Jonny

38

所有这些方法(包括我的方法)的问题在于它们没有消除评估print参数的开销。无论您使用哪种,这都将是昂贵的:

print(myExpensiveFunction())

唯一合理的解决方案是将实际的打印调用包装在条件编译中(假设DEBUG仅针对调试版本进行了定义):

#if DEBUG
print(myExpensiveFunction())
#endif

只有这样,才能防止myExpensiveFunction在发布版本中调用它。

但是,您可以使用autoclosure将评估推回一级。因此,您可以这样重写我的解决方案(这是Swift 3):

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

仅在仅打印一件事的情况下,这解决了该问题,通常是这样。那是因为item()在发布模式下没有调用它。print(myExpensiveFunction())因此不再昂贵,因为该调用被包装在一个闭包中而没有被评估,而在释放模式下,则根本不会被评估。


有什么用@autoclosure
凯琳

@matt在书中,您提到“打印的一个重要特征是,当应用独立于Xcode启动时,它会被有效地抑制”,这意味着我们这些天可以将打印语句保留在提交的应用中,还是我误会了什么东西
隐藏的用户名,

@ hidden-username是的,我倾向于将print声明保留在运输代码中,但这与我在此处的答案有所不同。一个print声明输出没有发送到您的Xcode独立发布版本的控制台,但它仍然是评估,所以知道如何抑制评估以防万一它是昂贵的或有不必要的副作用仍然有用。
马特

@matt哦,好吧...是的,我误会了。我将其注释掉。谢谢
隐藏的用户名,

这种方法会从二进制文件中删除打印的字符串吗?例如,如果我在应用程序的某处使用了该方法,则将“ print(“用户已登录的用户”)“放进去,然后如果有人尝试对我的应用程序进行逆向工程,他会在某个地方找到该字符串,或者根本就找不到该字符串吗?
Leszek Szary

18

如前所述,我是一名学生,需要对定义的事情有更清楚的了解。经过大量研究,我需要遵循的顺序是:

单击Xco​​de项目窗口左侧文件导航器顶部的项目名称。此行包含项目名称,有多少个构建目标以及iOS SDK版本。

选择“ Build Settings”选项卡,然后向下滚动到底部附近的“ Swift Compiler-Custom Flags ”部分。单击其他标志旁边的向下箭头以展开该部分。

单击“调试”行以将其选中。将鼠标光标放在该行的右侧,然后双击。将出现一个列表视图。点击+列表视图左下方按钮以添加一个值。文本字段将变为活动状态。

在文本字段中,输入文本-D DEBUG,然后按Return以提交该行。

将新的Swift文件添加到您的项目。您将要为文件创建一个自定义类,因此请按照以下内容输入文本:

class Log {

  var intFor : Int

  init() {
    intFor = 42
   }

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

我今天很难让该类被Xcode接受,因此init可能比必要的要重一些。

现在,您将需要在打算使用新的自定义函数的任何类中引用您的自定义类,而不是println()在每个适用的类中将其添加为属性:

   let logFor = Log()

现在,你可以替换的任何实例println()logFor.DLog()。输出还包括在其中调用该行的函数的名称。

请注意,在类内部函数中,除非将函数的副本作为该类中的类函数进行复制,否则无法调用该函数,并且输入println()内容也更加灵活,因此无法在每个实例中使用我的代码。


8
无需为调试日志创建自定义类。使用全局函数更容易,更实用。
Vojtech Vrbka,2015年

intFor = 42的用途是什么?
Ted

15

迅捷5

只需在项目中创建一个新文件并将此代码粘贴到:

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

该函数签名与默认的Swift签名匹配,因此它“覆盖”了项目中的函数。如果需要,您仍然可以使用来访问原始文档Swift.print()

添加完以上代码后,请print()照常使用,它将仅在调试版本中打印。

注意:在做forEach打印每个项目摆脱恼人的阵列支架的周围出现,如果你只是通过打印报表items直入Swift.print()

对于任何不熟悉Swift的人,您可能会想知道到底$0是什么。它只是代表传递到forEach块中的第一个参数。该forEach语句也可以这样写:

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

最后,如果您有兴趣,Swift的声明print如下:

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

我在上面的回答反映了确切的Swift实现-尽管我从不打印多于一件东西或更改分隔符/终止符。但是谁知道呢,您可能想要。


2
在哪里声明此功能,这是这样的扩展名或名称吗?我只是不想在每个文件中声明它)
Matrosov Alexander

1
@MatrosovAlexander您可以在应用程序项目中的任意位置创建一个swift文件,并将其放入其中。编译器足够智能,可以全局访问。
Trev14

11

这是我使用的一个功能,在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
    }

这是它生成的输出的示例:

输出屏幕截图

说明:

  • 绿色的选中标记用于使您能够快速在控制台中查看打印(gLog)消息,有时它们会在其他消息中丢失

  • 时间/日期戳

  • 正在运行的线程-在我的情况下,它是MainThread(我称为UI),或者不是MainThread(我称为BG,对于后台线程)

  • gLog消息所在的文件的名称

  • gLog消息所在的文件中的功能

  • gLog消息的行号

  • 您想打印出的实际gLog消息

希望这对其他人有用!


可以将其放入单独的文件以在整个应用程序中使用吗?我尝试将其作为类方法放在单独的类文件中。但是它与libMobileGestalt MobileGestaltSupport.m:153崩溃:pid 2574(Demo)没有对frZQaeyWLUvLjeuEK43hmg的沙箱访问,并且没有适当地命名为libMobileGestalt MobileGestalt.c:550:无法访问InverseDeviceID(请参阅<rdar:// problem / 11744455> )来自调试器的消息:由于内存问题而终止
omarojo

omarojo,我将其用作整个应用程序的全局函数。不需要上课。我有一个名为utils.swift的文件,其中包含我的所有实用程序功能,例如这样。您只需要确保导入Foundation-也许您错过了这一步?顺便说一句,对班内的类中声明你的函数,静态函数的详细信息,或作为全局函数,看到这个太问题和答案:stackoverflow.com/questions/30197548/...
基因Loparco

是的,仅通过使用内部函数创建一个新文件即可使其正常工作。由于某种原因,将其作为类函数会导致应用程序崩溃,并且没有明确的调试消息。
omarojo

感谢这个人,可惜我以前没有发现过。节省了我很多调试麻烦。
beowulf

我的荣幸@beowulf!
Gene Loparco '18 October

9

经过Swift 2.1Xcode 7.1.1测试

一旦知道Swift编译器删除了空函数,就有一种简单的方法可以将所有print语句从发行版中排除。

旁注:在Objective-C时代,有一个预解析器可用于在编译器启动之前删除NSLog语句,如我在此处的答案所述。但是由于Swift不再具有预解析器,因此该方法不再有效。

这就是我今天用作高级且易于配置的日志功能的方式,而不必担心在发行版本中将其删除。同样,通过设置不同的编译器标志,您可以根据需要调整记录的信息。

您可以根据需要调整功能,欢迎提出任何改进建议!

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

在这里设置编译器标志:

在此处输入图片说明

带有所有标志的示例输出如下所示:

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

带有log()的代码如下所示:

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

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

好东西!我从这里拿走它,最终制作了AELogAEConsole
tadija '16

在DEBUG模式下,这可以正常工作。现在,我已从“编辑方案”更改为“释放”模式。它还显示了发行版模式的控制台日志窗口。为什么这样?
Jayprakash Dubey

对于Xcode 9中的Swift 3.2,我需要将NSLog更改为使用log(message:“ hello”)进行打印和调用,并且还必须将这些标志放在带有引号的“ -D”“ kLOG_ENABLE”中。编译器通过建议的修复程序提取了所有其他快速版本更新。
iCyber​​Paul

1
在这里您说“空函数已被Swift编译器删除”,我们在文档中哪里找到?您怎么知道是这种情况?@ ronny-webers
zumzum

7

在确定-D DEBUG已设置OTHER_SWIFT_FLAGS调试构建设置之后,甚至更简单:

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

我怀疑这可能需要一个“ where”,因为可打印对象符合您在wwdc的vid中很少提到的那些系统协议之一,并且我认为在1.2和2的快速指南的末尾忘记了区别是系统之一
Stephen J

到目前为止,它可以与Swift 1.2一起使用。还没尝试过2.0。
里维拉2015年

6

XCode 8引入了一些新的构建设置
特别是,所引用Active Compilation Conditions的内容与“其他标志”设置的执行方式类似。

“活动编译条件”是一个新的构建设置,用于将条件编译标志传递给Swift编译器。

根据XCode 8(在8.3.2中测试),默认情况下您将获得此代码:

在此处输入图片说明

因此,无需任何配置,您可以编写以下代码:

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

我强烈建议您,如果您广泛使用此方法,请创建包装此日志记录逻辑的类/结构/函数。您可能需要进一步扩展。


4

到目前为止,Varun Naharia拥有更好的解决方案。我将他的回答与里维拉的...

  1. -D DEBUG在编译器指令上创建一个标志,构建设置。
  2. 然后添加以下代码:

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

此代码会将所有内容转换print为任何内容以供发布。


3

斯威夫特4 Xcode 10.0

也许你可以用这个

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

使用的原因@autoclosure是,如果将函数作为消息参数传递,则仅在调试模式下会调用该函数,这会导致性能下降。

Swift.print(_ items: Any..., separator: String = default, terminator: String = default)函数不同,我的解决方案只有一个参数,因为在大多数情况下,我们不传递多个参数,因为print函数仅在控制台中显示信息,我们可以将参数转换为String:,"\(param1)"+"\(param2)"对吗?希望你喜欢我的解决方案


1

您还可以使用一个断点,将其设置为在评估后继续运行,并在该断点处写入打印消息!

在此处输入图片说明



0

我的解决方案是在上课之前在AppDelegate中使用此代码

// 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 

}

0

对于我的解决方案,我使其变得简单

import UIKit

class DLog: NSObject {

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

   }

}

然后显示它只是调用

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

0

我最终使用了这个:

#if DEBUG
func dLog(_ item: @autoclosure () -> Any, _ file: String = #file, _ function: String = #function, _ line: Int = #line) {
    print("\(Date()) [\((file as NSString).lastPathComponent):\(line) \(function)] \(item())")
}
#else
func dLog(_ item: @autoclosure () -> Any) {}
#endif

它非常紧凑,可以打印一些有用的信息(时间戳,快速文件名,代码行,函数名),至少在我的测试中,以十六进制编辑器打开时,在应用程序二进制文件中找不到任何记录的字符串。


0

甚至更简单:利用断言从发行版本中删除并且仅从那里进行调用的事实。这将删除所有日志调用(是的,甚至包括Log.da的调用),因为它们在构建发行版时为空。

但是我也听说打印已被删除以用于发行版本,但无法以书面形式找到它。所以现在,我正在使用这样的东西Log下面。我在GitHub上有一个更加生动的版本,其中包含表情符号(出于可读性)和日志主题(出于一致性):

https://github.com/Gatada/JBits/blob/master/Project/Utility/Log.swift

public enum Log {

    /// A date formatter used to create the timestamp in the log.
    ///
    /// This formatter is only created if it is actually used, reducing the
    /// overhead to zero.
    static var formatter: DateFormatter?

    // MARK: - API

    /// Call to print message in debug area.
    ///
    /// Asserts are removed in release builds, which make
    /// the function body empty, which caused all calls to
    /// be removed as well.
    ///
    /// Result is zero overhead for release builds.
    public static func da(_ message: String) {
        assert(debugAreaPrint(message))
    }

    // MARK: - Helpers

    /// The function that actually does the printing. It returns `true` to
    /// prevent the assert from kicking in on debug builds.
    private static func debugAreaPrint(_ message: String) -> Bool {
        print("\(timestamp) - \(message)")
        return true
    }

    /// Creates a timestamp used as part of the temporary logging in the debug area.
    static private var timestamp: String {

        if formatter == nil {
            formatter = DateFormatter()
            formatter!.dateFormat = "HH:mm:ss.SSS"
        }

        let date = Date()
        return formatter!.string(from: date)
    }
}

在代码中:

Log.da("This is only handled in a debug build.")

在运行调试版本时才在Xcode调试区域中看到:

13:36:15.047-仅在调试版本中处理。


0

我的项目是在Objective C中开发的,但是从去年开始,我开始在Swift中合并新代码,因此在下面的解决方案对我有用的Swift中,我将该代码添加到了我的Swift常量文件中:

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

0

这对我有用(将其添加为项目中的全局功能)

func print(_ items: Any...) {
    #if DEBUG
        Swift.print(items[0])
    #endif
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.