一、1、IOS开发中,内存中的对象主要有两类
一类是值类型,比如int、float、struct等基本数据类型。
一类是引用类型,即继承自NSObject类的所有的OC对象。
A、 值类型会被放入栈中,
B、 引用类型会被放到堆中
@ 全局/静态存储区,全局变量和静态变量的存储区域
@栈区,在函数执行过程中,函数内局部变量的存储单元可以在栈上创建,函数执行结束后这些存储单元自动被释放。
@堆区,亦称动态分配区,由程序在运行过程中动态申请分配和管理的区,通常说的内存管理,基本是指对于这一内存区块的管理
@常量区,存储程序运行过程中用到的各种常量,不允许修改
二、Objective-C管理内存的方式
每个对象(特指:类的实例)内部都有一个retainCount的引用计数,
使用alloc, new, copy或者mutableCopy等以及调用addObject等方法时,引用计数器+1,使用release时,引用计数器-1,当引用计数器为0时,对象被释放
1) iOS内存管理的法则(Swift不适用,Swift自动管理内存)
• 谁创建谁释放
• 谁retain谁释放
2)MRC手动管理内存(人工引用计数)
当引用计数为0的时候,必须回收,引用计数不为0,不能回收,如果引用计数为0,但是没有回收,会造成内存泄露。如果引用计数为0,继续释放,会造成野指针。为了避免出现野指针,我们在释放的时候,会先让指针=nil。
3)ARC自动管理内存(自动引用计数)
在ARC模式下,只要没有强指针(强引用)指向对象,对象就会被释放。在ARC模式下,不允许使用retain、release、retainCount等方法。并且,如果使用dealloc方法时,不允许调用[superdealloc]方法。
ARC模式下的property变量修饰词为strong、weak,相当于MRC模式下的retain、assign。strong:代替retain,缺省关键词,代表强引用。weak:代替assign,声明了一个可以自动设置nil的弱引用,但是比assign多一个功能,指针指向的地址被释放之后,指针本身也会自动被释放。
@property的参数分为三类,也就是说参数最多可以有三个,中间用逗号分隔,每类参数可以从上表三类参数中人选一个。如果不进行设置或者只设置其中一类参数,程序会使用三类中的各个默认参数,默认参数:(atomic,readwrite,assign)
一般情况下如果在多线程开发中一个属性可能会被两个及两个以上的线程同时访问,此时可以考虑atomic属性,否则建议使用nonatomic,不加锁,效率较高;
retain,相当于ARC中的strong
assign,相当于ARC中的weak
属性参数
ARC下基本数据类型默认的属性参数为(atomic,readwrite,assign),对象类型默认的属性参数为(atomic,readwrite,strong)
非ARC下基本数据类型默认的属性参数为(atomic,readwrite,assign),对象的默认属性参数为(atomic,readwrite,retain)
ARC下
assgin : 基本数据类型、枚举、结构体(非OC对象)
strong : 除NSString\block以外的OC对象
copy : NSString,数组,字典,block
weak : 当2个对象相互引用,一端用strong,一端用weak。引用记数不会加1
非ARC下
assign,用于基本数据类型和C数据类型(int, float, double, char)另外还有id
retain,通常用于非字符串对象
copy,通常用于字符串对象、block、NSArray、NSDictionary
@@ strong与copy都是强类型,但这两者有什么区别呢。什么情况下使用copy,什么时候使用strong呢?
copy是深复制,会指向新的对象,即创建新的内存地址。不会跟着赋值的对象的值变化而变化,即不可变。 strong则是指向赋值对象所在的地址,所以当赋值对象值发生变化时,也会跟着改变。
使用copy的概念应该是出于安全的考虑。防止赋值给它的是可变的数据。
所以什么时候使用 strong,赋值对象改变时也要跟着改变。使用copy,赋值对象改变时property不跟着变化。
@@、retain、copy、assign的区别:
1.retain:当对一个对象A调用retain,然后赋值给B时,对象的引用计数加1,A和B指向同一个内存地址。
2.copy:当对一个对象A调用retain,然后赋值给B时,对象的引用计数加1,而且生成了一个新的拷贝,A和B指向不一样的内存地址。
3.assign:当对一个对象A调用retain,然后赋值给B时,对象的引用计数不变,A和B指向同一个地址。
四、那些内存管理中的坑
1)循环引用
即A持有了B,B持有了A,导致无论是先释放A还是B
解决这种亲密关系导致的循环引用,采用弱引用即可
2) Block中的坑
Block在访问对象变量(即类对象)时有两条隐含的retain对象规则,即:
A、如果访问类属性对象变量,则Block会强引用self,即retain一次类对象本身
3) B、如果访问了局部对象变量,则Block会强引用局部变量自身一次
3)闭包中循环引用
4) 闭包中变量的访问范围及持有变了隐含规则同Block
4)NSTimer中的对象retain问题
repeats参数被设置成YES时,target中的对象将永远不会被释放,只有调用invalidate方法之后才会释放target对象,从而释放接收处理target对象。
5) performSelector中的对象retain问题
只有当执行完成之后才会释放target和argument对象,它的执行前提条件是:1)时间到;2)满足指定的Loop Modes。因此在发起该方法的类销毁之前该方法不一定会被执行,因此就会存在内存泄漏的风险。能否在dealloc或deinit中释放呢?请看客考虑
五、自动释放池
自动内存释放使用@autoreleasepool关键字声明一个代码块,如果一个对象在初始化时调用了autorelase方法,那么当代码块执行完之后,在块中调用过autorelease方法的对象都会自动调用一次release方法。这样一来就起到了自动释放的作用,同时对象的销毁过程也得到了延迟(统一调用release方法)。对象的释放延迟到自动释放池销毁的时候。不过这只是一种半自动的机制。
注意:
1)autorelease不会改变对象的引用记数,如果Person对象引用记数非0,放入自动释放池是无法被销毁的。
2)自动释放池实质是当自动释放池销毁后调用对象的release方法。
3)如果一个操作比较占用内存(对象比较多或者对象占用资源比较多),最好不要放入自动释放池或者考虑放入多个释放池中。
4)ObjC中类库中的静态方法一般都不需要手动释放,内部已经调用了autorelease方法.
1.自动释放池实现了对象的延迟释放,将释放时机延后。当对一个对象调用autorelease方法后,对象被加入自动释放池。当自动释放池释放时,会对自动释放池中的对象调用release方法。
2.主线程会自动创建自动释放池,自己创建的线程需要自己负责创建自动释放池。在一个RunLoop周期开始时,系统会创建一个自动释放池,当RunLoop周期结束时,系统会释放之前创建的自动释放池。如果我们在使用autorelease时没有自己创建自动释放池,对象会在它所在的RunLoop周期结束时被释放掉。一个UI事件,Timer调用,delegate调用,都会是一个新的Runloop。
3.类似于[NSString stringWithFormat:]这样的类方法创建的对象默认是使用了自动释放池的,不需要释放。
4.当在短时间内大量的使用自动释放对象,要手动使用自动释放池来释放对象,否则内存会在短时间内疯涨。
六、循环引用
1. @class
对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类
这种代码编译会报错。当使用@class在两个类相互声明,就不会出现编译报错
用法概括
使用@class 类名; 就可以引用一个类,说明一下它是一个类
和#import的区别(面试题)
l #import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息
l 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来讲,使用@class方式就不会出现这种问题了
l 在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类
问题:如何解决循环引用的内存管理。
解决方案:一端用assign,一端用retain
/*
1.@class的作用:仅仅告诉编译器,某个名称是一个类
2.开发中引用一个类的规范
1>在.h文件中用@class来声明类
2>在.m文件中用#import来包含类的所有东西
2. 循环retain
比如A对象retain了B对象,B对象retain了A对象
这样会导致A对象和B对象永远无法释放
七、didReceiveMemoryWarning、dealloc方法使用
1.didReceiveMemoryWarning方法:
首先调用[super didReceiveMemoryWarning]方法,然后检查当前视图的父视图是否为空,如果为空,则释放掉一些不需要的数据。关于视图界面的释放不应该在这个方法中,应该放在viewDidUnload方法中。
3.dealloc方法:
对象释放时调用,在这个方法中,要释放掉所有的数据和输出口。比如释放输出口:[xxx release];(不使用self关键字)
八、一些注意的地方
1.向集合(NSArray,NSDictionary等)添加对象时,被添加的对象会被执行retain操作,当从集合中移走对象或者集合对象被释放时,集合中的对象会被执行release操作。
2.要保证有多少个alloc、copy、multablecopy、retain消息,就要有多少个release或者autorelease,保证代码平衡。
3.在程序中直接用@""创建的NSString对象,是常量,引用计数是-1,向它发送retain、release没有效果。
4.在View中使用图片时,大的图片区域尽量使用小的图片数据来填充,减小内存占用。
5.[UIImage imageNamed:@""],次方法使用了系统缓存来缓存图像,会长时间占用内存,最好使用imageWithContentsOfFile方法。
6.数据要延迟加载,只在内存中保留满足需要的最少的数据和视图元素,需要的时候再加载,不需要就马上销毁。
7.假如一个成员变量在property中使用了retain,当使用self关键字对其赋值时,会对创建的对象再retain一次,造成内存泄露。比如:self.xxx = [[XXXalloc] init];
对一个成员变量赋nil值时,self.xxx = nil,会调用xxx的release方法,并且将指针置空,xxx = nil,只是将指针置空。
9.在控制器中使用NSTimer会使当前控制器引用计数加1,所以在控制器释放之前,必须暂停和使定时器失效,否则控制器将不会被释放。