• 内存管理相关问题


    什么是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 显然这也是一个循环引用。

  • 相关阅读:
    javaweb学习总结二十六(response对象的用法二 下载文件)
    javaweb学习总结二十五(response对象的用法一)
    线程池的使用
    javaweb学习总结二十四(servlet经常用到的对象)
    javaweb学习总结二十三(servlet开发之线程安全问题)
    创建与删除索引
    Oracle中exists与in的区别
    win7安装IIS及将网站发布到IIS上
    C# toolstrip 上添加DateTimePicker Control控件
    用java实现zip压缩
  • 原文地址:https://www.cnblogs.com/sunyanyan/p/5300854.html
Copyright © 2020-2023  润新知