删除发行版iOS Swift的println()

时间:2014-11-13 16:35:42

标签: ios xcode swift xcode6

如果我不在Debug构建中,我想全局忽略我的Swift代码中的所有println()调用。我无法找到任何有力的分步说明,并希望得到指导。有没有办法在全球范围内执行此操作,还是需要使用println()语句包围每个#IF DEBUG/#ENDIF

19 个答案:

答案 0 :(得分:96)

最简单的方法是将自己的全局函数放在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.0 println中更改为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)
}

答案 1 :(得分:45)

针对Swift 4.x进行了更新:

现在,Swift 2.0 / 3.0和Xcode 7/8已经无法使用,在发布版本中如何禁用打印功能会有一些变化。

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

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

  2. 要使用#if DEBUG宏,您必须定义“Swift编译器 - 自定义标志 - 其他标志”以包含值-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
    }
    
  4. 注意:我们在此处将默认分隔符设置为空格,并将默认终结符设置为换行符。如果您愿意,可以在项目中以不同方式进行配置。

    希望这有帮助。

    <强>更新

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

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

答案 2 :(得分:32)

所有这些方法(包括我的方法)的问题在于它们不会消除评估print参数的开销。无论你使用哪一个,这都会很昂贵:

print(myExpensiveFunction())

唯一合适的解决方案是在条件编译中包装实际的print调用(假设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())因此不再昂贵,因为调用被封装在封闭中而没有被评估,并且在发布模式下,它根本不会被评估。

答案 3 :(得分:16)

如上所述,我是一名学生,需要更明确地定义一些内容。经过大量研究,我需要遵循的顺序是:

单击Xco​​de项目窗口左侧File Navigator顶部的项目名称。这是具有项目名称,有多少构建目标以及iOS SDK版本的行。

选择构建设置标签,然后向下滚动到&#34; Swift编译器 - 自定义标志&#34;底部附近。单击其他标志旁边的向下箭头以展开该部分。

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

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

将新的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()对输入也更灵活一些,所以我无法在我的代码中的每个实例中使用它。

答案 4 :(得分:9)

这是我使用的一个函数,它在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
    }

以下是它生成的输出示例:

screenshot of output

说明:

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

  • 时间/日期戳

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

  • gLog消息所在文件的名称

  • gLog消息所在的文件中的函数

  • gLog消息的行号

  • 您要打印的实际gLog消息

希望这对其他人有用!

答案 5 :(得分:8)

使用 Swift 2.1 测试&amp; Xcode 7.1.1

一旦您知道Swift编译器删除了空函数,就可以轻松地从发布版本中排除所有打印语句。

旁注:在Objective-C时代,有一个预解析器可用于在编译器启动之前删除NSLog语句,如我的回答here中所述。但是由于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
}

您可以在此处设置编译器标志:

enter image description here

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

   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
}

答案 6 :(得分:6)

XCode 8引入了一些new build settings 特别是一个被称为Active Compilation Conditions的人以类似的方式执行其他标志设置。

  

&#34;有效编译条件&#34;是一个新的构建设置,用于将条件编译标志传递给Swift编译器。

根据XCode 8(在8.3.2中测试),您将默认获得此信息:

enter image description here

因此,如果没有任何配置,您可以编写以下内容:

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

我强烈建议您,如果您使用此方法,请广泛创建一个包含此日志记录逻辑的类/结构/函数。您可能希望在此之后进一步扩展这一点。

答案 7 :(得分:5)

Swift 4.2

下面的代码对我来说很完美:

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

此函数镜像默认的Swift打印,因此您可以以完全相同的方式使用它,例如print("hello world")(无需输入分隔符或终止符参数)。另外,像这样打印每个项目,也摆脱了打印语句周围烦人的数组括号,如果您将items直接传递到Swift.print(),则会显示这些语句。

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

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

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

public func print(_ items: Any..., separator: String = default, terminator: String = default)

文档还说默认分隔符是单个空格(" "),默认终止符是换行符("\n"),因此我在上面的回答反映了Swift的确切实现-尽管我从未打印过不止一件事情或更改分隔符/终止符。但是谁知道呢,您可能想要。

答案 8 :(得分:4)

在确保为-D DEBUG调试版本设置设置OTHER_SWIFT_FLAGS之后,更简单:

#if !DEBUG
    func println(object: Any) {}
    func print(object: Any){}
#endif

在Swift 2 / Xcode 7中,您不再需要/使用println,但您可能需要添加以下行:

func print(_ items: Any..., separator separator: String = default, terminator terminator: String = default)

答案 9 :(得分:2)

迅速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)",对吧? 希望你喜欢我的解决方案

答案 10 :(得分:1)

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

  1. 在编译器指令上创建-D DEBUG标志,进行设置。
  2. 然后添加此代码:

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

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

答案 11 :(得分:1)

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

enter image description here

答案 12 :(得分:0)

我的解决方案是在课堂

之前在AppDelegate中使用此代码
$AWSaccessKey = 'xxxxxxxxxxxxxxxx';
$AWSsecretKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
$AWSregion = 'xxxxxxxxx';

// bucket
$bucket   = 'xxxxxxxx';
$postdata = $filedata = '<LifecycleConfiguration>
  <Rule>
    <Filter>
      <Prefix>/</Prefix>
    </Filter>
    <Status>Enabled</Status>
    <Expiration>
      <Days>0</Days>
    </Expiration>
  </Rule>
</LifecycleConfiguration>';
$filetype = 'text/plain';
$path     = '/x.php'; // file on which i want to put lifecycle to move it to GLACIER

// file md5
$file_md5 = base64_encode(md5($filedata, true));

// file size
$filesize = strlen($filedata);

// date
$date = gmdate('D, d M Y H:i:s').' +0000';

// -> for putting lifecycle config
$params = array(
    'x-amz-date'          => gmdate('D, d M Y H:i:s \\G\\M\\T'),
);
//'x-amz-security-token'=> $auth['Token']

// sort and stringify params (different to other requests, this is formatted like headers)
$params_str = '';
uksort($params, 'strcmp');
foreach($params as $k=>$v){
    $params_str .= $k.': '.$v."\\n";
}

// -> for putting lifecycle config
$to_sign = "PUT\\n$file_md5\\n$filetype\\n\\n".$params_str.'/'.$bucket.$path;

// create signature
// Note: S3 uses SHA1 instead of 256!
$signature = base64_encode(hash_hmac('SHA1', $to_sign, $AWSsecretKey, true));

$headers = "Host: $bucket.s3.amazonaws.com\\n"; // change to your region
$headers .= $params_str;  // note that the params get added to the header
$headers .= 'Content-MD5: '.$file_md5."\\n";
$headers .= 'Authorization: AWS '.$AWSaccessKey.':'.$signature."\\n";
$headers .= 'Content-Length: '.$filesize."\\n";

$ch = curl_init("http://$bucket.s3-$AWSregion.amazonaws.com");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, explode('\n', $headers));
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_STDERR, fopen(dirname(__FILE__).'/errorlog.txt', 'w'));

$result = curl_exec($ch); 
var_dump($result);

答案 13 :(得分:0)

您可以定义其内容大致为debug_println的内容:

#if DEBUG
  println()
#endif

答案 14 :(得分:0)

我的解决方案我简单了

import UIKit

class DLog: NSObject {

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

   }

}

然后显示它只是调用

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

答案 15 :(得分: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

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

答案 16 :(得分: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-仅在调试版本中处理。

答案 17 :(得分: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
}

答案 18 :(得分:0)

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

func print(_ items: Any...) {
    #if DEBUG
        Swift.print(items[0])
    #endif
}