Arun
Arun

Reputation: 1411

When to use macros in Objective-C

According to definitions "A macro is a fragment of code which has been given a name. Whenever the name is used, it is replaced by the contents of the macro". I used to use macros for those codes which are used multiple times in my class, basically hard coded strings.

My doubts are:

  1. When to use a macro
  2. Good and bad practices while declaring a macro

To explain my second doubt, I have an image file called "menuBurger.png" in my UIView.

[self.hamBurgerButton setImage:[UIImage imageNamed:@"menuBurger.png"] forState:UIControlStateNormal];

So there are two ways I can create a macro in this case

Case 1 : #define HAMBURGER_BUTTON_IMAGE_NAME @"menuBurger"

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

Is there anything wrong in the way I declare macro in case 2? Is it a good practice to use a macro instead of an object (in case 2 it is returning a UIImage object)?

Upvotes: 3

Views: 3827

Answers (1)

VitorMM
VitorMM

Reputation: 1078

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):

Upvotes: 8

Related Questions