我以前使用预处理器宏来定义常量,然后发现它们是个坏主意。它们位于名为Global.h
的头文件中。所以我创建了一个Global.m
文件,并使用以下内容从git中忽略它:
#if DEBUG
BOOL const GodMode = YES;
BOOL const TutorialDisabled = YES;
#elif STAGING
BOOL const GodMode = YES;
BOOL const TutorialDisabled = NO;
#else
BOOL const GodMode = NO;
BOOL const TutorialDisabled = NO;
#endif
和Global.h
有此内容:
extern BOOL const GodMode;
extern BOOL const TutorialDisabled;
STAGING
宏在项目设置Preprocessor Macros
中为我的自定义Configuration
Staging
STAGING=1
定义为DEBUG
。
DEBUG=1
宏是Xcode生成的默认宏,类似于暂存:Debug
。
有时,我想编写仅针对#if DEBUG
// do something
#endif
配置执行的代码,而无需添加新常量。
所以我开始使用:
// do something
有这些优点/缺点:
的优点:
!
对于其他配置甚至不存在,因此应用规模不会增加且不存在拼写错误的风险(例如添加// do something
),这将导致它在不需要时运行缺点:
#if
的缩进以匹配if (Debug) {
// do something
}
或其周围的代码,或者我是否应该添加如上所述的空行,或者是否应该在与代码相同的缩进级别的行的开头;有类似的东西:Global.h
听起来更好主意,所以我将extern BOOL const Debug;
extern BOOL const Staging;
extern BOOL const Release;
extern BOOL const GodMode;
extern BOOL const TutorialDisabled;
更改为:
Global.m
和#if DEBUG
BOOL const Debug = YES;
BOOL const Staging = NO;
BOOL const Release = NO;
BOOL const GodMode = YES;
BOOL const TutorialDisabled = YES;
#elif STAGING
BOOL const Debug = NO;
BOOL const Staging = YES;
BOOL const Release = NO;
BOOL const GodMode = YES;
BOOL const TutorialDisabled = NO;
#else
BOOL const Debug = NO;
BOOL const Staging = NO;
BOOL const Release = YES;
BOOL const GodMode = NO;
BOOL const TutorialDisabled = NO;
#endif
:
static BOOL const Debug = DEBUG;
static BOOL const Staging = STAGING;
static BOOL const Release = (!DEBUG && !STAGING);
并且有效。
出于某种原因,
Use of undeclared identifier 'STAGING'
抛出错误DEBUG
,即使它的定义与DEBUG=0
完全相同:这意味着其他配置没有//:configuration = Debug
GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1
//:configuration = Staging
GCC_PREPROCESSOR_DEFINITIONS = STAGING=1
//:configuration = Release
:
Preprocess
但是,查看if (Debug) {
助理编辑器视图,if
会显示出来。
我的问题是,public class App {
StringTokenizer st = new StringTokenizer("Pete likes Mathematics 3+3 and Jessica too 6+3.", " \t\n\r:;.!?,/\\|\"\'", true);
public static void main(String[] args) {
new App();
}
public App(){
ArrayList<String> renderedStrings = new ArrayList<String>();
while(st.hasMoreTokens()){
String s = st.nextToken();
if(!AdditionPatternFuntion.render(s, renderedStrings)){
renderedStrings.add(s);
}
}
for(String s : renderedStrings){
System.out.print(s);
}
}
}
检查是否会实际保留在发布配置的代码中?如果是,是否有更好的方法来实现我之前描述的内容?
答案 0 :(得分:2)
没有比告诉自己更好的方式了。通过我的Globals.h
和Globals.m
文件,我写了以下main.m
:
#import "Globals.h"
#import <Foundation/Foundation.h>
int main()
{
@autoreleasepool {
if (Debug) {
puts("We're in debug mode");
} else if (Staging) {
puts("We're in staging mode");
} else {
puts("We're in release mode");
}
}
}
非常简单,只需打印出我们所处的模式。让我们编译并运行它以查看会发生什么:
$ clang -g -fobjc-arc -o var_test main.m Globals.m && ./var_test
We're in release mode
看起来合理 - 我们没有提供DEBUG
或STAGING
,因此我们必须处于发布模式。 (-g
告诉clang输出调试信息,如果它有用; -fobjc-arc
打开ARC; -o
告诉clang输出具有特定名称的可执行文件。)测试:
$ clang -g -DDEBUG -fobjc-arc -o var_test main.m Globals.m && ./var_test
We're in debug mode
$ clang -g -DSTAGING -fobjc-arc -o var_test main.m Globals.m && ./var_test
We're in staging mode
一切都很好,看起来很有效! (-DDEBUG
在预处理器中定义DEBUG
; -DSTAGING
对STAGING
执行相同操作。)现在的问题是,clang是否删除了if语句并替换了代码只是一个打印声明?我不会假装你的装配技巧,所以让我们来看看编译器通过Hopper的伪代码模式生成的内容(这会在生成的目标代码中加载)由编译器反向反汇编成类似C代码的东西:
int _main() {
var_10 = objc_autoreleasePoolPush();
if (*(int8_t *)_Debug != 0x0) {
puts("We're in debug mode");
}
else {
if (*(int8_t *)_Staging != 0x0) {
puts("We're in staging mode");
}
else {
puts("We're in release mode");
}
}
objc_autoreleasePoolPop(var_10);
rax = 0x0;
return rax;
}
变量名称消失了,这看起来有点奇怪的C /汇编混合,但很明显if语句仍在那里...
好吧,好吧,也许如果我们尝试优化构建一点点?也许Clang在这里没有做任何优化。
$ clang -g -O2 -fobjc-arc -o var_test main.m Globals.m
(-O2
启用了一些激进的编译器优化。)Hopper输出:
int _main() {
rbx = objc_autoreleasePoolPush();
if (*(int8_t *)_Debug != 0x0) {
rdi = "We're in debug mode";
}
else {
if (*(int8_t *)_Staging != 0x0) {
rdi = "We're in staging mode";
}
else {
rdi = "We're in release mode";
}
}
puts(rdi);
objc_autoreleasePoolPop(rbx);
return 0x0;
}
好的,略有不同,但它还在那里!是什么给了什么?
对于许多编译语言,包含C / Objective-C,编译只需几个阶段即可完成。您在项目中包含的每个.m文件都被单独编译为目标代码(编译阶段),然后所有生成的目标代码文件一起链接(链接阶段)。这里的关键是每个.m文件都是单独编译的 - 即使变量是在Globals.m
中静态定义的,当编译main.m
时,编译器也不知道Debug
和Staging
的值 - 它只看到Globals.h
中的声明表示&#34;嘿,有线下的人会提供实际值这些变量,我保证&#34 ;;它由链接器将这些粘合在一起。 (这就是为什么你可以将main.m
编译成main.o
就好了,即使你忘记定义Debug
和Staging
- 只有当你试图< em>将所有.o文件链接到一个可执行文件中,您收到链接器错误,告诉您存在问题。)
好的,这是一个链接问题,那么,不是编译问题。我们有什么方法可以让链接器为我们解决这个问题吗?是的,是的。 :)
$ clang -g -flto -fobjc-arc -o var_test main.m Globals.m && ./var_test
We're in release mode
料斗输出:
int _main() {
rbx = objc_autoreleasePoolPush();
puts("We're in release mode");
objc_autoreleasePoolPop(rbx);
return 0x0;
}
啊,好多了! -flto
标志启用Link Time Optimization,允许链接器查看.o文件,因为它将它们粘合在一起并根据它看到的内容进行优化。在启用优化的情况下链接将花费更长的时间(您可以在目标的Xcode中的Build Settings中启用LTO),但会执行这样的优化。
现在,对于真正的问题:这甚至是一个很好的方法吗?呃,也许不是。
为此开启LTO有点矫枉过正。在您的代码中使用#if DEBUG
没有任何害处,而且它实际上相对常见。你可以保持缩进完全相同:
int main()
{
int x;
if (/* some calculation setting x */) {
#if DEBUG
NSLog("Calculation succeeded: %d", x);
#endif
// ...
}
}
或者,如果您更愿意坚持使用变量方法,欢迎您在static
中将变量定义为Globals.h
变量,完全取消Globals.m
(和摆脱LTO的需要):
#if DEBUG
static BOOL const Debug = YES;
static BOOL const Staging = NO;
static BOOL const Release = NO;
static BOOL const GodMode = YES;
static BOOL const TutorialDisabled = YES;
#elif STAGING
static BOOL const Debug = NO;
static BOOL const Staging = YES;
static BOOL const Release = NO;
static BOOL const GodMode = YES;
static BOOL const TutorialDisabled = NO;
#else
static BOOL const Debug = NO;
static BOOL const Staging = NO;
static BOOL const Release = YES;
static BOOL const GodMode = NO;
static BOOL const TutorialDisabled = NO;
#endif
您也可以使用
static BOOL const Debug = DEBUG;
static BOOL const Staging = STAGING;
static BOOL const Release = (!DEBUG && !STAGING);
版本。它不工作的原因正是你自己说的:&#34;其他配置没有DEBUG = 0&#34;。如果您没有定义DEBUG
,那么编译器应该对static BOOL const Debug = DEBUG;
做些什么?您应该在暂存和释放模式中定义DEBUG=0
,在调试和释放模式下定义STAGING=0
以使其正常工作。
希望这有帮助!