当我使用Objective-C中定义的宏时,Swift条件编译无法正常工作

时间:2019-07-03 22:03:01

标签: objective-c swift

我在Objective-C头文件中定义了一个简单的宏,然后通过项目桥接头将该头文件导入Swift。我可以在Swift中将此宏用作常量,但是当我使用它进行条件编译时,它将无法正常工作。

我在Xcode 10.2.1中创建了一个简单项目,并添加了一些代码来重现它。 在ViewController.h

#define TEST_FLAG 1

@interface ViewController : UIViewController
@end

在ViewController.m

#import "testMacro-Swift.h"

- (void)viewDidLoad {
    [super viewDidLoad];

    SwiftClass *s = [[SwiftClass alloc] init];
    [s printMSG];

#if TEST_FLAG
    NSLog(@"Objc works.");
#endif
}

在testMacro-Bridging-Header.h

#import "ViewController.h"

SwiftFile

@objc class SwiftClass: NSObject {
    @objc func printMSG() {
        print("Macro \(TEST_FLAG)")
        #if TEST_FLAG
        print("compiled XXXxXXXXX")
        #endif
    }
}

控制台输出

Macro 1
2019-07-03 14:38:07.370231-0700 testMacro[71724:11911063] Objc works.

我希望compiled XXXxXXXXX之后会打印Macro 1,但不会。

我很好奇为什么会这样。 我的项目混合了objc和swift。我不想迅速声明相同的标志。

2 个答案:

答案 0 :(得分:2)

根据Apple这篇https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/using_imported_c_macros_in_swift文章,简单的C(和Objective-C)宏作为全局常量导入了Swift。

行的输出对此进行了证明。
print("Macro \(TEST_FLAG)")

摘要

    #if TEST_FLAG
    print("compiled XXXxXXXXX")
    #endif

使用另一个TEST_FLAG,它是Swift预处理器标志。您可以在构建设置->活动编译条件下将其定义为TEST_FLAG,或者在构建设置->其他Swift标志下将其定义为-DTEST_FLAG

上面解释了为什么会发生这种情况。我想不出一种简单的方法来避免为Xcode中的Objective-C和Swift预处理器分别定义相同的标志。如果您只想控制是否根据TEST_FLAG 执行了某些Swift代码,则可以执行以下操作:

if TEST_FLAG != 0 {
        print("compiled XXXxXXXXX")
}

但是,如果您想控制代码的编译,则可能必须为Objective-C和Swift使用单独的TEST_FLAG,并确保它们一致。为了使它们一致,您可以在TEST_FLAG中设置Objective-C代码使用的Other C Flags,从而可以为不同的SDK,体系结构和构建类型定义不同的标志(发布/调试) 。主动编译条件具有相同的灵活性。

另一个促进(Objective-)C和Swift编译器标志一致性的技巧是创建一个新的用户定义的构建设置:单击“构建设置”下搜索框左侧的+

说,将其命名为COMMON_TEST_FLAG,并将其值设置为TEST_FLAG。然后将-D$(COMMON_TEST_FLAG)添加到其他C标志和其他Swift标志。现在,在构建代码时,TEST_FLAG将在目标中的Objective-C和Swift代码中进行定义。如果您不想定义它,只需将COMMON_TEST_FLAG的值更改为其他值即可。不过,有几点需要注意:

  • 您不能将COMMON_TEST_FLAG为空:这将导致另一个 标志只是-D,导致生成错误。
  • 确保COMMON_TEST_FLAG的值与 宏在其他地方定义。

答案 1 :(得分:1)

Swift的预处理器(有意地)比C受到更多限制。宏带有严重的缺点。

  1. 它们定义了不是在每个构建目标中都处于活动状态的代码区域。因此,您的测试不会遍历每个分支(甚至不会编译每个分支)。这很快成为维护灾难。对于n个唯一标志,可能有2 ^ n possible sets of values and thus, 2 ^ n个构建。您是否必须测试每个?也许,也许不是,但是,即使只是对要测试的内容进行推理也不容易。

  2. 它们可能会纠结在一起,变得异常复杂,尤其是使用嵌套块时。

通常,请尝试使用主要编程语言(C,ObjC,Swift)表达尽可能多的代码,并仅在有充分理由的情况下才使用宏,例如:

  1. 以某种函数无法实现的方式减少重复。
  2. 提高代码的关键区域的性能(通过强制内联宏代码,例如#define max(a, b) ((a) < (b) ? (b) : (a)))。尽管这种情况很少见。
  3. 表达一种无法用语言表达的逻辑。例如,如果不使用预处理器,就无法在Swift中表达if #available(...)

您的示例不满足以上任何条件。这是增加的重复,它对性能没有要求,并且没有执行常规Swift代码无法完成的工作。

一种更好的方法(在Swift和Objective C中)是创建一个记录器,该记录器在调试和发行版本之间使用不同的配置进行初始化。您的viewDidLoad方法不应该自己考虑是否设置了TEST_FLAG。视图控制器应该控制视图,而不是决定应该记录什么内容。它应该只调用记录器以发送要发送的任何日志消息,然后由记录器决定如何记录这些消息(到输出流,文件,内存循环缓冲区,数据库,流到Analytics API,请忽略它们,等等。