How to Use Objective-C Code with #Define MACros in Swift

How to use a Objective-C #define from Swift

At the moment, some #defines are converted and some aren't. More specifically:

#define A 1

...becomes:

var A: CInt { get }

Or:

#define B @"b"

...becomes:

var B: String { get }

Unfortunately, YES and NO aren't recognized and converted on the fly by the Swift compiler.

I suggest you convert your #defines to actual constants, which is better than #defines anyway.

.h:

extern NSString* const kSTRING_CONSTANT;
extern const BOOL kBOOL_CONSTANT;

.m

NSString* const kSTRING_CONSTANT = @"a_string_constant";
const BOOL kBOOL_CONSTANT = YES;

And then Swift will see:

var kSTRING_CONSTANT: NSString!
var kBOOL_CONSTANT: ObjCBool

Another option would be to change your BOOL defines to

#define kBOOL_CONSTANT 1

Faster. But not as good as actual constants.

use #define in objective C and access in swift class

Importing Objective-C macros into Swift doesn't always work. According to documentation:

Swift automatically imports simple, constant-like macros, declared with the #define directive, as global constants. Macros are imported when they use literals for string, floating-point, or integer values, or use operators like +, -, >, and == between literals or previously defined macros.

C macros that are more complex than simple constant definitions have no counterpart in Swift.

An alternative in your case would be to use a function that returns the value defined by macro

// .h file
#define baseUrl [NSString stringWithFormat:@"%@api/v4", MAINURL]
+ (NSString*) BaseUrl;

// .m file
+ (NSString*) BaseUrl { return baseUrl }

How to use Objective-C code with #define macros in Swift

I resolved this by replacing

#define AD_SIZE CGSizeMake(320, 50)

in the library's Constants.h with

extern CGSize const AD_SIZE;

and adding

CGSize const AD_SIZE = { .width = 320.0f, .height = 50.0f };

in the library's Constants.m file.

Accessing C macros in Swift

There is no easy answer. The aforementioned Apple documentation states this pretty clearly:

Complex macros are used in C and Objective-C but have no counterpart in Swift.

But, if you really need to call those (ugly!) C macros, you could define C99 inline functions around them (and call those instead from your Swift code).

For instance, given this C macro:

#define SQUARE(n) n * n

Define this C function in another header file:

inline double square(double n) {
return SQUARE(n);
}

Not the exact same thing I'm aware — note that I had to commit to the double number type; those crazy text/symbol manipulation won't work either; etc — but might get you halfway there :)


Pure Swift alternative. Of course, you could also convert all those C macros to idiomatic Swift functions by hand, using protocols, generics, etc to emulate some of the C macros magic.

If I went this route — being the paranoid engineer that I'm! — I would compare the MD5 of the original, converted header against the current file version and fail the Xcode build if both hashes don't match.

This could easily be done by a pre-action build script such as:

test EXPECTED_HASH != $(md5 HEADER_PATH) && exit 1

If this build step fails, then it's time to review your (manually) converted Swift code and update the EXPECTED_HASH afterwards :)

Converting an Objective-C #define macro to Swift

It's my understanding that the Swift compiler does not use the C preprocessor, so macros are simply not possible. Like the other commenters, I would suggest finding a more swift-like way to handle this situation. Macros have a serious down-side, as pointed out by @originaluser2 in his/her comment. (Not to mention the fatal flaw of being completely unsupported in Swift.)

How to use Objective-C code with #define macros in Swift

I resolved this by replacing

#define AD_SIZE CGSizeMake(320, 50)

in the library's Constants.h with

extern CGSize const AD_SIZE;

and adding

CGSize const AD_SIZE = { .width = 320.0f, .height = 50.0f };

in the library's Constants.m file.

How to use nesting defines from objc/c in swift?

Check out this documentation for working with C macros in Swift.

Here are the relevant parts:

Swift automatically imports simple, constant-like macros, declared
with the #define directive, as global constants. Macros are imported
when they use literals for string, floating-point, or integer values,
or use operators like +, -, >, and == between literals or previously
defined macros.

C macros that are more complex than simple constant definitions have
no counterpart in Swift.
You use complex macros in C and Objective-C
to avoid type-checking constraints or to avoid retyping large amounts
of boilerplate code. However, macros can make debugging and
refactoring difficult. In Swift, you can use functions and generics to
achieve the same results without any compromises.


EDIT

Here is a workaround that I've used before:

In Bridging_Header.h add a wrapper function

    int exposeTestUseDefine2()
{
return TEST_USE_DEFINE_2;
}

main.swift

    print(exposeTestUseDefine2())

When to use macros in Objective-C

1) When to use a macro

Let me reference another StackOverflow answer first:

Macros are preprocessor definitions. What this means is that before
your code is compiled, the preprocessor scans your code and, amongst
other things, substitutes the definition of your macro wherever it
sees the name of your macro. It doesn’t do anything more clever than
that.

Reference: https://stackoverflow.com/a/20837836/4370893

Based in that, there are some cases where using a macro will make your code not only more readable but also less suitable for bugs. I will list some of them:

1. Avoiding string repetition

The first case is when your program has to deal with lots of similar strings in different classes, like when you are dealing with a dictionary that only exists in your program.

#define USERNAME_KEY @"username"

