iOS-MRC与ARC区别以及五大内存区
技 术 文 章 / 超 人
个人觉得要更加深入直观了解MRC与ARC的区别建议先从内存分析开始所以文章开始会从内存说起
文章目录
- 1.五大内存区域
- 1.1 栈区
- 1.2 堆区
- 1.3 全局区
- 1.4 常量区
- 1.5 代码区
- 1.6 自由存储区
- 1.7 static静态变量
- 1.8 extern全局变量
- 1.9 const常量
- 2.属性标识符
- 2.1 @property、@synthesize、@dynamic
- 2.2 nonatomic与atomic
- 2.3 strong、weak、retain、assgin、copy、unsafe_unretained
- 2.4 readOnly、readWrite、getter=、setter=
- 2.5 __unsafe_unretained、__weak、__strong
- 3.MRC与ARC区别
- 3.1 MRC手动内存管理
- 3.2 ARC自动内存管理
- 3.3 autoreleasepool自动释放池
- 4.NSString单独说
1.五大内存区域
栈区,堆区,全局区,常量区,代码区
五大内存区域之外还有 自由存储区也称之五大区域之外区
-
1.1栈区:
创建临时变量时由编译器自动分配,在不需要的时候自动清除的变量的存储区
。
里面的变量通常是局部变量
、函数参数
等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。
@interface TestObject()
@end
@implementation TestObject
- (void)testMethodWithName:(NSString *)name
{
//方法参数name是一个指针,指向传入的参数指针所指向的对象内存地址。name是在栈中
//通过打印地址可以看出来,传入参数的对象内存地址与方法参数的对象内存地址是一样的。但是指针地址不一样。
NSLog(@"name指针地址:%p,name指针指向的对象内存地址:%p",&name,name);
//*person 是指针变量,在栈中, [Person new]是创建的对象,放在堆中。
//person指针指向了[Person new]所创建的对象。
//那么[Person new]所创建的对象的引用计数器就被+1了,此时[Person new]对象的retainCount为1
Person *person = [Person new];
}
-
1.2堆区:
就是那些由 new alloc 创建的对象所分配的内存块,它们的释放系统不会主动去管,由我们的开发者去告诉系统什么时候释放这块内存(一个对象引用计数为0是系统就会回销毁该内存区域对象)。一般一个 new 就要对应一个 release。在ARC下编译器会自动在合适位置为OC对象添加release操作。会在当前线程Runloop退出或休眠时销毁这些对象,MRC则需程序员手动释放。
堆可以动态地扩展和收缩。
//alloc是为Person对象分配内存,init是初始化Person对象。本质上跟[Person new]一样。
Person *person = [[Person alloc] init];
-
1.3全局/静态存储区,
全局变量
和静态变量
被分配到同一块内存中,在以前的 C 语言中,全局变量又分为初始化
的和未初始化
的(初始化的全局变量和静态变量在一块区域,
未初始化的全局变量与静态变量在相邻的另一块区域,
同时未被初始化的对象存储区可以通过 void* 来访问和操纵,
程序结束后由系统自行释放),在 C++ 里面没有这个区分了,
他们共同占用同一块内存区。 -
1.4常量存储区
这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。一般值都是放在这个地方的 -
1.5代码区
存放函数的二进制代码 -
1.6自由存储区,
就是那些由 malloc 等分配的内存块,他和堆是十分相似的,不过它是用 free 来结束自己的生命的。
NSString *string1;//string1 这个NSString 类型的指针,未初始化存在于<全局区>的<BBS区>
NSString *string2 = @"1234";//string2 这个NSString类型的指针,已初始化存在与<全局区>的<data数据区>,@“1234”存在与堆区,因为@代表了对象。
static NSString *string3;//string3 这个NSString 类型的指针存在于<全局区>的<BBS区>
static NSString *string4 = @"1234";//string4 这个NSString类型的指针存在与<全局区>的<data数据区>,@“1234”存在与堆区,因为@代表了对象。stiring2和string4的值地址是一样的
static const NSString *string5 = @"654321";//const 修饰后 string5不能修改值。 其他的与string4一样
- (void)test
{
int a;//a这个int类型的变量 是存在与<栈区>的
a = 10;//10这个值是存在与 <常量区>的
NSStirng *str;//str这个NSString类型的指针 存在于<栈区>
str = @“1234”;//@“1234”这个@对象存在于 <堆区>
static NSString *str1;//str1这个NSString类型的指针 存在于<全局区>的<BBS区>
static NSString *str2 = @"4321';//str2这个NSString类型的指针 存在于<全局区>的<data区>
NSString *str3;//str3这个NSString类型的指针 存在于<栈区>
str3 = [[NSString alloc]initWithString:@"1234"];//[[NSString alloc]initWithString:@"1234"]这个对象 存在于<堆区>
}
1.7 static静态变量
静态变量有两种
- 全局静态变量
优点
:不管对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会指向固定的指针地址,供所有对象使用,节省空间。
缺点
:存在的生命周期长,从定义直到程序结束。
建议
:从内存优化和程序编译的角度来说,尽量少用全局静态变量,因为存在的声明周期长,一直占用空间。程序运行时会单独加载一次全局静态变量,过多的全局静态变量会造成程序启动慢,当然就目前的手机处理器性能,几十几百个估计也影响不大吧。 - 局部静态变量
优点
:定义后只会存在一份值,每次调用都是使用的同一个对象内存地址的值,并没有重新创建,节省空间,只能在该局部代码块中使用。
缺点
:存在的生命周期长,从定义直到程序结束,只能在该局部代码块中使用。
建议
:局部和全局静态变量从本根意义上没有什么区别,只是作用域不同而已。如果值仅一个类中的对象和类方法使用并且值可变,可以定义全局静态变量,如果是多个类使用并可变,建议值定义在model作为成员变量使用。如果是不可变值,建议使用宏定义
static NSString *name;
1.8 extern全局变量
全局变量有两种
- 对内的全局变量:没有用extern在.h中修饰的变量,仅定义在.m中让该变量只能在该类使用
优点
:不管对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会存一份值,供所有对象使用,节省空间。跟全局静态变量一样,只是少了static修饰少了static特性
缺点
:存在的生命周期长,从定义直到程序结束
建议
:跟全局静态变量都一样了,还需要用对内的全局变量吗?不用extern修饰就少了extern的特性,还不如用全局静态变量,至少能明确的知道static是对内使用的 - 外部全局变量:除了该类,其他文件也可以访问该变量
优点
:除了该类,其他文件也可以访问该变量
缺点
:存在的生命周期长,从定义直到程序结束。并且外部可以修改其值,出现错误不容易定位
建议
:使用全局变量的原因就在于其对外的特性,但是其使用的方便性没有使用model的属性或宏来得方便。程序启动的时候会单独加载全局变量,同理与全局静态变量,少使用。
.m中要定义
NSString *name;
.h中同时要定义
extern NSString *name;
全局静态变量与全局变量 其实本质上是没有区别的,只是存在修饰区别,一个static让其只能内部使用,一个extern让其可以外部使用
const常量
不同于变量,常量的值是固定不可变的,一般用于只读值。
优点
:只可以读取值,不能修改。一般用于接口或者文字显示这种固定值。添加extern可以对外全局常量,任意位置都可以访问
缺点
:存在的生命周期长,从定义直到程序结束。需要在.h .m中分别定义代码较多
建议
:看个人习惯吧,使用宏或者常量只是编译加载方式不一样而已
.h中定义extern
extern NSString *const name;
.m中定义值
NSString *const name = @"123";
2.属性标识符
- 2.1 @property、@synthesize、@dynamic
@synthesize:在对象属性使用@synthesize声明的时候编译器会自动为该属性按照固有规则生成相应的getter setter方法。如果有手动生成getter setter方法也不会报错。
@dynamic:相反与@synthesize,使用@dynamic声明时相当于告诉编译器getter setter方法由用户自己生成。如果声明为@dynamic而没有手动生成getter setter方法编译的时候不报错,但是在运行时如果使用.语法
去调用该属性时会崩溃。之所以在运行时才会发生崩溃是因为OC具有动态绑定特性。只有在运行时才会去确认具体的调用方法。
@property:相对于@dynamic 和 @synthesize ,@property声明的作用区域在@interface内部。 它会告诉编译器自动生成getter setter方法。也允许用户手动生成getter setter中的一个方法,用@property声明的属性不能手动同时写getter setter方法,否则编译器会报错。@property更好的声明属性变量。因为访问方法的命名约定,可以很清晰的看出getter和setter的用处,会传递一些额外信息,后面会跟相应的各种信息例如:@property (nonatomic, strong,onlyread) NSString *name;大多数时候都用的@property声明
@interface TestObject()
//因为使用了@property声明,编译器会自动生成相应getter setter方法。
//使用@property不能手动同时生成getter setter方法,编译器会报错
//nonatomic表示属性是非原子操作,strong表示强引用,
//readonly表示该属性权限为仅读,那么编译器只会生成getter方法,不会生成setter方法
@property (nonatomic, strong,readonly) NSString *name;
@property (nonatomic, strong) NSMutableArray *array;
@property (nonatomic, strong) NSMutableDictionary *dic;
@end
@implementation TestObject
//如果上面的array使用的@property声明,而用户又要手动同时生成getter setter方法
//可以使用@synthesize 告诉编译器 该属性getter setter方法如果没有手动声明就自动创建,有就不知道生成。
@synthesize array = _array;
//如果dic用@property声明过了,会自动生成getter setter方法。但是又不希望它自动生成getter setter方法。
//可以用@dynamic 声明。告诉编译器 该属性的getter setter方法不自动生成
@dynamic dic = _dic;
- (void)setArray:(NSMutableArray *)array
{
_array = array;
}
- (NSMutableArray *)array
{
if (!_array) {
_array = [NSMutableArray new];
}
return _array;
}
- (void)setArray:(NSMutableDictionary *)dic
{
_dic = dic;
}
- (NSMutableDictionary *)dic
{
if (!_dic) {
_dic = [NSMutableDictionary new];
}
return _array;
}
@end
- 2.2 nonatomic与atomic
nonatomic(非原子性):在调用用nonatomic声明的对象属性时是非线程安全性的。最为直观的就是NSMutableArray的使用。当同时在子线程去增删数组元素,在主线程中去遍历数组元素就会出现数组越界或者数组没有遍历完。因为采用的nonatomic,不同操作可以同时执行,而不需要等前面的操作完成后在进行下一步操作。所以称之为非线程安全。非原子性的执行效率更高不会阻塞线程
atomic(原子性):相反与非原子性,atomic是具有线程安全性的。他会在getset方法中加入线程操作。每当对用atomic声明的对象属性操作时,会根据操作加入线程的顺序一步一步完成操作,而不是非原子性的同时操作。执行效率较低一般来说很少用atomic。
- 2.3 strong、weak、retain、assgin、copy、unsafe_unretained
retain:释放旧对象,提高输入对象的引用计数+1,将输入对象的值赋值于旧对象,只能用户声明OC对象
@property (nonatomic, retain) Room *room;
- (void)setRoom:(Room *)room // room = r
{
// 只有房间不同才需用release和retain
if (_room != room) {
// 将以前的房间释放掉 -1,将旧对象释放
[_room release];
// MRC中需要手动对房间的引用计数器+1
[room retain];
_room = room;
}
}
strong:强引用,它是ARC特有
。在MRC时代没有,相当于retain。由于MRC时代是靠引用计数器来管理对象什么时候被销毁所以用retain,而ARC时代管理对象的销毁是有系统自动判断,判断的依据就是该对象是否有强引用对象。如果对象没有被任何地方强引用就会被销毁。所以在ARC时代基本都用的strong来声明代替了retain。只能用于声明OC对象(ARC特有)
assgin:简单的赋值操作,不会更改引用计数,用于基本的数据类型声明。
weak:弱引用,表示该属性是一种“非拥有关系”。为这种属性设置新值时既不会保留新值也不会释放旧值,类似于assgin。 然而在对象被摧毁时,属性也会被清空(nil out)。这样可以有效的防止崩溃(因为OC中给没有对象地址的指针发送消息不会崩溃,而给有内存地址但地址中是空对象的指针发消息会崩溃,野指针),该声明必须作用于OC对象。对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。(ARC特有)
copy:不同于其他声明,copy会根据声明的属性是否是可变类型而进行不同操作。如果对象是一个不可变对象,例如NSArray NSString 等,那么copy等同于retain、strong。如果对象是一个可变对象,例如:NSMutableArray,NSMutableString等,它会在内存中重新开辟了一个新的内存空间,用来 存储新的对象,和原来的对象是两个不同的地址,引用计数分别为1. 这就是所谓的深拷贝浅拷贝,浅拷贝只是copy了对象的内存地址,而深拷贝是重新在内存中开辟新空间,新空间对象值与拷贝对象的值一样。但是是完全不同的2个内存地址。 例如copy修饰的类型为 NSString不可变对象时,copy可以保护其封装性,当赋值对象是一个 NSMutableString 类时(NSMutableString是 NSString 的子类,表示一种可修改其值的字符串),此时若是不用copy修饰拷贝字符串,那么赋值该对象之后,赋值对象字符串的值就可能会在其他地方被修改,修改后赋值后对象也会改变,造成值不对。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
unsafe_unretained:和weak 差不多,唯一的区别便是,对象即使被销毁,指针也不会自动置空, 对象被销毁后指针指向的是一个无用的内存地址(野地址)。如果对象销毁后后还使用此指针,程序会抛出 BAD_ACCESS 的异常。 所以一般不使用unsafe_unretained。目前我还未在实际项目中使用过该声明。(ARC特有)
- 2.4 readOnly、readWrite、getter=、setter=
readOnly表示属性仅能读不能设置其值。告诉编译器只生成getter方法不生成setter方法。
readWrite默认值,表示属性可读可写。编译器会自动生成getter setter方法
getter=指定属性gettter方法的方法名
@property (nonatomic, strong, getter=getMyDic) NSMutableDictionary *dic;
setter=指定属性setter方法的方法名
@property (nonatomic, strong,setter=myArray:) NSMutableArray *arr;
2.5__unsafe_unretained、__weak、__strong
__unsafe_unretained
NSMutableArray __unsafe_unretained *array = [[NSMutableArray alloc]init];
[array addObject:@"123"];
使用__unsafe_unretained修饰符的变量与使用__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己持有,所以生成的对象会立即被释放。也就是说在执行完init方法以后,对象指针所指向的内存就已经释放掉了,但因为用的__unsafe_unretained修饰指针并没不像__weak的指针那样,将指针自动置为nil,它依然指向原来的地址,可是这块地址的内存已经被系统回收了,再访问就是非法的,也就是野指针,再执行后面的addObject方法自然会出错了。
__weak
主要用于解决循环引用,用__weak修饰的变量 当对象释放后,指针自动设置为nil,当后面继续使用该指针变量的时候不会造成crash,更不会造成强引用使该释放的对象无法释放,造成内存泄露。
__weak typeof(self) weakSelf = self;
__strong
相反与__weak,主要用于当使用某个对象是,希望它没有提前被释放。强引用该对象使其无法释放。例如在block内部,希望block调用时该对象不会被提前释放造成错误。可以使用强引用。
TestAlertView *alertView = [TestAlertView new];
alertView = ^()
{
//当block内部需要使用本身这个局部对象时,需要用强引用方式,让alertView在传递完block后不会被释放依然可以执行setTitle操作
__strong typeof(alertView) strongAlertView = alertView;
[strongAlertView setTitle:@"1234"];
}
[alertView show];
3 MRC与ARC区别
3.1 MRC手动内存管理
引用计数器:在MRC时代,系统判定一个对象是否销毁是根据这个对象的引用计数器来判断的。
1.每个对象被创建时引用计数都为1
2.每当对象被其他指针引用时,需要手动使用[obj retain];让该对象引用计数+1。
3.当指针变量不在使用这个对象的时候,需要手动释放release这个对象。 让其的引用计数-1.
4.当一个对象的引用计数为0的时候,系统就会销毁这个对象。
NSMutableArray *array = [NSMutableArray array];//[NSMutableArray array]创建后引用计数器为1
NSLog(@"array的对象地址:%p,array的retainCount:%zd",array,[array retainCount]);
[array release];//调用release后[NSMutableArray array]创建的对象引用计数-1.
//当程序执行到[array addObject:@"1234"];这里是就会崩溃。因为此时array指针指向的内存地址中没有任何对象,该指针是一个野指针。
//因为release后[NSMutableArray array]创建的对象引用计数变为了0.系统就会销毁这个内存地址的对象。
[array addObject:@"1234"];
NSLog(@"array的对象地址:%p,array的retainCount:%zd",array,[array retainCount]);
NSLog(@"%@",array);
在MRC模式下必须遵循谁创建,谁释放,谁引用,谁管理
在MRC下使用ARC
在Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入 -fobjc-arc
3.2 ARC自动内存管理
WWDC2011和iOS5所引入自动管理机制——自动引用计数(ARC),它不是垃圾回收机制而是编译器的一种特性。ARC管理机制与MRC手动机制差不多,只是不再需要手动调用retain、release、autorelease;当你使用ARC时,编译器会在在适当位置插入release和autorelease;ARC时代引入了strong强引用来带代替retain,引入了weak弱引用。
在ARC下使用MRC方法
在ARC工程中如果要使用MRC的需要在工程的Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入 -fno-objc-arc
在非MRC文件中无法使用retain release retainCount 方法,无法再dealloc方法中调用[super dealloc];方法
3.3 autoreleasepool自动释放池
自动释放池始于MRC时代,主要是用于 自动 对 释放池内 对象 进行引用计数-1的操作,即自动执行release方法。
在MRC中使用autoreleasepool必须在代码块内部手动为对象调用autorelease把对象加入到的自动释放池,系统会自动在代码块结束后,对加入自动释放池中的对象发送一个release消息。无需手动调用release
int main(int argc, const char * argv[])
{
Person *father = [[Person alloc] init];//引用计数为1
@autoreleasepool {//这里创建自动释放池
int a = 10; // a在栈,10在常量区
int b = 20; //b在栈,20在常量区
// p : 栈
// [[Person alloc] init]创建的对象(引用计数器==1) : 在堆
Person *p = [[Person alloc] init];
//MRC下需要手动让对象加入自动释放池
[p autorelease];
Person *pFather = father;
[father retain];//father指针指向的对象被pFather引用,在MRC下需要手动让被引用对象引用计数+1
NSLog(@"pFather对象内存地址:%p,pFather的引用计数:%zd",pFather,[pFather retainCount]);
NSLog(@"father对象内存地址:%p,father的引用计数:%zd",father,[father retainCount]);
}//这里释放 自动释放池
// 当autoreleasepool内部代码块执行完毕后上面代码块后, 栈里面的变量a、b、p 都会被回收
// 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1。当autoreleasepool代码块执行完毕后,会对释放池内部的所有对象执行一个release消息。如果发送release消息后,对象引用计数为0了,那么就会被系统回收。
NSLog(@"father对象内存地址:%p,father的引用计数:%zd",father,[father retainCount]);
return 0;
}
在ARC中对@autoreleasepool的使用相比MRC不太多。主要用于一些大内存消耗对象的重复创建时,保证内存处于比较优越的状态。常用于创建对象较多的for循环中。在ARC下不要手动的为@autoreleasepool代码块内部对象添加autorelease,ARC下自动的把@autoreleasepool代码块中创建的对象加入了自动释放池中。
for (int i = 0; i < 10000000; i++)
{
@autoreleasepool{
NSMutableArray *array = [NSMutableArray new];
NSMutableDictionary *dic = [NSMutableDictionary new];
NSMutableArray *array1 = [NSMutableArray new];
NSMutableDictionary *dic1 = [NSMutableDictionary new];
NSMutableArray *array2 = [NSMutableArray new];
NSMutableDictionary *dic2 = [NSMutableDictionary new];
NSData *data = UIImageJPEGRepresentation([UIImage imageNamed:@"testimage"], 1);
NSError *error;
NSURL *url = [NSURL URLWithString:@"www.baidu.com"];
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
}
}
使用autoreleasepool时
可以从上图看出,使用autoreleasepool时,内存一直保持稳定状态,上次几乎没浮动
不使用autoreleasepool因为没有使用 autoreleasepool,随着for循环一直执行下去,内存一直在上升。
autorelease对象释放的时机
1.系统自行创建的@autoreleasepool释放时机
线程与Runloop是一对一关系,主线程中会自动创建Runloop,而子线程需要自行调用Runloop来让子线程自动创建Runloop。当@autoreleasepool加入到某个线程时,该线程的Runloop会
借用runloop的Autorelease对象释放的背后的解释(ARC环境下)
图中第1步 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前
图中第6步 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
图中第10 Observer 监视事件是exit(即讲退出runloop),其回调内会调用 _objc_autoreleasePoolpop() 释放自动释放池。
从上面就能看出,Runloop中系统自动创建的@autoreleasepool是在准备进入休眠状态才被销毁的。所以在ARC下,在线程中的临时对象是在当前线程的Runloop进入休眠或者退出loop或者退出线程时被执行release的。
2.自己创建的@autoreleasepool
@autoreleasepool
{//这个{开始创建的自动释放池,这里开始内部的对象自动加入autorelease
}//这个}开始,不必再对 对象加入autorelease,自动释放池被销毁。
从上面就能看出,自行创建的@autoreleasepool,是在}后被释放的,而其中的autorelease对象,也是在这个时候自动执行的release操作。
从 1
, 2
两点可以看出,虽然autorelease对象释放的时机并不都是在代码块结束后就释放。但是他们有一个共同特性,那就是必定是在@autoreleasepool被销毁时,释放的。 所以要清楚autorelease对象什么时候被释放,只需要搞清楚@autoreleasepool什么时候被销毁即可
主线程中既有系统创建的@autoreleasepool也有开发者自行创建的@autoreleasepool。那么他的释放顺序是怎样的呢?
因为@autoreleasepool是以栈的形式存储的,按照先进后出的规则释放栈中每个@autoreleasepool。主线程的@autoreleasepool是在Runloop一开始就创建了所以,它必然是栈最里面的,而自行创建的@autoreleasepool是在Runloop运行中创建的,所以在栈上面一点。按照栈的规则,@autoreleasepool是先释放自行创建的@autoreleasepool,在释放系统创建的。
NSString 单独说
为什么要单独说NSString呢,因为NSString在内存上与其他类型存在很大的不同
//该代码是在MRC环境下测试用
NSString *str1 = @"123456789";//用@""方法创建一个 固定长度为9的字符串
NSString *str2 = @"1234567890";//用@""方法创建一个 固定长度为10的字符串
NSString *str3 = [NSString stringWithFormat:@"234567890"];//用stringWithFormat方法创建一个 固定长度为9的字符串
NSString *str4 = [NSString stringWithFormat:@"2345678901"];//用stringWithFormat方法创建一个 固定长度为10的字符串
NSString *str5 = [[NSString alloc] initWithString:@"345678901"];//用initWithString方法创建一个 固定长度为9的字符串
NSString *str6 = [[NSString alloc] initWithString:@"3456789012"];//用initWithString方法创建一个 固定长度为9的字符串
NSString *str7 = [[NSString alloc] initWithFormat:@"456789012"];//用initWithFormat方法创建一个 固定长度为9的字符串
NSString *str8 = [[NSString alloc] initWithFormat:@"4567890123"];//用initWithFormat方法创建一个 固定长度为9的字符串
NSString *str9 = [NSString stringWithFormat:@"1234567890"];//用stringWithFormat方法创建一个 固定长度为10的字符串并与str2字符串一样的字符串
NSString *str10 = [[NSString alloc] initWithString:@"1234567890"];//用initWithString方法创建一个 固定长度为10的字符串并与str2字符串一样的字符串
NSLog(@"str1 用@"" 的retainCount为:%ld \n 对象内地地址:%p",[str1 retainCount],str1);
NSLog(@"str2 用@"" 的retainCount为:%ld \n 对象内地地址:%p",[str2 retainCount],str2);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str3 用stringWithFormat 的retainCount为:%ld \n 对象内地地址:%p",[str3 retainCount],str3);
NSLog(@"str4 用stringWithFormat 的retainCount为:%ld \n 对象内地地址:%p",[str4 retainCount],str4);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str5 用initWithString 的retainCount为:%ld \n 对象内地地址:%p",[str5 retainCount],str5);
NSLog(@"str6 用initWithString 的retainCount为:%ld \n 对象内地地址:%p",[str6 retainCount],str6);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str7 用initWithFormat 的retainCount为:%ld \n 对象内地地址:%p",[str7 retainCount],str7);
NSLog(@"str8 用initWithFormat 的retainCount为:%ld \n 对象内地地址:%p",[str8 retainCount],str8);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"使用lu看一下 str1 的retainCount为:%lu \n 对象内地地址:%p",[str1 retainCount],str1);
NSLog(@"使用lu看一下 str4 的retainCount为:%lu \n 对象内地地址:%p",[str4 retainCount],str4);
NSLog(@"-----------------------------------------------------------------");
NSLog(@"str9 字符串与str2一样 的retainCount为:%lu \n 对象内地地址:%p",[str9 retainCount],str9);
NSLog(@"str10 字符串与str2一样 的retainCount为:%lu \n 对象内地地址:%p",[str10 retainCount],str10);
输出结果
结果
1.为什么用 @""
, stringWithFormat
, initWithString
, initWithFormat
四种方式。
因为方式不同,创建的对象所存在内存区域不同。你会发现str2 与 str9 字符串内容一样。为什么对象地址一个是0x1053674f8
另一个是0x604000033580
。正常情况下,字符串内容一样,应该取的是同一个内存地址。就像str2与str10一样。虽然创建的方法不一样,但是字符串一样,内存地址就是一样的。 这就是区别。 @“”与initWithString方法创建的字符串于stringWithFormat、initWithFormat创建的字符串所在的内存区域不同。
2.为什么要区分长度9 和 长度10的字符串?
因为字符串长度不一样,字符串所在内存区域不同,retainCount也不同。从上面结果中可以看出,str3和str4是同一种方式创建的字符串,但一个内存是0xa287dcaecc2ac5d9
一个是0x6040000339a0
。因为前者是在五大区域之外的内存区,而后者在堆中。
由上面连2点结合可知,由initWithString和stringWithString创建的NSString对象,不管字符串的内容和长度怎么变化,该字符串对象始终是存储在常量区的,引用计数为-1;从用%lu打印来看initWithString和stringWithString创建的字符串retainCount是无符号长整型的最大值。所以可以说他们没有引用计数这个概念
而由initWithFormat和stringWithFormat创建的对象,如果字符串内容是非汉字的,那么当字符串长度小于10个时,该字符串存储区域在五大区域之外,且随着字符串长度的变化,存储地址会有很大变化。当字符串长度超过10个以后,该字符串在堆中,与正常的OC对象一样。这里为什么要说非汉字呢,因为如果字符串内容是汉字,不管字符串的内容和长度怎么变化,该字符串都是在堆中,与正常OC对象一样。