发布应用前是否需要禁用NSLog?

122

在发布 iPhone 应用时,如果禁用 NSLog();,它会表现更好吗?


1
在我的当前项目中,我使用 UALogger。它不会在生产环境中记录日志,除非你明确要求它这样做。与普通的 NSLog 相比,它还具有其他优点,例如开箱即用的严重级别(包括 DEBUGINFO 等)。干得好! - Anton Gaenko
1
回答你的问题,“它会表现更好吗?”是的,但你得到的产出取决于你在应用程序中有多少个 NSLog()NSLog() 需要时间来执行,并为应用程序的运行时增加了额外的开销。无论如何,如果使用一个简单的 DEBUG 预处理器宏可以提高性能,那么我们应该禁用它。 - Scott
我还建议,如果您的代码中有很多NSLog / print语句,那么这可能表明您应该花些时间学习更多关于调试器的知识。我经常设置断点以打印我感兴趣的信息,并自动继续执行。是的,它可能会稍微减慢运行速度,但在大多数情况下并不会影响太大。此外,还可以使用条件断点来调查发生意外情况的原因。 - bshirley
12个回答

127

有一种方法是进入您的构建设置,在调试配置下添加一个值到“预处理器宏”值中,例如:

DEBUG_MODE=1

请确保您仅对调试配置执行此操作,而不是Beta或Release版本。然后,在一个公共头文件中,您可以执行以下操作:

#ifdef DEBUG_MODE
#define DLog( s, ... ) NSLog( @"<%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )
#else
#define DLog( s, ... ) 
#endif

现在在测试和调试时,请将NSLog替换为DLog。您将获得调试消息。当您准备发布Beta版本或最终版本时,所有这些DLog行都会自动变为空白,不会有任何输出。这样就不需要手动设置变量或注释NSLogs。选择构建目标即可完成此操作。


2
@SergiusGee:如果找不到函数的声明,则会收到隐式声明警告,此时它认为您正在尝试声明该函数。确保您的类可以访问声明了该函数的头文件。 - sudo rm -rf
1
不要轻易退出日志记录,因为这会使你失去从用户那里获得更好错误报告的能力。使用异步日志记录和日志级别来将性能损失降至几乎为零!(参见Cocoa Lumberjack或Java的Log4j) - Daij-Djan
2
我会选择#if而不是#ifdef,因为DEBUG_MODE 0仍将通过真实路径。 - Grady Player
我已经做了同样的事情,但仍然不起作用。它会给出警告 MR_MODE 宏被重新定义。MR_MODE(DEBUG_MODE) 在构建设置中。 - Abhishek Thapliyal
1
这不是回答问题 - Martin Mlostek
显示剩余2条评论

119

Xcode 5和iOS 7的更新

注意: 对于在发布版本中移除print()语句的Xcode 7 / Swift 2.1解决方案,请查看我的回答这里

是的,你应该在发布代码中删除任何NSLog语句,因为它只会减慢你的代码速度,在发布版本中没有任何用处。幸运的是,在Xcode 5(iOS 7)中,自动删除所有NSLog语句非常简单。那么为什么不去做呢。

首先是三个步骤,然后是一些说明

1)在你的Xcode项目中,找到“yourProjectName-prefix.pch”文件(通常你会在“支持文件”组下找到它,就像你的main.m文件一样)

2)在'.pch'文件的末尾添加以下3行:

#ifndef DEBUG
   #define NSLog(...);
#endif

3) 测试您的“调试”版本和“发布”版本之间的差异。一种方法是通过“编辑方案” - >“运行应用程序名称” - >在选项卡“信息”下,使用调试和发布之间的下拉框进行选择。在发布版本中,您将不会在调试控制台中看到任何NSLog输出!

这一切是如何工作的呢?

首先,必须知道预处理器相对比较“愚笨”,只是在调用编译器之前充当“文本替换器”。它通过将#define语句后面的内容替换为您指定的任何内容来替换您的内容。

#define NSLog(...);

(...)代表方括号()内的“任何内容”。同时注意末尾的;。这并非严格必要,因为编译器会优化掉它,但我喜欢把它放在那里,因为这更加“正确”。

使用#ifdef(如果定义)或#ifndef(如果未定义)可以使define语句具有条件性。

在这里,我们写了#ifndef DEBUG,这意味着“如果符号DEBUG未定义”。#ifdef#ifndef需要用#endif进行“关闭”处理。

Xcode 5在构建模式为“DEBUG”时,默认为我们定义了“DEBUG”符号。在“release”中,此符号未定义。您可以在项目设置下验证此设置,选项卡“Build settings” -> 滚动到“Apple LLVM 5.0 - Preprocessing”部分 -> 预处理器宏。您将看到在发布版本中未定义符号“DEBUG”!

最后,.pch文件由Xcode自动创建,并在编译时自动包含在每个源文件中。因此,这就像是您将整个#define事物放入每个源文件中一样。


1
谢谢@Whasssaaahhh,它运行得很好。 小心不要在日志语句中放置代码!预处理器将删除整个NSLog语句,而不考虑其中的内容。 - Eric Platon
1
如果这是一个没有在预处理器宏中设置调试标志的旧项目,那么重要的是为项目添加“debug=1”,而不是目标。 - Priebe
1
此外,不要使用 NSLog 作为一个无操作语句,例如 if(AllCool) NSLog(@"Cool!Do Nothing!"); else... 而是将 NSLog 放在一些花括号中 if(AllCool) {NSLog(@"Cool!Do Nothing!");} else... - arcady bob
当NSLog周围没有花括号时,这会导致错误的代码。 - Tom
请使用以下代码替换分号:#define NSLog(...) - Tom

