OC内存管理

什么是 内存管理

  • 移动设备的内存极其有限,每个app所能占用的内存是有限制的
  • app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等
  • 管理范围
    • 任何继承了 NSObject 的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效

:存储 OC对象
:存储 非OC对象 (栈内存会被系统自动回收)

对象 的基本结构

  • 每个OC对象都有自己的引用计数器,是一个整数,表示 对象被引用的次数,即有多少人正在使用这个OC对象
  • 分配 4 个字节的存储空间,来存储引用计数器

引用计数器 的作用

  • 当使用 allocnew或者 copy 创建一个新对象时,其引用计数器 默认1
  • 当一个对象引用计数器值为0时,对象占用的内存就会被系统回收
  • 如果对象引用计数器不为 0,那么在整个程序的运行过程中,它占用的内存就不会被回收,除非整个程序已经退出

引用计数器的操作

  • 对象发送一条 retain 消息,可以使引用计数器+1retain 方法返回对象本身
  • 对象发送一条 release 消息,可以使引用计数器-1release 没有 返回值)
  • 可以给对象发送 retainCount 消息获得当前的引用计数器值

对象 的销毁

  • 当一个对象引用计数器值为0时,它将被销毁,其占用的内存被系统回收

  • 当一个对象被销毁时,系统会自动对象发送一条 dealloc 消息

  • 一般会重写 dealloc 方法,释放相关资源

  • 一旦重写了 dealloc 方法,就必须调用 [super dealloc],并且放在最后面调用

  • 不要直接调用 dealloc 方法

  • 一旦对象被回收了(就变成了僵尸对象),它占用的内存不再可用,坚持使用就导致程序崩溃(野指针错误

  • 僵尸对象:已经被销毁的的对象,不能再使用

  • 野指针:指向僵尸对象(不可用内存)的指针

  • 野指针错误EXC_BAD_ACCESS:访问了一块坏的内存(已经被回收、已经不可用内存

  • 为了避免野指针错误的常见办法

    • 在对象被销毁之后,将指向对象的指针变为空指针
  • 空指针:没有指向存储空间的指针(里面存的是nilNULL0),OC不存在空指针错误,给空指针发消息,不报错

@implementation VampireJune
// 当一个 VampireJune 对象被回收的时候,就会自动调用这个方法
- (void)dealloc
{
  NSLog(@"VampireJune 对象被回收");

  // super 的 dealloc 一定要调用,而且放在此方法的最后面
  [super dealloc];
}
@end

int main()
{
  VampireJune *v = [[VampireJune alloc] init];
  NSUInteger c = [v retainCount];
  NSLog(@"v的计数器:%ld ",c );
  
  // retain 方法 返回的是对象本身
  [v retain];

  [v release];

  // 指针 v 变成空指针
 v = nil;

  return 0;
}

内存管理原则

  • 只要还有人在用某个对象,那么这个对象不会被回收
  • 只要你想这个对象,就让对象计数器 +1
  • 当你不再使用这个对象时,就让对象计数器 -1

多对象内存管理

  • 使用某个对象,就应该让对象计数器 +1(让对象做一个 retain 操作)

  • 不想再使用某个对象,就应该让对象计数器 -1 (让对象做一次 release 操作)

  • 谁 创建,谁 release

    • 如果你通过 allocnew[mutable]copy创建一个对象,那么你必须调用 releaseautorelease
  • retain,谁 release

    • 只要你调用了 retain无论这个对象是如何生成的,你都要调用 release

总结

  • 有始有终,有
  • 曾经对象计数器 +1,就必须最后让对象的计数器 -1

内存管理代码规范

  • 只要调用了 alloc,必须有 releaseautorelease

  • 如果对象不是通过 alloc 产生的,就需要 release

  • set 方法的代码规范
    1> 基本数据类型:直接赋值

 - (void)setAge:(int)age
  {
  // 基本数据类型不需要内存管理
  _age = age;
  }

2> OC对象类型

  - (void)setVampire:(Vampire *)vam
  {
    // 1.先判断是不是新传进来的对象
    if( vam != _vam)
    {
        // 2.对旧对象做一次 release
        [_vam release];

        // 3.对新对象做一次 retain
        _vam = [vam retain];
    }
  }
  • dealloc 方法的代码规范
    • 一定要 [super dealloc],而且放到最后面
    • self(当前)所拥有的其他对象做一次 release
 - (void)dealloc
  {
    [_vam release];
    [super dealloc];
  }

@property 的参数

  • set 方法内存管理相关参数
    • retain : release 旧值,retain 新值(适用于OC对象类型)
    • assign : 直接赋值(默认,适用于非OC对象类型)
    • copy : release 旧值,copy 新值
retain : 生成的 set 方法里面, release 旧值,retain 新值
@property (retain) Book *book;
  • 是否要生成 set 方法

    • readwrite : (默认)同时生成 setter\getter 声明和实现
    • readonly : 会生成 getter 的声明、实现
  • 多线程管理

    • nonatomic : 性能高(一般用这个
    • atomic : 性能低(默认)
@property (nonatomic, retain, readwrite) Book *book;
@property (nonatomic, assign, readwrite) int num;
  • settergetter 方法的名称
    • setter : 决定了 set 方法的名称,一定要有个冒号 :
    • getter : 决定了 get方法的名称(一般用在 BOOL 类型)
    • 返回 BOOL 类型的方法名一般以 is 开头
    • @property (getter = isRich) BOOL rich
@interface VampireJune : NSObject
// setter 方法的 冒号一定不能少!!!
@property (getter = abc, setter = setAbc:) int age;
@end
int main ()
{
  VampireJune *v = [[VampireJune alloc] init];
  // setter 使用
  v.abc = 10;
  [v setAbc: 10];

   // getter 使用
  NSLog(@"p的age:%ld   %ld ",[v abc], v.abc);
}

@property 参数补充

  • nonnull: setter 和 getter 都不能为 nil (__nonnull)
  • nullable: setter 和 getter 都可以为 nil (__nullable)
  • null_resettable : setter 可以为 nil,getter 不可以为 nil
// 不能为nil
@property (nonatomic, strong, nonnull) NSArray *names;
等于
@property (nonatomic, strong) NSArray * __nonnull names;

// 可以为nil,默认情况下,不加 nullable,setter 和 getter 都是可以为 nil
// nullable 更多的作用是在于程序员之间的沟通交流(提醒同事某个属性可能是 nil)
@property (nonatomic, strong, nullable) NSArray *names;
等于
@property (nonatomic, strong) NSArray * __nullable names;

null_resettable : setter 可以为 nil,getter 不可以为 nil
@property (null_resettable, nonatomic, strong) NSArray *names;

@class 和 #import 的区别

  • 如果想使用某一个 ,只需要 #import 类的 .h 文件即可
  • @class Card 仅仅告诉编译器 Card 是一个类
    • 使用场景
    • 对于循环依赖关系来说 ,如:A 类引用 B 类,同时 B 类引用 A 类
    • 这种代码编译会报错,当使用 @calss 在两个类互相声明,就不会出现编译报错
#import "B.h"
@interface A : NSObject{
  B *b;
}
@end

#import "A.h"
@interface B : NSObject{
  A *a;
}
@end

  • 开发中引用一个的规范
    1> 在 .h 文件中用 @class 引用一个类
    2> 在 .m 文件中用 #impor 包含这个类的 .h 文件

  • 循环 retain

    • 比如 A 对象 retain 了 B 对象,B 对象 retain 了 A 对象
    • 这样会导致 A 对象 和 B 对象永远无法释放
  • 两端 循环引用解决方案
    1> 一端用 retain
    2> 一端用 assign

#import "B.h"
@interface A : NSObject{
  @property (nonatomic, retain) B *b;
}
@end

@class A.h
@interface B : NSObject{
  @property (nonatomic, assign) A *a;
}
@end

  • 作用上的区别

    • #import 会包含引用类的所有信息(内容),包含引用类的变量和方法
    • @class 仅仅告诉编译器有这么一个类,具体这个类里有什么信息,完全不知,当实现文件中真正用到的时候,才会去真正查看这个类中的信息(内容)
  • 效率上的区别

    • 如果有上百个头文件都 #import 了同一个文件,或者这些文件依次被 #import ,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,编译效率非常低
    • 相对来讲,使用 @class 方式就不会出现这种问题了
    • .m 实现文件中,如果需要引用到类的实体变量或者方法时,还需要使用 #import 引入被引用类

autorelease

  • 给某个对象发送一条 autorelease 消息时,就会将这个对象加到一个自动释放池中
  • 调用 autorelease 方法时并不会改变对象的计数器,并且会返回对象本身
  • 当池子被销毁时,会对池子里面所有对象做一次 release
  • autorelease 实际上只是把对 release 的调用延迟了,对于每一次 autorelease ,系统只是把该对象放入了当前的 autorelease pool 中,当该 pool 释放时,该 pool 中的所有对象都会被调用 release
 @autoreleasepool
 { // { 开始代表创建释放池
   VampireJune *v = [[[VampireJune alloc] init] autorelease];
 } // } 结束代表销毁释放池
  • 好处

    • 不用再关心对象释放的时间
    • 不用再关心什么时候调用 release
  • 使用注意

    • 占用内存较大的对象不要随便使用 autorelease
    • 占用内存较小的对象使用 autorelease,没有太大影响
  • 错误写法

    • alloc 之后调用了 autorelease,又调用 release
@autoreleasepool
  { 
    // 1
    VampireJune *v = [[[VampireJune alloc] init] autorelease];

    // 0
    [v release]
  }
  • 连续调用多次 autorelease
 @autoreleasepool
{ 
    VampireJune *v = [[[[VampireJune alloc] init] autorelease] autorelease];
}
  • 自动释放池
    1.在 iOS 程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在(先进后出
    2.当一个对象调用 autorelease 方法时,会将这个对象放到栈顶的释放池

  • 自动释放池的创建方式

    • iOS 5.0前
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

 [pool release]; // [pool drain];(Mac开发 上使用的)
  • iOS 5.0 开始
@autoreleasepool
{ 

}
  • 注意
    • 系统自带的方法中,如果不包含 allocnewcopy,那么这些方法返回的对象都是已经 autorelease 过的,如
      [NSString stringWithFormat:...];
      [NSDate date];

    • 开发中经常写一些类方法快速创建一个 autorelease 的对象,创建对象时不要直接使用类名,用 self

+ (id)VampireJune
{
  return [[[self alloc] init] autorelease];
}

MRC 手动管理内存

  • Manual Reference Counting

ARC 自动引用计数 是 编译器特性

  • Automatic Reference Counting

  • ARC 的判断准则

    • 只要没有强指针指向对象,就会释放对象
  • 特点

    • 不允许调用releaseretainretainCount
    • 允许重新 dealloc,但是不允许调用 [super dealloc]
    • @property的参数
      • strong : 成员变量是强指针,相当于 MRC 的 retain(适用于OC对象类型)
      • weak : 成员变量是弱指针,相当于 MRC 的 assign(适用于OC对象类型)
      • assign : 适用于 非 OC对象类型
    • 以前的 retain 改为用 strong
  • 指针

    • 强指针:默认情况下,所有的指针都是强指针 (__strong)
    • 弱指针:被 __weak 修饰的指针
// 错误的写法,没有意义的写法
__weak VampireJune *v = [[VampireJune alloc] init];
  • 注 -- ARC 转换 --
    • 如果项目是 ARC,想让部分文件支持 非ARC,去 Xcode -> Build Phases - Compile Sources 下选中相应的文件,双击或回车,写上 " -fno-objc-arc " 即可,(f = flag,标记)

    • 与 1 相反 则写上 " -f-objc-arc " 即可

循环引用

  • ARC : 一端用 strong ,另一端用 weak
  • MRC : 一端用 retain ,另一端用 assign
@class B.h
@interface A : NSObject{
  @property (nonatomic, strong) B *b;
}
@end

@class A.h
@interface B : NSObject{
  @property (nonatomic, weak) A *a;
}
@end

你可能感兴趣的:(OC内存管理)