【Objective-C 篇】 ☞ 5. MRC、ARC

MRC 手动管理内存

1.1 内存引用平衡原则

1) 如果使用alloc,new开头,或者是copy(复制一个对象)来创建一个对象,意味着你拥有这个对象的所有权。这个对象的引用计数器初始值为1(也有可能>1)

2) 如果你拥有这个对象的所有权,在不使用此对象时,就有责任向对象发送release消息。(谁创建了对象,谁就有责任release这个对象)

3) 如果并不拥有一个对象的所有权,而想要使用这个对象,为了防止你在使用此对象期间,对象被别人释放掉,需要向对象发送retain消息,以保持对象。此时可以认为,你也拥有了这个对象所有权。

4) 当你使用完retain过的对象后,有责任release一下这个对象。

(谁retain了一个对象,谁就有责任release这个对象)

       配对出现:(+1、-1 ==>平衡) 

我们创建的对象不用了,就release;我们retain的对象不用了,就release

    内存管理的原则就是有加就有减。也就是说, 一次allocnew对应一次release, 一次retain对应一次release。

1.2 自动释放池(autoreleasepool)

通过自动释放池来管理对象,只需要一个自动释放池,可以管理很多对象,当自动释放池结束的时候,会自动向池中的每个对象都发送release消息。

1) 如果一个对象创建后,不能马上释放它,但又不得不尽到释放对象的责任,此时可以将对象放入自动释放池,延迟对象的释放时机。比如绝大部分工厂方法都是如此。工厂方法中的对象是方法中创建的,按理来说应该由工厂方法内部释放,但工厂方法的功能决定了这个对象不能马上释放,此时应该将对象放入自动释放池。

2) 当自动释放池结束时,会向池中的所有对象发送release消息,如果此时,池中的对象的引用计数器是1,那么,对象会被释放掉。

3) 如何开始和结束一个自动释放池呢?

//自动释放池,用于回收对象的存储空间。

@autoreleasepool{ //开始创建一个自动释放池

  ……  ……

} //结束自动释放池销毁了, 给自动释放池中所有的对象发送一条release消息

      还有一种性能低下,被淘汰的自动释放池创建方式(了解)

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];//开始一个自动释放池

   ……  ……

[pool drain];//结束一个自动释放池

4) 无论一个对象是否在自动释放池,只要这个对象是由别人创建的,你要使用此对象,就得retain,用完此对象,就得release,即使对象在自动释放池,也依然如此。

5) 实际开发中,合理使用自动释放池来避免内存使用出现峰值。

如果App出现的内存使用的峰值,此时才考虑是否是由于大量使用工厂方法造成的,是否需要使用自动释放池解决问题。要合理使用自动释放池,大量使用会消耗系统资源。

6) autorelease方法:在自动释放池中使用,目的是将对象添加到自动释放池中。自动释放池销毁时会对池中对象作release操作。(延迟release)

@autoreleasepool {

Person *p = [[[Person alloc] init] autorelease];

        

}

  • autorelease方法与release方法的区别:这两个方法都能把对象的引用计数器减1。
    • release是一个精确的减1,对对象的操作只能在release之前进行,如果是在之后,就会出现野指针错误;
    • autorelease是一个不精确的引用计数器减1,当给对象发送autorelease消息时,对象就会被放到自动释放池中,自动释放池销毁时会给池中的所有对象发送release消息,使得所有对象的计数器减1,所以本质上autorelease还是会调用release。
  • 常见错误:

// 销毁自动释放池的时候 要对person再执行release操作的话 会报野指针错误

@autoreleasepool {

Person *person = [[[Person alloc] init] autorelease];

[person release];

// 对象执行两次autorelease意味着自动释放池销毁的时候 对象会执行两次release操作 会报野指针错误

@autoreleasepool {

Person *person = [[[[Person alloc] init] autorelease] autorelease];

1.3 setter方法的内存管理(应用场景:两个类是聚合关系)

当一个方法传入一个对象后,如果需要将这个对象用实例变量等手段保存起来持续使用时,需要做以下事:

1) 先将此对象的引用计数器加1(retain)

2) 再将原来实例变量指向的对象的引用计数器减1(release)

3) 最后将传入的对象地址保存到实例变量中

4) retain的对象通常需要在dealloc方法中release.

 

只要一个对象想使用房间,就需要对这个房间的引用计数器+1

只要一个对象不想使用房间,就需要对这个房间的引用技术器-1

      

经常会被问到的问题:

1) 前面3个顺序可否颠倒?

能不能先release原来的对象,再赋值,最后retain新对象? 一般可以,但不建议

       

2) dealloc方法中,可不可以使用self.属性 = nil;的方式释放属性所指向的对象? ok

可以的,self.属性 相当于调用上面的setter方法(该方法中有向对象发release

    Demo:setter方法的内存管理

     

    

1.4 property修饰符

1) nonatomic 非原子性操作 (安全性低,但效率高。iOS实际开发中99%用这个,以确保性能)

atomic 原子性操作 (安全性高,但很耗费资源)

