Reputation: 143
I define a simple macro in objective-c header file, and import this header file into Swift through project bridging header. I was able to use this macro as a constant in Swift, but when I use it to do conditional compiling, it doesn't work properly.
I create a simple project in Xcode 10.2.1 and add some code to reproduce it. In ViewController.h
#define TEST_FLAG 1
@interface ViewController : UIViewController
@end
In ViewController.m
#import "testMacro-Swift.h"
- (void)viewDidLoad {
[super viewDidLoad];
SwiftClass *s = [[SwiftClass alloc] init];
[s printMSG];
#if TEST_FLAG
NSLog(@"Objc works.");
#endif
}
In 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
}
}
Console Output
Macro 1
2019-07-03 14:38:07.370231-0700 testMacro[71724:11911063] Objc works.
I expected compiled XXXxXXXXX
to be printed after Macro 1
, but it not.
I am curious why this will happen. My project is mixed with objc and swift. I don't want to declare a same flag in swift.
Upvotes: 4
Views: 2068
Reputation: 4891
Based on this Apple article, https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/using_imported_c_macros_in_swift, simple C (and Objective-C) macros are imported into Swift as global constants. This is demonstrated by the output from your line
print("Macro \(TEST_FLAG)")
The snippet
#if TEST_FLAG
print("compiled XXXxXXXXX")
#endif
uses a different TEST_FLAG
, which is a Swift preprocessor flag. You could define it under Build Settings -> Active Compilation Conditions as TEST_FLAG
or under Build Settings -> Other Swift Flags as -DTEST_FLAG
.
The above explains why this happens. I can't think of a simple way to avoid defining the same flag separately for Objective-C and Swift preprocessor in Xcode. If you just want to control whether some Swift code is executed based on the TEST_FLAG
, you can do something like this:
if TEST_FLAG != 0 {
print("compiled XXXxXXXXX")
}
However, if you want to control compilation of the code, then you may have to use separate TEST_FLAG
s for Objective-C and Swift and make sure they are consistent. To aid in making them consistent, you could set the TEST_FLAG
used by Objective-C code in Other C Flags
, which allow you to define different flags for different SDKs, Architectures, and build types (Release/Debug). Active Compilation Conditions allow the same flexibility.
Another trick to facilitate consistency between (Objective-)C and Swift compiler flags is to create a new user-defined build setting: click on +
to the left of the search box under Build Settings.
Say, call it COMMON_TEST_FLAG
and set its value to TEST_FLAG
. Then add -D$(COMMON_TEST_FLAG)
to Other C Flags and Other Swift Flags. Now when you build your code TEST_FLAG
will be defined in both Objective-C and Swift code within your target. If you don't want it to be defined, just change the value of COMMON_TEST_FLAG
to something else. A couple of things to watch for, though:
COMMON_TEST_FLAG
empty: this will cause the other
flags to be just -D, leading to a build error. COMMON_TEST_FLAG
does not conflict with
macros defined elsewhere.Upvotes: 5
Reputation: 63389
Swift's preprocessor is (intentionally) waaaaaaay more limited than C's. Macros come with really serious draw backs.
They define regions of code that aren't active in every build target. Because of this, your tests won't be hitting every branch (or even compiling every branch). This quickly becomes a maintenance disaster. For n
unique flags, there are 2^npossible sets of values and thus,
2^n` possible builds. Do you have to test each of them? Maybe, maybe not, but even just reasoning about what to test isn't easy.
They can get tangled up and incredibly complex, especially with nested blocks.
In general, try to express as much of your code in the main programming language (C, ObjC, Swift), and resort to using macros only when there's a good reason, such as:
#define max(a, b) ((a) < (b) ? (b) : (a))
). Though this is exceptionally rare.if #available(...)
in Swift, without using the preprocessor.Your example doesn't meet any of these criteria. It's increasing repetition, it's not performance-critical, and it's not doing something that can't be done in regular Swift code.
A much better approach to this (in both Swift and Objective C), is to create a logger that is initialized with different configurations between debug and release builds. Your viewDidLoad
method should not concern itself whether or not TEST_FLAG
is set. View controllers should control views, not make decisions as to what things should be logged. It should just call the logger to send off whatever log messages it wants to send, and leave it up to the logger to decide how to log those messages (to an output stream, file, in-memory circular buffer, database, stream to an analytics API, ignore them, etc.)
Upvotes: 2