SoFunction
Updated on 2025-04-06

Objective-C macro definition detailed introduction

People who like to read some open source projects will always find that there are always some short and efficient macro definitions in the code of the master. Click in and look at it and find that it is obscure and difficult to understand. Not to mention learning, sometimes understanding is a difficult problem, but the macro definition itself is not that difficult. However, writing a good macro requires rich experience and technology. Next, let’s talk about macro definitions. Understanding the master’s macro is the first step. Occasionally writing one is also a good way to show off~

definition:

Macro definitions are divided into two types: one is object-like macro, and the other is function-like macro.

According to the name, it can also be understood that the object macro is used to define a quantity. Through this macro, this variable can be obtained. For example, we define a π value: #define PI 3.1415926 If the π value is used here, there is no need to write a floating point number. Using PI directly is equivalent to writing this constant floating point number. The essential significance of its PI in the code is replaced with a real constant during the compilation stage, which is generally used to define some commonly used constants, such as the width and height of the screen, the system version number, etc. But it should be noted that when you define an expression as a macro, you need to see the essence of the compiler through the surface of the macro, for example

#define MARGIN  10 + 20

But when you use it to calculate a width, if you use MARGIN * 2, the result will be out of your wish. What you get will be a 50 instead of 60. You can see by expanding the expression

MARGIN * 2 // Expand to get
//  10 + 20 * 2  = 50

We need to consider its operation priority, and the solution is very simple, and then add a small bracket to the outer layer of it.

#define MARGIN (10 + 20)
// MARGIN * 2
// (10 + 20) * 2 = 60

The function of a function macro is similar to a function. It can pass parameters and perform a series of operations through parameters. For example, we often use to calculate the maximum value of two numbers. We can define it in this way.

#define MAX(A,B) A > B ? A : B

It seems that there is no problem to write this way. It is also easy to compare MAX(1,2) and find that there is no problem, but when someone uses your macro to perform more complex calculations, new problems will appear, such as when comparing three values, you may write this way

int a = 3;
int b = 2;
int c = 1;
MAX(a, b > c ? b : c) //
= 2

The result is definitely not what you want. The maximum value is obviously 3, but the calculation result is indeed 2. What happened and caused the calculation error. We can expand the macro to find out. The following is the expansion of the macro.

MAX(a,b > c ? b : c);
//a > b > c ? b : c ? a : b > c ? b : c
//(a > (b > c ? b : c) ? a : b) > c ? b : c // This is the priority of the operation// You can see that the value is brought in//( 3 > (2 > 1 ? 2 : 1 ) ? 3 : 2) > 1 ? 2 : 1
// (3 > 2 ? 3 : 2) > 1 ? 2 : 1
// 3 > 1 ? 2 : 1

I believe everyone has seen the problem, or because of priority, so remember here that writing two more brackets will not be tiring. Regardless of whether there will be problems, writing brackets will be more safe after all~
But there are always weird ways to write, and it seems to make sense when you see it~

c = MAX(a++,b); // **I just expand it for you**// c = a++ > b ? a++ : b
// c = 3++ > 2 ? 3++ : 2
// c = 4
// a = 5

No matter how much the person who wrote this is, it seems that there is no problem after all. We have to deal with such a situation, but using our ordinary brackets can no longer solve it. We need to use assignment extension ({...}) and I believe some friends have recognized this usage. We can use this method to calculate an object without wasting the variable name. We can form a small scope to calculate special values.

int a = ({
 int b = 10;
 int c = 20;
 b + c;
})
// a = 30;
int b; // Continue to use b and c when the variable name is not a problemint c;

Back to the current issue, how should we modify this macro to adapt to this trick?

#define MAX(A,B) ({__typeof(A) __a = (A);__typeof(B) __b = (B); __a > __b ? __a : __b; })

__typeof() is to convert to the same type of variable value, which perfectly solves this problem, but there is another accident that doesn't happen. From the above, we can also know that we have generated new variables __a, __b. How someone uses __a, __b, and compiles errors for duplication of variable names. If someone uses this, you can pick up your keyboard and smash it in the face. Of course, the reason is not that __a makes your macro wrong, but what does __a mean? The importance of variable names is self-evident. Unless you have a grudge against the person who reads the code, please use meaningful variable names. Next, let's take a look at how the official MAX is implemented.

#define __NSX_PASTE__(A,B) A##B

