Dont Put Static Var In Header Files

| /

I helped a colleague troubleshoot an issue last week. The culprit is a static variable in a header file.

Strictly speaking, this should belong to the knowledge of the C language, not iOS-specific.

Our company uses a revamped cocoapods-binary to manage individual components.

When we need to upgrade components, we usually generate source and binary components. When integrated into the main APP, binary integration is used. Of course, in the testing phase, source code integration will also be used.

We have a base component on which other business components depend.

flowchart LR
  A[Base_A]--> B[Componet_B]
  A--> C[Component_C]

  B--> D(MainApp)
  C--> D
  A--> D

The requirement is to modify a routing address, which is configured in the basic component.

The colleague said that the basic components have been upgraded and reintegrated into the main app, but the phenomenon does not seem to take effect. That is, the jump still uses the old routing address.

Because my colleagues took a long time to troubleshoot the problem, and even suspected that the integration tool was in addition to the problem, I also participated in and assisted the investigation.

Just modify a config, there shouldn’t be any weird issues.

I checked the code and found that this configuration is actually a static variable in the header file.

The actual use of this configuration is in several other business components. as follows:

1
2
3
4
5
6
7
8
9
10
// base_a.h
static NSString * const url = @"url";

// component_b.m
#import "<base_a/base_a.h>"
[Router openUrl:url];

// component_c.m
#import "<base_a/base_a.h>"
[Router openUrl:url];

diff:

1
2
3
// base_a.h
- static NSString * const url = @"url";
+ static NSString * const url = @"url2";

When the B and C components previously built their own binary artifacts, the value of the url was already determined.

The static modification in the header file of the A component will not affect the already constructed products of the B and C components.

So this is why there are the following two phenomena:

  • Relying on the latest basic component A, rebuilding B and C and re-integrating it will take effect.
  • Using the source code integration method to integrate A, B and C at the same time can also take effect.

A static variable is local to a compilation unit, and a compilation unit is a .cpp or .c or .m file and contains the contents of a header file that is imported using #include.

For knowledge about compilation and pre-compilation, please refer to the description in CSAAP

Preprocessing phase.
The preprocessor (cpp) modifies the original C program according to directives that begin with the # character.
For example, the #include <stdio.h> command in line 1 of hello.c tells the preprocessor to read the contents of the system header file stdio.h and insert it directlyinto the program text.
The result is another C program, typically with the .i suffix.

The correct thing to do is to use extern instead of static to handle this situation. as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
// base_a.h
extern NSString * const url;

// base_a.m
NSString * const url = @"url2";

// component_b.m
#import "<base_a/base_a.h>"
[Router openUrl:url];

// component_c.m
#import "<base_a/base_a.h>"
[Router openUrl:url];

extern variables are stored in the data segment. The extern modifier tells the compiler that a different compilation unit is actually declaring the variable, so don’t create another instance of it or there will be a name collision at link time.