内存管理相关问题

什么是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下内存管理的缺点:

  1. 当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放)
  2. 释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放)
  3. 模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。
  4. 多线程操作时,不确定哪个线程最后使用完毕

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;

两个问题:

  1. 添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;

  2. 使用了 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 显然这也是一个循环引用。

posted @ 2016-03-21 10:27  孙焱焱  阅读(331)  评论(0编辑  收藏  举报