注意,默认是原子性的(atomic),所以在定义属性时一定要写上nonatomic

2) assign, retain, copy

    assign  是默认值,仅做赋值。不会解决内存问题(即不会retain,也不会release)。在MRC中可用于对象类型和非对象类型。

    retain 只能用于对象类型的属性,会解决内存问题(生成的setter方法会自动加入retain,release等内存操作代码)

    copy 一些特殊对象类型,如果不希望和别人共享一个对象,用copy会自动创建一个新的对象。

      有些属性在传入后,需要拷贝一份,以防止传入的对象内容被修改后,影响到我的对象。

(并不是所有的对象都能拷贝,只有遵守<NSCopying>协议,实现了协议中CopyWithZone方法的这种对象才拥有拷贝的功能)

3) readonly/readwrite

readwrite 是默认的,编译器生成gettersetter方法

readonly 只读,编译器只生成getter方法

  属性访问器的类型:

  

注:声明属性默认情况下,并没有解决内存问题

当使用 @property(retain)引用数据类型,帮我们解决了setter使用中的内存问题,但dealloc中的release操作,我们自己来做。

1.5  MRC中的循环引用(两个对象互相引用(即互相包含)时,要一强一弱!)

  如果A对象要拥有B对象, 而B对象又要拥有A对象, 此时会形成循环retain

  如何解决这个问题:让其中一方不要做retain操作即可!

Demo:

   

ARC 自动管理内存

1.1 概念

Automatic Reference Counting  自动引用计数

基于MRC, 在MRC下,对象的引用计数是由程序员负责的。在ARC下,对象的引用计数工作由编译器来完成。

1.2 ARC的工作原理

在ARC下,堆内存空间的管理依然使用引用计数器的方式。只是ARC下的引用计数器的加减工作不再由程序员来做,而是由编译器来做。

编译器在编译程序期间,会在程序恰当的位置自动加入对对象的retain,release和autorelease的操作。

注意:ARC是 编译期语法或编译器特性(即可以理解为是Xcode的一个功能),而不是运行时特性。

1.3 怎么用

  程序员不要对对象的引用计数器进行操作,编译器会帮我们做:

    1)  在ARC下,不要在程序中调用retain, release, autorelease方法。

    2)  在ARC下,不要重写retain, release, autorelease方法。

    3)  在ARC下,不要在dealloc方法中调用父类的dealloc方法。实际上,ARC下,dealloc方法基本没有用了。

      总之,一切与内存操作相关的东西都由ARC自动完成。

1.4 ARC的判断原则:(即系统怎么判断对象是否要释放)

  • 自动管理内存的判断原则:只要还有一个强指针变量指向对象,对象就会保持在内存中。
  • 强指针(强引用):
    • 默认情况下所有的指针变量都是强指针
    • __strong修饰的指针
    • 例如:

TRPerson *p1 = [[TRPerson alloc]init];

__strong TRPerson *p2 = [[TRPerson alloc]init];

 

  • 弱指针(弱引用)
    • __weak修饰的指针
    • 例如:

__weak TRPerson *p = [[TRPerson alloc]init];

 

  • 举例:

//ARC的判断准则:只要没有强指针指向对象,对象就会释放

TRPerson *p1 = [[TRPerson alloc]init];

__strong TRPerson *p2 = [[TRPerson alloc]init];

__weak TRPerson *p3 = p2;

p2 = nil;//p2改变了指向,此时就没有强指针指向对象,对象就会释放

}//出了大括号,局部变量p1就释放,此时就没有强指针指向对象,对象就会释放

        单个对象的内存管理:如果一个对象不再使用了,就把指向对象的强指针置为nil,对象就会释放。

  • 注意:
    • 当使用ARC的时候,暂时忘记“引用计数器”,因为判断标准变了。
    • 在实际开发中,千万不要使用一个弱指针来保存一个刚刚创建的对象。

{

//p是弱指针,对象会被立即释放

__weak TRPerson *p = [[TRPerson alloc]init];//刚创建就被释放!

}

 

1.5 ARC中多个对象的内存管理:

  • ARCMRC一样,想拥有某个对象必须用强指针保存对象,但是不需要在dealloc方法中release

@class TRDog;

@interface TRPerson : NSObject

 

//MRC下写法

@property (nonatomic, retain) TRDog *dog;

//ARC下写法

@property (nonatomic, strong) TRDog *dog;

 

@end

MRC

A对象想使用B对象, 需要对B对象进行一次retain

A对象不用B对象了, 需要对B对象进行一次release

即,property的时候进行retain, dealloc的时候进行release

 

ARC

A对象想使用B对象, 那么就需要用一个强指针指向B对象(即用strong,表示强引用)

// ARC中保存一个对象用strong, 相当于MRC中的retain

@property (nonatomic, strong) Dog *dog;

A对象不用B对象了, 什么都不需要做, 编译器会自动帮我们做

 