34
几乎所有的回答都提出了解决方案,但没有解释问题所在。我在谷歌上进行了搜索,找到了原因。以下是我的答案:是的,如果你在发布版本中注释掉NSLog,性能会变得更好。因为NSLog非常慢。 为什么?NSLog将执行两个操作:1)将日志消息写入Apple System Logging(ASL),2)如果应用在Xcode中运行,则还会将日志写入stderr。
主要问题在于第一个问题。为了实现线程安全,每次调用NSLog时,它都会打开与ASL设施的连接,发送消息,并关闭连接。连接操作非常昂贵。另一个原因是NSLog花费一些时间获取时间戳以记录日志。
参考自这里

23

我个人最喜欢使用可变参数宏。

#ifdef NDEBUG
    #define NSLog(...) /* suppress NSLog when in release mode */
#endif

1
你把这个放在哪里? - user6631314
如果没有在 NSLog 周围加上花括号,而且代码中没有 #ifdef DEBUG,则会出现错误的代码。 - Tom

21

除了那些明智地评论说在生产中根本不调用NSLog()的所有人之外,我要补充一点:

所有这些NSLog()输出字符串都可以被任何从商店下载并将设备插入运行Xcode的Mac电脑(通过组织者窗口)的人看到。

取决于您记录的信息(特别是如果您的应用程序联系服务器、进行身份验证等),这可能是一个严重的安全问题


谢谢提供信息 - 这个在文档里面吗,还是你自己发现的?对于Swift中的打印,这仍然有效吗? - Ronny Webers
我不记得阅读任何文档。我只是在我的设备上安装了存档的构建(与我提交到商店的相同二进制文件),然后将其连接到Xcode。我不知道Swift的print()是否也是这样,但很可能是这样的。 - Nicolas Miari
@NicolasMiari 您所说的“插入Xcode”是什么意思?我们如何将二进制文件插入到Xcode中,我想尝试一下。请给予建议。谢谢。 - iDevAmit
@iDeveloper 我的意思是从AppStore下载你的应用到设备(例如iPhone),通过USB将该设备连接到Xcode,启动你的应用程序并在Xcode的“设备”窗口中查看日志。 - Nicolas Miari
@NicolasMiari 好的,我明白了。谢谢。 - iDevAmit
3
打印输出不会在设备控制台上显示。我刚测试过。 - LC 웃

14

项目默认设置

在Xcode中的当前项目默认设置中,NS_BLOCK_ASSERTIONS宏将在发布版本中设置为1,并且在调试版本中设置DEBUG=1

因此,我更喜欢以下方法。

// NS_BLOCK_ASSERTIONS is defined by default, as shown in the screenshot above.
// Or, you can define yourself Flags in the `Other C Flags` -> `Release`.
#ifndef NS_BLOCK_ASSERTIONS
    #define _DEBUG
#endif

#ifdef _DEBUG
// for debug mode 
#define DLog(fmt,...) NSLog(@"%s " fmt, __FUNCTION, ##__VA_ARGS__) 
... /// something extra
#else
// for release mode
#define DLog(fmt,...) /* throw it away */
... /// something extra
#endif

有一个打字错误,应该是 #ifdef DEBUG。 - Tom
@Tom #define _DEBUG。如果没有定义NS_BLOCK_ASSERTIONS,那么这行代码将会定义_DEBUG - AechoLiu

5

所有的答案都很好,然而这里有另外一个小技巧,你可以在应用程序的开发/测试阶段考虑使用它。

如果你只想关闭你的调试代码而不关闭可能指示你的代码直接控制范围之外问题的消息,则也可以对应用发布代码有用。

技巧:

您可以通过在.m文件顶部简单地包含以下行来关闭NSLog::

#define NSLog(...)

(注意:不要将此代码放在 .h 文件中,只需放在 .m 文件中!)

这只是让编译器通过展开预处理宏来评估 NSLog()。该宏仅仅是去掉了参数。

如果你想再次启用它,你可以随时使用

#undef NSLog

你可以通过以下方式防止在特定方法组中调用NSLog:
```objc #ifndef DEBUG #define NSLog(...) #endif ```
这将在非DEBUG模式下禁用NSLog输出。
#define NSLog(...)
-(void) myProblematicMethodThatSometimesNeedsDebugging {
    ...
}
#undef NSLog

5

是的,你应该禁用它。特别是如果你想要最大化代码的速度。过多地使用NSLog会污染系统日志,其他开发人员可能需要查找这些日志,并且它可能对速度关键的代码(例如循环内部等)产生重大影响。我曾经在一个递归函数中留下了一些日志消息,几周后发布了一个“30%速度提升!”的更新... ;-)


3

NSLog很慢,不应在发布版本中使用。像下面这样的简单宏将禁用它以及您可能有的任何断言,这些断言也应该被禁用。在较少见的情况下,您确实希望在发布版本中使用NSLog,请直接调用它。不要忘记将“-DNDEBUG”添加到您的“其他c标志”构建设置中。

#ifdef NDEBUG
#define MYLog(f, ...) 
#else
#define MYLog(f, ...) NSLog(f, ## __VA_ARGS__)
#endif

Xcode已经有一个DEBUG定义可以使用。在发布模式下,它将自动设置为0。 - Tom

2

在pch文件的#endif之前写下以下内容:

#define NSLog() //

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接