Const/Static/Extern的区别
参照以往的定义
const的作用:
- const仅仅用来修饰右边的变量(基本数据变量p,指针变量*p)。
- 被const修饰的变量是只读的。
static的作用
- 延长变量的生命周期,程序结束才会销毁。
- 在同一作用域或文件中该语句只会在编译时被执行一次(初始化一次),无法用断点捕捉。
- 改变变量的作用域。
针对不同变量
- Static局部变量:
在当前的”{}”内部有且仅有一份,且不会随着函数结束而被释放(作用1),函数多次被调用,始终保持同一内存地址同一值(作用2).
- 修饰全局变量
在当前文件中只有一份(作用1),即使声明在.h中,多个.m加载同一个.h,变量名称一样,但是实际地址是独立的(作用3),每个独立的变量多次调用始终保持同一地址(作用2)
extern的作用
- 只是用来获取全局变量(包括全局静态变量)的值,不能用于定义变量
- extern工作原理:先在当前文件查找有没有全局变量,没有找到,才会去其他文件查找。
Const/Static/Extern的常用方法
static常用于TableViewCell的Identifer
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = @"cell";//使用静态避免多次初始化
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
return cell;
}
const和extern常用于全局的常量字符串,比如全局的通知字符串,其中UIKIT_EXTERN等于extern,如果不加UIKIT_EXTERN,需要加载定义的头文件,如果加了,只需要加载UIKit库就可以使用,不需要特意加载头文件
//.h
UIKIT_EXTERN NSString *const CallBackNotification;
//.m
NSString *const CallBackNotification = @"CallBackNotification";
Xcode7.2中Static NSString的研究
结论:Xcode7.2中对字符串的处理static已经不用特意标记了,已经被默认.只要是通过@””创建的字符串,只要内容一样,都指向同一个内存.
static测试
根据以上概念,static修饰于局部变量的时候,会延长局部变量的生命周期,到底是延长多久呢?于是用以下代码测试.
//viewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
for (int i = 0; i < 10; ++i) {
[self printValue];
}
NSLog(@"weakPoint = %p",_otherWeakClass.weakPoint);
}
- (void)printValue {
static NSString *tempStr = @"xxx";
if (!_otherWeakClass.point) {
_otherWeakClass.point = tempStr;
NSLog(@"weakPoint get value");
}
NSLog(@"%p ==== %@,weakPoint = %p",tempStr,tempStr,_otherWeakClass.weakPoint);
}
- (void)dealloc {
NSLog(@"ViewController did dealloc %p",self);
}
//otherWeakClass.m
- (void)touchUpInsidePrintBtn:(UIButton *)button {
NSLog(@"_waekPoint = %p,value = %@",_waekPoint,_waekPoint);
}
在以上测试中,用一个ViewController中声明的static的字符串,然后用另外一个类的weakPoint指针指向它.在不同的地方打印字符串本身或者weakPoint.
然后奇怪的事情发生了( ̄ε(# ̄) ,和教科书上讲的不一样
无论何时打印weakPoint,总能得到和tempStr一样的值和地址,也就是说内存始终没有被释放,检查weakPoint指向内存的retainCount,得到的是18446744073709551615,也就是(2^64-1).这是什么鬼>﹏<
2016-01-11 14:43:16.835 Learn[24725:3132909] weakPoint get value
2016-01-11 14:43:20.541 Learn[24725:3132909] 0x10b36e620 ==== xxx,weakPoint = 0x10b36e620
2016-01-11 14:43:20.542 Learn[24725:3132909] 0x10b36e620 ==== xxx,weakPoint = 0x10b36e620
2016-01-11 14:43:20.542 Learn[24725:3132909] 0x10b36e620 ==== xxx,weakPoint = 0x10b36e620
.......
2016-01-11 14:43:20.543 Learn[24725:3132909] 0x10b36e620 ==== xxx,weakPoint = 0x10b36e620
2016-01-11 14:43:20.543 Learn[24725:3132909] weakPoint = 0x10b36e620,value = xxx
为了验证教科书是否正确,用以下代码,代替了Static那一行,按说应该10次输出10个不同的地址
NSString *tempStr = @"xxx";
结果呢…..让我思密达了,输出还是一模一样的….地址仍然没有变,那我再换一种呢
NSString *tempStr = [NSString stringWithFormat:@"%@",@"xxx"];
这样总是重新初始化了吧…结果(´・ω・`)还是没变…难道Xcode坏掉了
NSString *str1 = @"xxx";
NSString *str2 = @"xxx";
NSLog(@"str1 address = %p, str2 address = %p",str1,str2);
于是写下这样的基本代码来测试…发现str1和str2地址还是一样的….(°ー°〃=)>书本上写的都喂狗了么….为什么会这样
static分析
于是打上断点,在控制台里,分别用print和print object指令打印不同的字符串,看看有什么不同
static NSString *tempStr = @"xxx";//__NSCFConstantString
NSString *tempStr = @"xxx";//__NSCFConstantString
NSString *tempStr = [NSString stringWithFormat:@"%@",@"xxx"];//NSTaggedPointerString
发现凡是通过@””创建的都是__NSCFConstantString的字符串,static根本就没鸟用啊…但是通过stringWithFormat创建的就是NSTaggedPointerString.这两个有啥差别呢
差别
- __NSCFConstantString
字符串常量,是一种编译时常量,它的 retainCount 值很大,是 4294967295,在控制台打印出的数值则是 18446744073709551615==2^64-1,测试证明,即便对其进行 release 操作,retainCount 也不会产生任何变化。是创建之后便是放不掉的对象。相同内容的 __NSCFConstantString 对象的地址相同,也就是说常量字符串对象是一种单例。
这种对象一般通过字面值 @”…”、CFSTR(“…”) 或者 stringWithString: 方法(需要说明的是,这个方法在 iOS6 SDK 中已经被称为redundant,使用这个方法会产生一条编译器警告。这个方法等同于字面值创建的方法)产生。
这种对象存储在字符串常量区。
- __NSTaggedPointerString
这个类型是标签指针字符串,这是苹果在 64 位环境下对 NSString,NSNumber 等对象做的一些优化。简单来讲可以理解为把指针指向的内容直接放在了指针变量的内存地址中,因为在 64 位环境下指针变量的大小达到了 8 位足以容纳一些长度较小的内容。于是使用了标签指针这种方式来优化数据的存储方式。从他的引用计数可以看出,这货也是一个释放不掉的单例常量对象。在运行时根据实际情况创建。
对于 NSString 对象来讲,当非字面值常量的数字,英文字母字符串的长度小于等于 9 的时候会自动成为 NSTaggedPointerString 类型.
如果有中文或其他特殊符号(可能是非 ASCII 字符)存在的话则会直接成为 )__NSCFString 类型。
这种对象被直接存储在指针的内容中,可以当作一种伪对象。
- __NSCFString
这个才是教科书上教的传统的字符串,和 __NSCFConstantString 不同, __NSCFString 对象是在运行时创建的一种 NSString 子类,他并不是一种字符串常量。所以和其他的对象一样在被创建时获得了 1 的引用计数。
通过 NSString 的 stringWithFormat等方法创建的中文NSString对象一般都是这种类型。
这种对象被存储在堆上。
教科书回来了
如果按照以上分析,把字符串改为中文,并且用初始化的方法进行生成,打印结果就是10个不同的地址了.
- (void)printValue {
NSString *tempStr = [NSString stringWithFormat:@"%@",@"哈哈"];
if (!_otherWeakClass.point) {
_otherWeakClass.point = tempStr;
NSLog(@"weakPoint get value");
}
NSLog(@"%p ==== %@,weakPoint = %p",tempStr,tempStr,_otherWeakClass.weakPoint);
}
如果这个时候我在初始化方法前加入Static行不行
static NSString *tempStr = [NSString stringWithFormat:@"%@",@"哈哈"];
这个时候编译器会提示错误:
Initializer element is not a compile-time constant
初始化方法不是一个编译时的常量,也就是说static必须针对一个可以编译时就确定的变量,而不是运行时动态生成的.这也是为什么很少见UIView前加入static的原因.
非字符串的Static变量(运行时)
运行时static的运用复测
如果static变量声明时必须是complie-time(编译时)的,是不是就无法使用运行时的变量了?其实也是可以的,只要在编译时给予static变量一个固定值就可以了
{
static UIView *view = nil;//编译时给予空指针,确定指针的地址
view = [[UIView alloc]init];//运行时进行初始化,改变指针指向的内容
}
如此就延长了view的指针自身地址的生命周期,但是其指向的内容是可以改变的.即使多次进入代码片段view指向的Object也不会被释放.有人会有疑问,多次进入代码static那一行赋值不会再次赋值么?就如同编译器提示的一样,static是编译时语句,只有编译时被执行,运行时不会被执行,所以不会赋值nil多次
运行时static作用域复测
局部变量
如果在同一个文件不同的函数中(局部变量),仅仅是针对该局部延长了生命周期,即使两个函数中的static变量都叫view,两个view并不在同一个地址,是两个独立的Object
- (void)functionOne {
static UIView *view = nil;
view = [[UIView alloc]init];
[view setBackgroundColor:[UIColor red]];
}
- (void)functionTwo {
static UIView *view = nil;//编译时给予空指针,确定指针的地址
view = [[UIView alloc]init];//运行时进行初始化,改变指针指向的内容
[view setBackgroundColor:[UIColor blue]];
}
全局变量
在不同的.m文件中,由于.m之间是互相不可见的,所以static限定作用域的作用就基本无用了,和普通的全局变量一样.
static UIView *_view = nil;
@implementation Class
- (void)functionOne {
_view = [[UIView alloc]init];
}
@end
但是如果在.h文件中生命static的变量
//Class.h
static UIView *_view = nil;
@interface Class
- (void)functionOne;
@end
然后不同的点.m文件加载同一个.h
//Class1.m
#import "Class.h"
//Class2.m
#import "Class.h"
此时Class1.m中的全局变量_view和Class2.h中的全局变量_view不为同一个地址,两个的初始化是独立的.
局部static和全局static混用(作死小能手)
同时在全局和局部声明static
static UIView *_view = nil;
@implementation Class
- (void)functionOne {
NSLog(@"%@",_view);//输出为nil
_view = [[UIView alloc]init];
NSLog(@"%@",_view);//输出为地址1
static UIView *_view = nil;//由于运行时不会执行,所以仍为全局的_view,地址1
NSLog(@"%@",_view);//输出仍为地址1
}
@end
全局声明static,局部声明同名临时变量.
static UIView *_view = nil;
@implementation Class
- (void)functionOne {
NSLog(@"%@",_view);//输出为nil
_view = [[UIView alloc]init];
NSLog(@"%@",_view);//输出为地址1
UIView *_view = nil;//由于运行时执行,所以从此行往下都是局部变量_view,往上的_view则为全局变量
NSLog(@"%@",_view);//输出为nil,打印的为局部变量
}
@end