参考:C Storage Classes
iOS定义静态变量、静态常量、全局变量
iOS开发——OC篇&常用关键字的使用与区别
iOS 宏(define)与常量(const)的正确使用
一、C语言的存储类型
在C语言中,程序内变量或函数的作用域和寿命是由其存储类确定的。每个变量都具有生存周期,或存储其值得上下文。方法,同变量一样,也存在,或可见于,一个特殊的范围里,这就决定了哪一部分能够知道且能够访问它们。
C里有四种存储类:
- auto
- register
- static
- extern
1、auto
很有可能你从来没见过这个关键字。这是因为auto是默认存储类,因此通常并不需要显式的使用。
当运行到程序块的时候,auto类型的变量能自动分配内存,并且在该程序块运行完成时释放。 访问auto变量仅限于声明它的block,以及任何嵌套block内。
2、register
大多数OC程序员可能也不熟悉register,因为它没有被广泛的使用在NS世界里。
register行为就像auto,但不同的是它们不是被分配到堆栈中,它们被存储在一个寄存器里。
寄存器能比内存提供更快的访问速度,但由于内存管理的复杂性,把变量放在寄存器中并不能保证程序变得更快。事实上,很可能由于在寄存器上占用了不必要的空间而最终被放缓执行。使用寄存器实际上只是一个给编译器存储变量的建议,实现时可以选择是否遵从这一点。
寄存器在OC不够普及其实挺好的:最好还是不要使用它,因为比起其他任何明显的方式加快应用程序,它更容易引起让人更加头疼的结果。
3、static
作为关键字,static被以很多不同的,不兼容的方式使用,因此要弄清楚每一个实例到底是什么意思可能会造成混淆。
方法或函数内部的一个static变量保留其调用之前的值。
-
全局声明的一个static变量可以被任何函数或方法调用,只要这些方法出现在跟static变量同一个文件中。这同样适用于static方法。
静态单例
OC中一个常见的模式是静态单例,在这个case里,一个静态声明的变量被初始化,并在任何一个函数或者类方法中被返回。 dispatch once 用于保证变量初始化在一个线程安全的方式下 只 发生一次:
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
单例模式对于创建整个应用程序共享的对象是很有用的,诸如HTTP客户端或一个通知管理,或创建过程很昂贵的对象,诸如格式化。
4、extern
当static使得一个特定的文件中的函数和变量全局可见,extern则使它们对所有文件可见。
一般来说,全局变量并不是一个好主意。由于没有如何及何时改变只的任何对extern有两个常见和实际的用途。
全局字符串常量
任何时候,如果你的应用程序要在一个公共文件头部申明一个非自然语言的字符串常量,都应该将其声明为外部字符串常量。尤其是在声明诸如userInfo字典,NSNotification 名称和 NSError 域的时候。
该模式是在公共头文件里申明一个extern的NSSting*const,并在实现文件里定义该NSString * const:
//AppDelegate.h
extern NSString * const kAppErrorDomain;
//AppDelegate.m
NSString * const kAppErrorDomain = @"com.example.yourapp.error";
字符串的值并没有特别需要注意的事情,只要它是唯一的。使用字符串常量建立了严格的约束,用该常数变量来代替字符串文本值本身。
公共方法
一些API可能会想要公开曝光一些辅助方法。由于仅提供辅助而与具体状态无关的考虑,用方法来封装这些行为是一个很好的方式,而且如果特别有用,还可能值得使其全局可用。
//TransactionStateMachine.h
typedef NS_ENUM(NSUInteger, TransactionState) { TransactionOpened, TransactionPending, TransactionClosed,};extern NSString * NSStringFromTransactionState(TransactionState state);
//TransactionStateMachine.m
NSString * NSStringFromTransactionState(TransactionState state) { switch (state) { case TransactionOpened: return @"Opened"; case TransactionPending: return @"Pending"; case TransactionClosed: return @"Closed"; default: return nil; }}
理解任何事情其实都是去了解上下文。可能那些我们看到的很明显且不证自明的东西,对所有那些没有我们的参照系的人来说是未知的。我们无法真正了解和欣赏自己和他人的观点及信息的差异或许使我们最根本的缺点。
这就是为什么,在我们构建的逻辑0和1的宇宙中,我们如此谨慎的区分上下文,并基于这些明确的规则上构建我们的假设。C存储类对于理解程序是如何运行时必不可少的。如果没有他们,我们的开发将如履薄冰。因此,需要谨慎对待这些简单的规则,才能包含新新的编写代码。
二、iOS的内存存储区的划分
- 1、栈区:栈区主要存放函数内部定义的变量、数组。函数调用时,开辟空间,函数执行完毕,回收空间,空间的开辟与回收有系统管理。
- 2、堆区:堆区最大的特点:空间的开辟与释放由开发人员手动管理。
- 3、全局静态区:主要存放函数外部定义的全局变量以及静态变量,空间一旦开辟,就不会回收,直到应用程序执行结束。
- 4、常量区:存储常量:整形常量、浮点型常量、字符串常量
- 5、代码区:存放程序编译之后生成的cpu指令。
三、OC中的一些关键字
1、static
1.1 static变量只是在编译时候进行初始化,对于static变量,无论是定义在方法体里面还是在方法体外面其作用域都一样。
我们经常使用的UItableViewController里面,在定义UItableView的时候,模板会经常使用以下代码
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
在上面定义了static变量,在编译期间会对这个变量进行初始化赋值,也就是说这个变量值要么为nil,要么在编译期就可以确定其值,一般情况下,只能用NSSrting或者基本类型,并且这个变量只能在cellForRowAtIndexPath访问。这个和C语言里面的static的变量属性一样。
1.2 static变量也可以定义在.m的方法体外。这样所有的方法内部都可以访问这个变量。但是在类之外是没有办法访问的,也就是不能用 XXXClass.staticVar 的方式来访问 staticVar变量。相当于static变量都是私有的。
如果.m文件和方法体里面定义了同名的static变量,那么方法体里面的实例变量和全局的static变量不会冲突,在方法体内部访问的static变量和全局的static变量是不同的。
@implementation IFoundAppDelegate
static NSString * staticStr = @"test";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
static NSString * staticStr = @"test2";
NSLog(@"the staticStr is %@ -- %d",staticStr,[staticStr hash]);
}
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(@"the staticStr is %@ -- %d",staticStr,[staticStr hash]);
}
以上两个static变量是两个不同的变量,在didFinishLaunchingWithOptions方法内部,访问的是方法体内部定义的staticStr变量,在applicationWillResignActive方法体里面,访问的是全局定义的staticStr变量。可以通过日志打印其hash来进行确认两个变量是否一样。
2、const和define
2.1、当我们想全局共用一些数据时,可以用宏、变量、常量
宏:
#define HSCoder @"汉斯哈哈哈"
变量:
NSString *HSCoder = @"汉斯哈哈哈";
常量:
//四种写法:
static const NSString *HSCoder = @"汉斯哈哈哈";
const NSString *HSCoder = @"汉斯哈哈哈";
NSString const *HSCoder = @"汉斯哈哈哈";
NSString * const HSCoder = @"汉斯哈哈哈";
思考:宏与常/变量的选择?
- 宏:只是预处理器里进行文本替换,没有类型,不做任何类型检查,编译器可以对相同的字符串进行优化。只保存一份到 .rodata 段。甚至有相同后缀的字符串也可以优化,你可以用GCC 编译测试,"Hello world" 与 "world" 两个字符串,只存储前面一个。取的时候只需要给前面和中间的地址,如果是整形、浮点型会有多份拷贝,但这些数写在指令中。占的只是代码段而已,大量用宏会导致二进制文件变大。
- 变量:共享一块内存空间,就算项目中N处用到,也不会分配N块内存空间,可以被修改,在编译阶段会执行类型检查。
- 常量:共享一块内存空间,就算项目中N处用到,也不会分配N块内存空间,可以根据const修饰的位置设定能否修改,在编译阶段会执行类型检查。
2.2 常量区分
全局常量: 不管你定义在任何文件夹,外部都能访问。
const NSString *HSCoder = @"汉斯哈哈哈";
局部常量:用static修饰后,不能提供外界访问
static const NSString *HSCoder = @"汉斯哈哈哈";
ps :static经常用到.m文件中,如果是.h文件定义的全局变量,在别的文件中,引入这个头文件,也是可以访问这个变量的。
2.3const修饰位置不同,代表什么?
1.const NSString *HSCoder = @"汉斯哈哈哈";
"*HSCoder"不能被修改, "HSCoder"能被修改
2.NSString const *HSCoder = @"汉斯哈哈哈";
"*HSCoder"不能被修改, "HSCoder"能被修改
3.NSString * const HSCoder = @"汉斯哈哈哈";
"HSCoder"不能被修改,"*HSCoder"能被修改
结论:const右边的总不能被修改
验证:
所以一般我们定义一个常量又不想被修改应该这样:
NSString * const HSCoder = @"汉斯哈哈哈";
一般项目中,定义全局常量,会写在独立文件中
访问:
导入头文件
访问常量