五大内存区域
栈区,堆区,全局区,常量区,代码区,五大内存区域之外还有 自由存储区也称之五大区域之外区、
-
栈区
创建临时变量时由编译器自动分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。@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]; }
-
堆区
就是那些由 new alloc 创建的对象所分配的内存块,它们的释放系统不会主动去管,由我们的开发者去告诉系统什么时候释放这块内存(一个对象引用计数为0是系统就会回销毁该内存区域对象)。一般一个 new 就要对应一个 release。在ARC下编译器会自动在合适位置为OC对象添加release操作。会在当前线程Runloop退出或休眠时销毁这些对象,MRC则需程序员手动释放。//alloc是为Person对象分配内存,init是初始化Person对象。本质上跟[Person new]一样。 Person *person = [[Person alloc] init];
-
全局/静态存储区
全局变量和静态变量被分配到同一块内存中,在以前的 C 语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过 void* 来访问和操纵,程序结束后由系统自行释放),在 C++ 里面没有这个区分了,他们共同占用同一块内存区。 -
常量存储区
这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。一般值都是放在这个地方的 -
代码区
存放函数的二进制代码 -
自由存储区
就是那些由 malloc 等分配的内存块,他和堆是十分相似的,不过它是用 free 来结束自己的生命的。 -
全局静态变量
优点 不管对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会指向固定的指针地址,供所有对象使用,节省空间 缺点 存在的生命周期长,从定义直到程序结束 建议 从内存优化和程序编译的角度来说,尽量少用全局静态变量,因为存在的声明周期长,一直占用空间。程序运行时会单独加载一次全局静态变量,过多的全局静态变量会造成程序启动慢,当然就目前的手机处理器性能,几十几百个估计也影响不大吧。
-
局部静态变量
优点 定义后只会存在一份值,每次调用都是使用的同一个对象内存地址的值,并没有重新创建,节省空间,只能在该局部代码块中使用 缺点 存在的生命周期长,从定义直到程序结束,只能在该局部代码块中使用。 建议 局部和全局静态变量从本根意义上没有什么区别,只是作用域不同而已。如果值仅一个类中的对象和类方法使用并且值可变,可以定义全局静态变量,如果是多个类使用并可变,建议值定义在model作为成员变量使用。如果是不可变值,建议使用宏定义
-
对内的extern全局变量
没有用extern在.h中修饰的变量,仅定义在.m中让该变量只能在该类使用优点 不管对象方法还是类方法都可以访问和修改全局静态变量,并且外部类无法调用静态变量,定义后只会存一份值,供所有对象使用,节省空间。跟全局静态变量一样,只是少了static修饰少了static特性 缺点 存在的生命周期长,从定义直到程序结束 建议 跟全局静态变量都一样了,还需要用对内的全局变量吗?不用extern修饰就少了extern的特性,还不如用全局静态变量,至少能明确的知道static是对内使用的
-
外部全局变量
除了该类,其他文件也可以访问该变量优点 除了该类,其他文件也可以访问该变量 缺点 存在的生命周期长,从定义直到程序结束。并且外部可以修改其值,出现错误不容易定位 建议 使用全局变量的原因就在于其对外的特性,但是其使用的方便性没有使用model的属性或宏来得方便。程序启动的时候会单独加载全局变量,同理与全局静态变量,少使用。
-
const常量
不同于变量,常量的值是固定不可变的,一般用于只读值。优点 只可以读取值,不能修改。一般用于接口或者文字显示这种固定值。添加extern可以对外全局常量,任意位置都可以访问 缺点 存在的生命周期长,从定义直到程序结束。需要在.h .m中分别定义代码较多 建议 看个人习惯吧,使用宏或者常量只是编译加载方式不一样而已
属性标识符
-
@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声明 -
nonatomic
非原子性,在调用用nonatomic声明的对象属性时是非线程安全性的。最为直观的就是NSMutableArray的使用。当同时在子线程去增删数组元素,在主线程中去遍历数组元素就会出现数组越界或者数组没有遍历完。因为采用的nonatomic,不同操作可以同时执行,而不需要等前面的操作完成后在进行下一步操作。所以称之为非线程安全。非原子性的执行效率更高不会阻塞线程 -
atomic
原子性,相反与非原子性,atomic是具有线程安全性的。他会在getset方法中加入线程操作。每当对用atomic声明的对象属性操作时,会根据操作加入线程的顺序一步一步完成操作,而不是非原子性的同时操作。执行效率较低一般来说很少用atomic。 -
retain
-
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特有),strong 和 weak的指针,根本区别在于,strong执行了retain操作,而weak没有。 -
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特有) -
readOnly
表示属性仅能读不能设置其值。告诉编译器只生成getter方法不生成setter方法。 -
readWrite
示属性可读可写。编译器会自动生成getter setter方法 -
__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];
MRC与ARC区别
-
MRC手动内存管理
在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下使用ARC 在Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入 -fobjc-arc
-
ARC自动内存管理
1.WWDC2011和iOS5所引入自动管理机制——自动引用计数(ARC),它不是垃圾回收机制而是编译器的一种特性。ARC管理机制与MRC手动机制差不多,只是不再需要手动调用retain、release、autorelease;当你使用ARC时,编译器会在在适当位置插入release和autorelease;ARC时代引入了strong强引用来带代替retain,引入了weak弱引用。 2.ARC工程中如果要使用MRC的需要在工程的Build Phases的Compile Sources中选择需要使用MRC方式的.m文件,然后双击该文件在弹出的会话框中输入 -fno-objc-arc
-
autoreleasepool自动释放池
1.自动释放池始于MRC时代,主要是用于 自动 对 释放池内 对象 进行引用计数-1的操作,即自动执行release方法。 2.在MRC中使用autoreleasepool必须在代码块内部手动为对象调用autorelease把对象加入到的自动释放池,系统会自动在代码块结束后,对加入自动释放池中的对象发送一个release消息。无需手动调用release 3.在ARC中对@autoreleasepool的使用相比MRC不太多。主要用于一些大内存消耗对象的重复创建时,保证内存处于比较优越的状态。常用于创建对象较多的for循环中。在ARC下不要手动的为@autoreleasepool代码块内部对象添加autorelease,ARC下自动的把@autoreleasepool代码块中创建的对象加入了自动释放池中。
-
autorelease对象释放的时机
1.系统自行创建的@autoreleasepool释放时机 线程与Runloop是一对一关系,主线程中会自动创建Runloop,而子线程需要自行调用Runloop来让子线程自动创建Runloop。 2.自己创建的@autoreleasepool @autoreleasepool {//这个{开始创建的自动释放池,这里开始内部的对象自动加入autorelease }//这个}开始,不必再对 对象加入autorelease,自动释放池被销毁。
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);
为什么用 @"", stringWithFormat, initWithString, initWithFormat四种方式创建的对象所存在内存区域不同。正常情况下,字符串内容一样,应该取的是同一个内存地址。就像str2与str10一样。
网友评论