The original address

preface

Almost all C family languages have long made use of the macros provided by the preprocessor, such as constant definition, conditional compilation, code generation, magic “syntactic sugar” and so on……

But such a useful function, we must be sure to prevent abuse, due to its plane of action, may be due to improper use problems also strange, so this article will revolve around the content of the macro in iOS modular, facing the problems for the final will be a summary of all issues and the use of a more general advice is given.

The question before binarization

Isolation of macros

Here we will be exposed to external use macros called macros, derived in modular, due to the number of components is difficult to estimate, even in some big companies have a lot of different departments group colleague development maintenance of multiple components, respectively, and inter-departmental communication across the group often need more cost, so try to keep less export macro, By other means such as const constants, function/method statement to the outside world export interface or data, avoid to cause macros and macro, macro and variable, function name, such as conflict, the conflict in the absence of the compiler to do some Settings often produce warnings, and there will be no obvious error, lead to appear puzzling bugs at runtime.

The impact of Clang modularity on macros

When a component relies on a macro from another component (external), there is a question that needs to be paid attention to. That is, whether you are an explicit dependency or an implicit dependency. Implicit dependencies may be difficult to understand, but here is an example:

// ModuleA.h
#define ModuleA_Var 1

// ModuleB.h
# ifdef ModuleA_Var
	#define ModuleB_Var 1
#else
	#define ModuleB_Var 2
#endif

// Business.m
#import <ModuleA/ModuleA.h>
#import <ModuleB/ModuleB.h>

NSLog(@"%d",ModuleB_Var);
Copy the code

As you can see, the business side imports both ModuleA and ModuleB, and ModuleB actually relies on macros in ModuleA, but is referenced together in the business side code instead of explicitly importing it, which I refer to as an implicit dependency. Does the business side print the ModuleB_Var as 1 or 2?

Here leads to a rather need to pay attention to the problem, we know that the preprocessor for macro definition is just a simple textual substitution, the use of components do not open the Module, the import relationship is depend on your order # import instructions, the above example, the print out will be 1, but if you will exchange export order, It will print a 2; When Module is enabled for components and Module is enabled for business (Xcode currently has both options enabled by default), the #import directive is no longer a simple text substitution. The #import directive is automatically changed into the @import directive depending on whether it meets the criteria (Module standard). Moving from a simple text import to a module import, CLang generates a context-clean preprocessor that compiles the module’s header file and caches the results in binary form for reuse by other compilation units that import the module. So the ModuleA header that was imported before it is actually not aware of ModuleB, so you might think you wouldn’t write this kind of code, but what if you changed the macro condition in ModuleA to a macro from a PCH file? That’s when it’s harder to detect.

So the problem here, in fact leads to a conclusion, that is the header files do not rely on any components inside himself did not show the import, the import in other documents (other components) before their macro definition, because the code we’re going to have a certain robustness, regardless of whether or not open modular, should have the execution of the same effect.

Macro variability

In fact, if macros are classified in a general direction, they can be divided into mutable and immutable. Immutable macros are more common, more general, and cause fewer problems. And variable macro generally used in the switch of environment, some special popular third-party libraries considering the compatibility and the need to add some precompiled macros, these, in fact, from the user perspective, it should be regarded as an immutable macros, because and immutability is variable from the user, rather than from the provider.

So what are the problems with mutable macros? Because this is in accordance with the before and after a binary binary to differentiate between the present, so here before, only in binary derivation variable macro will bring some compilation performance problems here, because of its variability caused part of the cache invalidation and trigger recompilation, but did not bring the impact of the results.

Problems with binarization

The problems that have been described before do not mean that there are no problems with binarization. In fact, it is cumulative, and binarization is the basis for the use of macros more stringent conditions, we continue.

The disappearance of the macro

This is clear from the title, because once binalized, the macro in the component is replaced because it has been compiled, and by vanishing I mean the compiled implementation file, not the finger file. If the macro is immutable, then the disappearance of the binary macro is not a problem. The problem is the mutable macro, because it has been replaced, so it does not respond to any future changes to the mutable macro, but can only be recompiled.

We can deal with this problem in two ways:

  1. Methods use macros internally

    In this case, the binary disappear after macro problem, we can provide a always won’t be the source of the binary components (macro components), the required conditions determine macro outward (that is, to the binary component party) provide a method or function interface, and the macro components inside the function or method by judging the macro to return data, roughly as follows:

    // Macro/macro.h + // ModuleA internal - (void) XXXX {#if DEBUG // code #else // code #endif} // Macro // macro.h + (BOOL)isDebug; // Macro.m + (BOOL)isDebug { #if DEBUG return YES; #else return NO; #endif} // ModuleA inside - (void) XXXX {if ([Macro isDebug]){// code} else {// code}}Copy the code
  2. Method to use macros externally

    For macros used outside the method, the solution is to refactor them into macros used inside the method, for example:

    #if DEBUG NSString *const BaseAPI = @"<https://xxx>"; #else NSString *const BaseAPI = @"<https://xxx>"; - (NSString *)baseAPI {#if DEBUG return @"<https://xxx>"; #else return @"<https://xxx>"; #endif }Copy the code

How to use macros safely

Here are some suggestions on how to use macros more safely:

  1. Macros exported from components and macros imported from external components should be avoided as far as possible. Mutable macros should not be mentioned, and must not be imported or exported. Immutable macros can be used appropriately if the stability of their macro interface can be guaranteed, such as macros that handle closure loop references.
  2. Subject to article 1, do not implicitly rely on macros from external components, but should be imported explicitly, otherwise there will be dependency import order and macros disappear after modularization.
  3. In the case of clause 2, do not have two components loop dependent (this clause is really a componentized split problem).

conclusion

Due to my limited level, I hope to be pointed out if there are any errors found, and I also hope that these contents can be useful to students who need them.