内存管理相关问题
什么是arc
自动引用计数(Automatic Reference Counting, ARC)
引用计数
手工管理、引用计数式的内存管理在iOS中是这样工作的: 当使用alloc/init(或其它类似方法)创建对象时,随同对象返回的,还有个retainCount,其值为1,表明我们获得了这个对象的所有权。
NSObject *obj = [[NSObject alloc] init];
// do some stuff
[obj release];
在对象的alloc/init和release(即放弃对象的所有权)之间,我们可以任意处理它,这很安全,因为系统是无法回收正在使用中的对象的。
将对象加入到自动释放池也是类似,对象会一直存在,如果该对象引用计数为0,则销毁。
-(NSObject*) someMethod {
NSObject *obj = [[[NSObject alloc] init] autorelease];
return obj; // will be deallocated by autorelease pool later
}
MRC下内存管理的缺点:
- 当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放)
- 释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放)
- 模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。
- 多线程操作时,不确定哪个线程最后使用完毕
ARC工作原理
在编译期,ARC用的是更底层的C接口实现的retain/release/autorelease,这样做性能更好,也是为什么不能在ARC环境下手动retain/release/autorelease,同时对同一上下文的同一对象的成对retain/release操作进行优化(即忽略掉不必要的操作);ARC也包含运行期组件,这个地方做的优化比较复杂,但也不能被忽略。
ARC新规则
-
不能调用retain/release/autorelease/retainCount。
-
@property指令中的assign/retain/copy参数来告诉编译器,如何管理这些属性的内存。用了ARC之后,这些参数就作废了,改用weak/strong这两个参数。
-
基于Zone的内存已经没了(在运行时里也没了)。
-
管理常规变量
__strong: 默认限定符,不需要显式指定。表示任何用alloc/init创建的对象在当前范围的生命期内得以保留。“当前范围”是指变量声明语句所在的两个大括号之间(方法、循环、块,等等)。 __weak: 表示对象可以随时被摧毁。只有当它被其它对象强引用时才有用。__weak变量在摧毁时,被设为nil。
readwrite,readonly,assign,retain,copy,nonatomic 属性的作用
readwrite,readonly
- 设置可供访问级别
assign(默认)
- setter方法直接赋值,不更改索引计数(即不进行retain操作)
- 为了解决原类型与环循引用问题
- 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char, 等)
retain
-
setter方法对参数进行release旧值再retain新值
-
释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1
-
此属性只能用于Objective-C对象类型,而不能用于Core Foundation对象。(原因很明显,retain会增加对象的引用计数,而基本数据类型或者Core Foundation对象都没有引用计数)。
注意: 把对象添加到数组中时,引用计数将增加对象的引用次数+1。
assign与retain:
- 假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给 (assign)了b。此时a和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的,因为a并不知道b是否还在使用这块内存,如 果a释放了,那么b在使用这块内存的时候会引起程序crash掉。
- 了解到上面中assign的问题,那么如何解决?最简单的一个方法就是使用引用计数(reference counting),还是上面的那个例子,我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时引用计数增加到 2。这时如果a不再使用这块内存,它只需要把引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数减1。当引用计数变为0的时候, 代表该内存不再被任何指针所引用,系统可以把它直接释放掉。
总结:上面两点其实就是assign和retain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assign。retain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。
copy
-
对 NSString
此属性只对那些实行了NSCopying协议的对象类型有效。
- 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
-
建立一个索引计数为1的对象,然后释放旧对象
-
setter方法进行Copy操作,与retain处理流程一样,先旧值release,再 Copy出新的对象,retainCount为1。
-
这是为了减少对上下文的依赖而引入的机制
-
copy与retain的区别:
- copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。
- retain 是指针拷贝(指针相同,内容相同),copy 是内容拷贝(指针不同,内容相同)。
nonatomic,
- 非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级
atomic(默认)
-
为setter方法加锁(默认就是atomic)而这种机制是耗费系统资源的,
-
如果有多个线程同时调用setter的话,不会出现某一个线程执行setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样。
@synchronized(self) { _age = age; }
assign vs weak
修饰对象
- assign适用于基本数据类型,“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)
- weak是适用于NSObject对象,并且是一个弱引用。
指针地址改变
- assign其实也可以用来修饰对象,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。
- 而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。
- assigin 可以用非 OC 对象,而 weak 必须用于 OC 对象
这个写法会出什么问题: @property (copy) NSMutableArray *array;
两个问题:
-
添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;
-
使用了 atomic 属性会严重影响性能 ;
__block vs __weak
在block中修改变量
- Blocks可以访问局部变量,但是不能修改,如果修改局部变量,需要加__block
- _ _block:使用__block修饰的变量在block代码快中会被retain(ARC下,MRC下不会retain)
- _ _weak:使用__weak修饰的变量不会在block代码块中被retain
避免循环引用
- 要避免block出现循环引用 __weak typedof(self)weakSelf = self;
- 为什么不用__block 是因为通过引用来访问self的实例变量 ,self被retain,block也是一个强引用,引起循环引用,用__week是弱引用,当self释放时,weakSelf已经等于nil。
使用atomic一定是线程安全的吗
不是的
- atomic仅限于getter,setter时的线程安全。
- 在一个线程执行setter方法的时候,会涉及到字符串拷贝,另一个线程去读取,很可能读到一半的数据,也就是garbage数据。
- 比如@property(atomic,strong)NSMutableArray *arr;如果一个线程循环读数据,一个线程循环写数据,肯定会产生内存问题。因为它和setter,getter没有关系。
retain cycle例子
block中的循环引用:一个viewController
@property (nonatomic,strong)HttpRequestHandler * handler;
@property (nonatomic,strong)NSData *data;
_handler = [httpRequestHandler sharedManager];
[ downloadData:^(id responseData){
_data = responseData;
}];
self 拥有_handler, _handler 拥有block, block拥有self(因为使用了self的_data属性,block会copy 一份self)
vc异步的网络请求,成功后的block调用vc,如果此时,用户已经不用此vc了,vc还是没有释放。
BAD_ACCESS在什么情况下出现?
访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环
使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api 需要考虑:
所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty = xyz; }];
这些情况不需要考虑“引用循环”。
但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );
类似的:
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
self --> _observer --> block --> self 显然这也是一个循环引用。