iOS原理/面试

iOS常量使用extern和static原理探究,为什么Defi

2018-12-04  本文已影响30人  LuckyCat_A



一、场景需求

我们在项目架构中,每个模块下都会需要一个相关定义文件。例如:注册、登录、忘记密码为一个Account模块,模块下的枚举、宏定义、通知、数据存储 Key 等都放在一个EHIAccountDefines文件中。对于宏定义、通知、数据存储 Key 等常量的定义使用我们来讨论一下。

二、使用方法

2.1 宏定义

// EHIAccountDefines.h
#define kEHIChangedPasswordNotification @"UserChangedPassword";

最省事的方式就是宏定义了,但是宏定义本质只是文本替换。在预编译器影响速度、不能声明变量类型、不能使用 const 修饰导致使用中值可以更改,所以常量的定义不推荐。一般用于方法的定义(方法也可以使用内联函数inline代替一部分,具体使用不多赘述)。

2.2 static

// EHIAccountDefines.h
static NSString *const kEHILogoutNotification = @"UserDidLogout";

这种写法也是很常见的,不止是在这个场景,也有很多用在.m中定义一个动画时长、cell复用池名称的定义等。

2.3 extern

// EHIAccountDefines.h
extern NSString *const kEHILoginNotification;

// EHIAccountDefines.m
NSString *const kEHILoginNotification = @"UserDidLogin";

排除#define的写法。extern是各个博客和书上的默认写法,没有太多对此的解释,好像理应如此。而实际上static的话只需要.h文件即可,更加方便。所以这次探究一下为什么还都默认使用extern呢?

三、探究

3.1 举个例子

.pch引用的全局的一个Defines文件中写下:

// .h
extern NSString *const kEHIConstExternStr;

static NSString *const kEHIConstStaticStr = @"一个静态字符串";
// .m
NSString *const kEHIConstExternStr = @"一个外部字符串";

在几个ViewControllerviewDidLoad中使用这两个字符串(注意是分别几个文件中写,不是基类):

NSLog(@"%@ %@:%p, %x", self.className, kEHIConstExternStr, kEHIConstExternStr, &kEHIConstExternStr);
NSLog(@"%@ %@:%p, %x", self.className, kEHIConstStaticStr, kEHIConstStaticStr, &kEHIConstStaticStr);

%p打印的是这个字符串的值的地址,后面%x是存放这个变量的地址。
只有值类型是直接放的值,引用类型放的都是地址。字符串是引用类型,故而它地址存放的是它的值地址。

3.2 输出结果

[1982:519479] AppDelegate 一个外部字符串:0x106715780, 6692cd8
[1982:519479] AppDelegate 一个静态字符串:0x1066e2020, 6669410

[1982:519479] EHIHomeContantViewController 一个外部字符串:0x106715780, 6692cd8
[1982:519479] EHIHomeContantViewController 一个静态字符串:0x1066e2020, 667b080

[1982:519479] EHIHomeViewController 一个外部字符串:0x106715780, 6692cd8
[1982:519479] EHIHomeViewController 一个静态字符串:0x1066e2020, 667d530

查看输出结果,可以得知:

extern:变量地址和值地址都是同一个。
static:值地址是同一个,但是变量地址是不同的。

那么 static 在所有地方引用,是在所有地方都分配一个变量地址呢,还是只有使用到才会分配呢?反编译查看:

static NSString *const kShowGuideKey = @"kShowGuideKey01";

static 的变量地址是变的,但是在用到的地方才会创建。三个文件里有用到,就会自动生成三个变量,分别是_kShowGuideKey_kShowGuideKey_101980440_kShowGuideKey_1019949c0,都指向同一个字符串@“kShowGuideKey01”值。
编译的时候第一次用到是原名加下划线前缀,只有再有用到会追加地址最为后缀区分。

extern 的不管多少个地方用到,编译时只有一个下划线前缀的变量,因为 extern 的只有一个.m有这个定义。

3.3 分析

extern:全局只有一个变量,对一个常量字符串的引用,只会被初始化一次。
static:全局有多个变量,每个引用的地方的变量都会初始化一次,分配内存空间,对一个常量字符串的引用(所以很多博客和书根本就没提这样写)。

3.3.1 static 的原理

而为什么会有这个结论呢?static 修饰的变量为什么多次分配内存呢?

上面的使用,如果既不用 extern 修饰,也不用 static 修饰呢?答案是报错,因为重复定义了。
我们先了解下 .h 的作用:

.h文件可以说没有用,只是给外部看的。h中是实际上无法定义变量的,编译.m的时候会把.h中的内容在.m中展开后编译才会真正的生成一个变量。

所以,没有任何修饰符的话相当于多个类中都定义了同一个变量名,就重复定义报错了。

然而,这里加上static的话,就不会报错,只是每个文件使用的变量不一样。也就是说 static 限制了该变量作用域在每个使用它的文件内。

我们可以得到结论:

static:限制作用域。

(这里就不扩展说它修饰局部变量、全局变量、函数的作用了~~~ )

3.3.2 extern 的原理

接下来,为什么 extern 可以保持变量和常量一致呢?

我们看个例子,日常开发中在 .m 中使用 static 的情况很多,例如:

static NSString *const kEHIShowGuideKey = @"EHIShowGuideKey";

如果不加 static 修饰符呢?

// ClassA.m
NSString *const kEHIShowGuideKey = @"EHIShowGuideKey";

系统会自动给我们加上 extern 修饰为外部变量。按照上面说的 static 限制作用域的原理,我们在其他地方就不能重复定义这个变量名称了。但是它现在是一个外部变量,我们可以在其它地方使用这个变量。

如果其他地方使用,我们可以这么用:

// ClassB.m
extern NSString *const kEHIShowGuideKey;

ClassB 在使用时,声明我在使用一个外部变量。但是这样就要每个使用该字符串的时候都去声明下。

所以常规的写法还是如下:

// ClassA.h
extern NSString *const kEHIShowGuideKey;

// ClassA.m
NSString *const kEHIShowGuideKey = @"EHIShowGuideKey";

我们可以得到结论:

extern:外部的,作用就是修饰后面的内容是由外部定义的而已。

四、总结

终于把思路理清楚写完了!😭

了解了原理再看各种情况就清晰明了了。还没有一下就明白的小伙伴自己写写例子、消化下哈。

上一篇 下一篇

猜你喜欢

热点阅读