Условная компиляция Swift не работает должным образом, когда я использую макрос, определенный в target-c - PullRequest
2 голосов
/ 04 июля 2019

Я определяю простой макрос в файле заголовка target-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
}

В тесте Macro-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.

Я ожидал, что после Macro 1 будет напечатано compiled XXXxXXXXX, но это не так.

Мне любопытно, почему это произойдет.Мой проект смешан с objc и swift.Я не хочу объявлять тот же флаг в swift.

Ответы [ 2 ]

2 голосов
/ 04 июля 2019

Основываясь на этой статье 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.

Выше объясняется, почему это происходит.Я не могу придумать простой способ избежать определения одного и того же флага отдельно для Objective-C и препроцессора Swift в Xcode.Если вы просто хотите контролировать, выполняется ли какой-либо код Swift , выполняемый на основе TEST_FLAG, вы можете сделать что-то вроде этого:

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

Однако, если вы хотите контролировать компиляция кода, тогда вам, возможно, придется использовать отдельные TEST_FLAG s для Objective-C и Swift и убедиться, что они согласованы.Чтобы помочь сделать их согласованными, вы можете установить TEST_FLAG, используемый кодом Objective-C, в Other C Flags, что позволит вам определять разные флаги для разных SDK, архитектур и типов сборки (Release / Debug).Условия активной компиляции допускают такую ​​же гибкость.

Еще один прием для обеспечения согласованности между (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 голос
/ 04 июля 2019

Препроцессор Свифта (намеренно) более ограничен, чем С.Макросы имеют очень серьезные недостатки.

  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. Выражение части логики, которая не может быть выражена в языке.Например, нет способа выразить if #available(...) в Swift без использования препроцессора.

Ваш пример не соответствует ни одному из этих критериев.Это увеличение повторений, это не критично для производительности, и он не делает то, что не может быть сделано в обычном коде Swift.

Гораздо лучший подход к этому (как в Swift, так и в ObjectiveC), для создания регистратора, который инициализируется с различными конфигурациями между сборками отладки и выпуска.Ваш метод viewDidLoad не должен заботиться о том, установлен или нет TEST_FLAG.Контроллеры представления должны управлять представлениями, а не принимать решения относительно того, что должно быть зарегистрировано.Он должен просто вызвать регистратор для отправки любых сообщений журнала, которые он хочет отправить, и оставить его на усмотрение регистратора, чтобы решить, как записывать эти сообщения (в выходной поток, файл, кольцевой буфер в памяти, базу данных, поток вAPI аналитики, игнорируйте их и т. д.)

...