#if !defined(MAX)
  #define __NSMAX_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); __typeof__(B) __NSX_PASTE__(__b,L) = (B); (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__b,L) : __NSX_PASTE__(__a,L); })
  #define MAX(A,B) __NSMAX_IMPL__(A,B,__COUNTER__)
#endif

This is the MAX definition in the Function framework. Let me parse it step by step. The first thing I see is

#define __NSX_PASTE__(A,B) A##B
// Connect A and B to one

Its function is to connect A and B to a piece and generate a string of characters. For example, A##12 becomes A12

Next we see a macro definition with three parameters __NSMAX_IMPL__(A,B,_COUNTER__)

#if !defined(MAX)
  #define __NSMAX_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); __typeof__(B) __NSX_PASTE__(__b,L) = (B); (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__b,L) : __NSX_PASTE__(__a,L); })
  #define MAX(A,B) __NSMAX_IMPL__(A,B,__COUNTER__)
#endif

Let's first explain what __COUNTER__ is. __COUNTER__ is a precompiled macro. It will add 1 at each compilation time. In this way, it can ensure that the variable names generated by __NSX_PASTE__(__b,__CONNTER__) are not easy to repeat, but this is still a bit dangerous, that is, if you call the variable name __a20, there is really no way~

Variable parameter macros

Speaking of variable parameters, the most common method we use NSLog(...) is variable parameters. Variable parameters means that the number of parameters is uncertain. As an important tool for debugging, NSLog is really too wasteful and can only print the corresponding time and parameter information. The important information such as file name, line number, method name are not given. Today we will use this to implement a super version of NSLog macro~~~

#define NSLog(format, ...) do { fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \ } while (0)

First, look at the definition of this macro NSLog(format,...) and found that it has..., which is a variable parameter, and __VA__ARGS__ is all the remaining parameters except format. Next we found that a do{}while(0) loop was used, which means that this loop only executes and stops again. It feels nonsense. Our purpose is to execute it once, but this is written for defensive programming. If someone writes it like this

if (100 > 99)
  NSLog(@"
%@",@"Fuck");

The last two prints will occur no matter what. I believe everyone knows the problems that arise. Then we just use {} to expand them. This problem has indeed been solved after actual operation, but if we expand it again, new problems will appear when we use if{} else if{}

if (100 &gt; 99)
 NSLog(@"%@",@"Fuck");
else {
}
// You can get it after expandingif (100 &gt; 99)
{ fprintf(stderr, "&lt;%s : %d&gt; %s\n",
 [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
 (NSLog)((format), ##__VA_ARGS__);
 fprintf(stderr, "-------\n");};
else {
}

Compilation errors, everyone also found that NSLog will follow;, if I use {} directly, I will add it outside during compilation, resulting in a compilation error, and this problem will not occur after using do{} while(0) loop.

if (100 > 99)
 do { fprintf(stderr, "<%s : %d> %s\n",
 [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
 (NSLog)((format), ##__VA_ARGS__);
 fprintf(stderr, "-------\n");} while(0);
else {
}

The problem has been solved at this location. Let’s take a look at the internal structure. __FILE__ is the compiled file path, __LINE__ is the number of lines, and __func__ is the compiled method name. We see it again below.

(NSLog)((format), ##__VA_ARGS__);

##I have seen it above. The function here is similar, and it also means connection. __VA_ARGS__ is all the remaining parameters. After connecting with ##, NSLog (format,__VA_ARGS__) is NSLog (format,__VA_ARGS__). This is the NSLog method. But I wonder if anyone has found a detail. If __VA_ARGS__ is empty, then it will become NSLog (format,) and it will definitely report an error in the compilation. However, Apple masters have long thought of a solution. If __VA_ARGS__ is empty, ## will swallow the previous one here, so there will be no problem. Then we can use this powerful NSLog().

Next, let’s talk about the use of multi-parameter functions

- (void)say:(NSString *)code,... {  
  va_list args;
  va_start(args, code);
  NSLog(@"%@",code);
  while (YES) {
    NSString *string = va_arg(args, NSString *);
    if (!string) {
      break;
    }
    NSLog(@"%@",string);
  }
  va_end(args);
}

We can first define a va_list args to define multi-parameter variable args, and then start the value by va_start(args, code). The code is the first value, va_arg(args, NSString *) to define the type of the retrieved value. The value method is a bit like a generator. After the acquisition is completed, va_end(args) is called to close. This is the whole process. This method is rarely used in normal times. If you have any good practical methods, please comment and give me advice~~

Thank you for reading, I hope it can help you. Thank you for your support for this site!