In the example above, you have a macro with the @"username" string, which you can use instead of writing @"username" all the time. One of the benefits of it is that you will never have to deal with typos. Writing the key name wrongly will be a thing in the past.

Some people prefer using static const's instead, but if it's better or not, it depends of your needs. If you add a static const to a .h file it will be available to any class that imports it, and it will allocated only once.

However, in case you need to use Macros or static const's in multiple parts of the app, you can simply add them to you project .pch file. Since Macros are replaced in compiling time, they will be allocated each time that they are used, but static const's will be allocated for each class that you have, even in the ones that you don't use it. Like I said before, it depends of your needs.

2. Multiple compiling options

Marcos are replaced in compiling time, which means that you can use them to create multiple versions of your app in a single project. In an example, imagine that your app have a regular version (macOS 10.9+ compatible) and a legacy version (macOS 10.6+). Maintaining two projects would be terrible, so you can use macros to solve that problem.

#define IS_LEGACY_VERSION __MAC_OS_X_VERSION_MAIN_REQUIRED < __MAC_10_9

The line above creates a IS_LEGACY_VERSION macro which is a BOOL, telling you if your project requires a version lower than macOS 10.9 or not. That allows you to use macros in a different situation where the compiling result changes:

#if IS_LEGACY_VERSION == TRUE
// Only in legacy app
#else
// Only in regular app
#endif

See the different if/else above? That can be used everywhere, even outside functions, to make anything happen (or even exist) given the condition of the macro.

That's specially useful when you want to use something that wasn't supported in the past and so you need to add a whole new class to support it, like reading JSON's. You can add that function to NSData:

-(id)jsonObject
{
#if IS_LEGACY_VERSION == FALSE
return [NSJSONSerialization JSONObjectWithData:self options:0 error:nil];
#else
NSString* string = [[NSString alloc] iniWithData:self encoding:NSUTF8StringEncoding];
return [string jsonObject];
#endif
}

Where the [NSString jsonObject] function comes from SZJsonParser. You can add both files to your project, add #if IS_LEGACY_VERSION == TRUE to the beginning of both files and #endif to the end of them. Then you just import "SZJsonParser.h" and done! The [NSData jsonObject] function works in both regular and legacy version, and there is no trace of SZJsonParser in the regular version, and no trace of NSJSONSerialization in the legacy version.

Question: Couldn't you just use SZJsonParser directly?

Sure, but maybe one day I will have to drop the Legacy version of the app, and this part of the app would have to die. Also, Apple's NSJSONSerialization is much more optimized then SZJsonParser, so if I can give the regular version users a better experience, why not?

3. Ease string replacement

Imagine that the string from example 1 is a key from a JSON request which is turned into a dictionary. If someone decides that the key will no longer be "username", but "name" instead, it will be very easy for you to replace it.

That also applies to URLs, file paths, hosts, and even more complicated objects like colors, but you should know when to use it or not. With that you can create a list of define's with all your app URLs so they are all in a single place, making them easier to find. static const's also share that advantage.

4. Combine the ones above

If you combine the cases given by the three examples above, the possibilities are numerous. Macros are way more useful than they look.

2) Good and bad practices while declaring a macro

I will use your own cases has an example of good and bad practices:

#define HAMBURGER_BUTTON_IMAGE_NAME     @"menuBurger"

Good practice: With that you can call a hamburger button image by the name from any place in your app, and in case its name change the replacement will be very easy. Also that gives your variable a name, which is much better than calling [UIImage imageNamed:@"menuBurger"] directly (again, don't forget the static const explanation).

#define HAMBURGER_BUTTON_IMAGE          [UIImage imageNamed:@"menuBurger"]

Bad practice: That takes part of the logic in your app and hide it inside the define, so it's not a good thing. You have to pay attention at them when it comes to logic. I will give a couple more examples:

#define RGB(r, g, b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1.0]

Good practice: You are simplifying a function, there no implicit logic in it.

#define ScreenWidth [[UIScreen mainScreen] bounds].size.width

Good practice: You reduced a big function call to a define, which may be very useful. However, you should be careful; you can only add it to classes with UIKit imported, so this may be a dangerous approach.

#define DATE_COMPONENTS NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit

Good practice: Since it's a value built with constraints it will have a fixed value.

#define NavBar self.navigationController.navigationBar

Bad practice: Using self will probably lead you to mistakes since you will be restricted to a certain kind of class. Remember that Macros are replaced in compiling time, so self will be a different object depending of where you use it.

#define ApplicationDelegate ((AppDelegate *)[[UIApplication sharedApplication] delegate])

Bad practice: If your class doesn't import AppDelegate that will fail, which can lead you to mistakes.

#define MAX(x, y) ((x) > (y) ? (x) : (y))

Very bad practice: x and y are both used twice, which can lead you to problems. You can find an explanation for that one here:
http://weblog.highorderbit.com/post/11656225202/appropriate-use-of-c-macros-for-objective-c

Other examples of macros uses (not necessarily all of them are good practices):

  • https://www.tutorialspoint.com/objective_c/objective_c_preprocessors.htm
  • http://matt.coneybeare.me/my-favorite-macros-for-objective-c-development-in-xcode/
  • https://gist.github.com/numo16/3407652
  • http://amattn.com/p/useful_objective_c_macros.html


Related Topics



Leave a reply



Submit