欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OC内存管理

程序员文章站 2024-01-14 22:55:22
...

什么是 内存管理

  • 移动设备的内存极其有限,每个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