《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数

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

《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数_第1张图片
859001-7ceabf4418ec5228.png

手动内存管理MRC

  • 内存管理的思想
    思想一:自己生成的对象,自己持有。
    思想二:非自己生成的对象,自己也能持有。
    思想三:不再需要自己持有的对象时释放对象。
    思想四:非自己持有的对象无法释放。
    从上面的思想来看,我们对对象的操作可以分为三种:生成,持有,释放,再加上废弃,一共有四种。它们所对应的Objective-C的方法和引用计数的变化是:
《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数_第2张图片
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方法,使对象的持有权转移给了自动释放池。所以实现了:调用方拿到了对象,但这个对象还不被调用方所持有.

《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数_第3张图片
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对象。

《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数_第4张图片
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对象都会被生成或废弃.

《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数_第5张图片
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 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左右,现在需要获取所有图片的尺寸,你会怎么做?

《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数_第6张图片
0EED4362-F987-4DCF-929E-5FB8250C67D4.png

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

《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数_第7张图片
179360B6-0EE5-4DCD-8BBA-9A0C801F2B8D.png

你可能感兴趣的:(《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数)