美文网首页iOS Developer
《Objective-C高级编程:iOS与OS X多线程和内存管

《Objective-C高级编程:iOS与OS X多线程和内存管

作者: charlotte2018 | 来源:发表于2017-07-24 15:01 被阅读158次

    听说这本书很好,所以在项目不怎么忙的时候就读了读。总结了点笔记。

    859001-7ceabf4418ec5228.png

    手动内存管理MRC

    • 内存管理的思想
      思想一:自己生成的对象,自己持有。
      思想二:非自己生成的对象,自己也能持有。
      思想三:不再需要自己持有的对象时释放对象。
      思想四:非自己持有的对象无法释放。
      从上面的思想来看,我们对对象的操作可以分为三种:生成,持有,释放,再加上废弃,一共有四种。它们所对应的Objective-C的方法和引用计数的变化是:
    436A54CE-6351-4973-8161-120549A416CE.png

    思想一:自己生成的对象,自己持有

    alloc
    new
    copy
    mutableCopy

    如 id obj = [[NSObject alloc] init];//持有新生成的对象

    思想二:非自己生成的对象,自己也能持有

    id obj = [NSMutableArray array];//非自己生成并持有的对象
    [obj retain];//持有新生成的对象
    

    来看看[NSMutableArray array]是怎么个实现法,和自己生成的对象有什么区别:

    - (id)object
    {
        id obj = [[NSObject alloc] init];//持有新生成的对象
        [obj autorelease];//自动释放
        return obj;
    }
    

    通过autorelease方法,使对象的持有权转移给了自动释放池。所以实现了:调用方拿到了对象,但这个对象还不被调用方所持有.

    E33F7638-D143-45FA-BEBA-FBCCC0264E93.png

    思想三:不再需要自己持有的对象时释放对象

    id obj = [[NSObject alloc] init];//持有新生成的对象
    [obj release];//事情做完了,释放该对象
    
    

    或者是

    id obj = [NSMutableArray array];//非自己生成并持有的对象
    [obj retain];//持有新生成的对象
    [obj release];//事情做完了,释放该对象
    
    

    思想四:无法释放非自己持有的对象

    情况一:多次释放。

    id obj = [[NSObject alloc] init];//持有新生成的对象
    [obj release];//释放该对象,不再持有了
    [obj release];//释放已经废弃了的对象,崩溃
    

    情况二:对象不被自己持有,就释放。

    id obj = [NSMutableArray array];//非自己生成并持有的对象
    [obj release];//释放了非自己持有的对象
    

    实现原理

    Objective-C对象中保存着引用计数这一整数值。
    调用alloc或者retain方法后,引用计数+1。
    调用release后,引用计数-1。
    引用计数为0时,调用dealloc方法废弃对象。

    autorelease

    autorelease的具体使用方法如下:
    生成并持有NSAutoreleasePool对象。
    调用已分配对象的autorelease方法。
    废弃NSAutoreleasePool对象。

    8DB271FD-F954-4672-89FA-3434A8F02A4E.png

    所有调用过autorelease方法的对象,在废弃NSAutoreleasePool对象时,都将调用release方法(引用计数-1)

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];//相当于obj调用release方法
    
    

    NSRunLoop在每次循环过程中,NSAutoreleasePool对象都会被生成或废弃.

    096923F9-158F-4836-846C-DB293855CA83.png

    这样会有个问题如果是生成大量的autorelease对象,只要不废弃NSAutoreleasePool的话对象就不能释放,所以会产生内存不足的问题。由上图NSRunLoop是在应用程序主线程处理完了才会废弃Pool。所以如果在for循环里创建好多的局部对象,他们得不到及时的释放,就会使得程序因为内存不足奔溃。

    for (int i = 0; i < 10000; i++)
    {
        图像文件读入到data对象,
       data生成UIimage对象,
       改变对象的尺寸生成一个新的UIimage对象
    }
    

    解决办法:

    for (int i = 0; i < 10000; i++)
    {
       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        图像文件读入到data对象,
        data生成UIimage对象,
        改变对象的尺寸生成一个新的UIimage对象
      [pool drain];//相当于obj调用release方法
      
    }
    

    每个循环都会创建个池子,池子也是循环完了就释放。所以自动变量也都释放了。自动变量都是加在离它最近的池子的。

    苹果的实现

    class AutoreleasePoolPage
    {
        static inline void *push()
        {
            //生成或者持有 NSAutoreleasePool 类对象
        }
    
        static inline void pop(void *token)
        {
            //废弃 NSAutoreleasePool 类对象
            releaseAll();
        }
    
        static inline id autorelease(id obj)
        {
            //相当于 NSAutoreleasePool 类的 addObject 类方法
            AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 实例;
           autoreleaesPoolPage->add(obj)
        }
    
        id *add(id obj)
        {   
            //将对象追加到内部数组中
        }
    
        void releaseAll()
        {
            //调用内部数组中对象的 release 方法
        }
    };
    
    //压栈
    void *objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    
    //出栈
    void objc_autoreleasePoolPop(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }
    
    id *objc_autorelease(id obj)
    {
       return  AutoreleasePoolPage::autorelease(obj);
    }
    
    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // 等同于 objc_autoreleasePoolPush
    
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    // 等同于 objc_autorelease(obj)
    
    [pool drain];
    // 等同于 objc_autoreleasePoolPop(pool)
    
    

    想要了解更多autoreleasepool原理看这个http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/

    ARC下的内存管理

    在ARC机制下,编译器就可以自动进行内存管理,减少了开发的工作量。但我们有时仍需要四种所有权修饰符来配合ARC来进行内存管理。

    四种所有权修饰符

    ARC提供四种修饰符,分别是strong, weak, autoreleasing, unsafe_unretained

    __strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。

    NSObject *obj = [[NSObject alloc]init];
      他们是等价的
    NSObject *__strong obj = [[NSObject alloc]init];
    
    obj = nil;
    

    __weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。

    比如避免循环引用的弱引用声明:

         __weak __typeof(self) weakSelf = self;
    
    

    _autoreleasing:将对象赋值给附有 _ autoreleasing 修饰符的变量等同于ARC 无效时调用对象的autorelease方法

    @autoreleasepool {
        id __autoreleasing obj = [[NSObject alloc] init];
    }
    

    unsafe_unretained:相当于assign。直接赋值。引用计数不变。他会发生野指针现象。所以不安全。不像weak,当指向的对象为空的时候,将指针置为nil。

    属性的内存管理

    ObjC2.0引入了@property,提供成员变量访问方法、权限、环境、内存管理类型的声明,下面主要说明ARC中属性的内存管理
    属性的参数分为三类,基本数据类型默认为(atomic,readwrite,assign),对象类型默认为(atomic,readwrite,strong),其中第三个参数就是该属性的内存管理方式修饰,修饰词可以是以下之一:

    assign:直接赋值

    assign一般用来修饰基本数据类型

      @property (nonatomic, assign) NSInteger count;
    

    当然也可以修饰ObjC对象,但是不推荐,因为被assign修饰的对象释放后,指针还是指向释放前的内存,在后续操作中可能会导致内存问题引发崩溃。

    retain

    retain和strong一样,都用来修饰ObjC对象
    使用set方法赋值时,实质上是会先保留新值,再释放旧值,再设置新值,避免新旧值一样时导致对象被释放的的问题
    MRC写法如下

     - (void)setCount:(NSObject *)count {
             [count retain];
             [_count release];
             _count = count;
         }
    

    ARC对应写法

    - (void)setCount:(NSObject *)count {
            _count = count;
        }
    

    copy

    一般用来修饰String、Dict、Array等需要保护其封装性的对象,尤其是在其内容可变的情况下,因此会拷贝(深拷贝)一份内容給属性使用,避免可能造成的对源内容进行改动。
    使用set方法赋值时,实质上是会先拷贝新值,再释放旧值,再设置新值。
    实际上,遵守NSCopying的对象都可以使用copy,当然,如果你确定是要共用同一份可变内容,你也可以使用strong或retain。

    weak:ARC新引入修饰词,可代替assign,比assign多增加一个特性(置nil)

    weak和strong一样用来修饰ObjC对象。
    使用set方法赋值时,实质上不保留新值,也不释放旧值,只设置新值。

     @property (weak) id<MyDelegate> delegate;
    

    strong:

    ARC新引入修饰词,可代替retain.ARC一般都写strong。

    block的内存管理

    OS中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点:
    1)如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),尽量不要使用retain。

       @property (nonatomic, copy) void(^block)(NSData * data);
    

    block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。

       __weak typeof(self) weakSelf = self;
    

    循环中对象占用内存大

    这个问题常见于循环次数较大,循环体生成的对象占用内存较大的情景。

     for (int i = 0; i < 10000; i ++) {
             Person * p = [[Person alloc]init];
         }
    

    该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。

    for (int i = 0; i < 10000; i ++) {
             @autoreleasepool {
                 Person * p = [[Person alloc]init];
             }
     }
    

    特殊情况
    假如有10000张图片,每张2M左右,现在需要获取所有图片的尺寸,你会怎么做?

    0EED4362-F987-4DCF-929E-5FB8250C67D4.png

    用imageNamed方法加载图片默认会写到缓存里,autoReleasePool也不能释放缓存,对此问题需要另外的解决方法

    179360B6-0EE5-4DCD-8BBA-9A0C801F2B8D.png

    相关文章

      网友评论

        本文标题:《Objective-C高级编程:iOS与OS X多线程和内存管

        本文链接:https://www.haomeiwen.com/subject/wpxbkxtx.html