1.6 ARC下循环引用问题

  循环引用:指两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。

  • ARC和MRC一样,如果A拥有B,B也拥有A,那么必须一方使用弱指针
  • 两个强指针互相引用,两个空间就会永不释放!所以必须要一强一弱。

@class TRDog;

@interface TRPerson : NSObject

//MRC写法

//@property (nonatomic, retain) TRDog *dog;

//ARC写法

@property (nonatomic, strong) TRDog *dog;

@end

 

@interface TRDog : NSObject

//错误写法,循环引用会导致内存泄露

//@property(nonatomic, strong)TRPerson *owner;

 

//正确写法,当如果保存的是对象类型建议使用weak

//@property(nonatomic, assign)TRPerson *owner;

@property (nonatomic, weak) TRPerson *owner;

@end

  • ARC下循环引用的案例:

    

1.7 如果在ARC下定义属性的内存特质(attribute)

  • 在MRC下, 与内存相关的属性特质有:assign, retain, copy
  • 在ARC下, 与内存相关的属性特质有:

(1)strong  强引用 

  类似于retain,引用时候会引用计数+1。

strong 案例:

@property (nonatomic, strong) NSString *str1;

@property (nonatomic, strong) NSString *str2;

self.str1 = @"Hello World";

self.str2 = self.str1;

self.str1 = nil;

NSLog(@"str2 = %@", self.str2);

结果是:str2 = Hello World

(2)weak   弱引用

  • 类似于assign,不会改变引用计数,只做简单的赋值。
  • weakassign的区别:
    • assign 仅做赋值(默认值),不会解决内存问题。在MRC中可用于对象类型和非对象类型。
    • weak只用于ARC下的对象类型(即带*号的指针类型)。weak比assign更完全,会自动把野指针置空。
    • assign一般在ARC下用于基本数据类型(即不带*号的类型,包括id类型)
    • weak 的特点:
      • 声明为weak的指针,指针指向的地址一旦被释放,这些指针都将被赋值为nil。这样的好处能有效的防止野指针。
      • 特定情况下,如果内存的释放会出现问题,经常使用weak来解决,比如,最常见的问题是"内存循环引用"

weak 案例:

@property (nonatomic, strong) NSString *str1;

@property (nonatomic, weak) NSString *str2;

self.str1 = @"Hello World";

self.str2 = self.str1;

self.str1 = nil;

NSLog(@"str2 = %@", self.str2);

结果是:str2 = null

(3)unsafe_unretained  (从名字上来看:不安全,不retain)

  • 默认的, 可以用于非对象类型/对象类型。
  • 用于非对象类型时和assign一样,只做简单的赋值。
  • 用于对象类型时类似于weak,但没有weak安全(因为当指向的对象被销毁时,指针不会自动置空,会产生野指针错误)

unsafe_unretained 案例:

@property (nonatomic, strong) NSString *str1;

@property (nonatomic, unsafe_unretained) NSString *str2;

self.str1 = @"Hello World";

self.str2 = self.str1;

self.str1 = nil;

NSLog(@"str2 = %@", self.str2);

没有输出结果!程序崩掉,会报野指针错误(EXC_BAD_ACCESS 坏访问)

(4)copy   拷贝 (和以前的copy一样)

    使用copy: 对NSString

    效果其实和retain没什么两样,唯一的区别就是copy只用于NSString而不能用于NSMutableString, 如果当一个类继承NSObject,那么这个类里面的属性需要使用copy。

    retain是指针拷贝,copy是内容拷贝。

  • strong, weak, unsafe_unretained往往都是用来声明属性的。

    如果想声明临时变量就得用__strong, __weak, __unsafe_unretained, __autoreleasing,其用法与上面介绍的类似。

案例:

__strong NSString *str1 = @"Hello World";

__weak  NSString *str2 = str1;

__unsafe_unretained NSString *str3 = str2;

str1 = nil;

//现在str1str2的指针都为nil,str3不为nil,但是是野指针。

提示:没有两个下划线的放属性里,有两个下划线的放变量前。

  • objective-c内存管理中有一条是:谁分配谁释放。__autoreleasing则可以使对像延迟释放。

      autoreleasing的应用:在函数内部申请的空间,在函数外部也可以使用

 

//MRC

-(NSString *)stringTest

{

    NSString *retStr = [NSString stringWithString:@"test"];

    return [[retStr retain] autorelease];

}

//ARC

-(NSString *)stringTest  

{

    __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"];  

    return retStr;  

}

  • 总结:

    ARC下@property参数:

      strong:用于OC对象,相当于MRC中的retain (强引用)

      weak:用于OC对象,相当于MRC中的assign  (弱引用)

      assign:用于基本数据类型,跟MRC中的assign一样。

 

1.8 ARC下的自动释放池

在ARC下,对象的工厂方法依然会将对象放入自动释放池。当池结束时,向池中的对象发送release消息。

池中的对象什么时候销毁,无法确定,因为编译器会做很多优化。

在iOS开发中,一个事件循环结束,自动释放池会释放一次,池中的对象会收到release消息。

原文地址:https://www.cnblogs.com/bossren/p/6428254.html