iOS内存管理之MRC

前言:

在iOS中,使用引用计数来管理OC对象内存
一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。
调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1。

内存管理的经验总结

当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease释放它。
想拥有某个对象,就让他的引用计数+1;不想再拥有某个对象,就让他的引用计数-1。

一、 MRC 手动管理内存(Manual Reference Counting)

1、引用计数器

引用计数器:
一个整数,表示为「对象被引用的次数」。系统需要根据对象的引用计数器来判断对象是否需要被回收。

关于「引用计数器」,有以下几个特点:

  • 每个 OC 对象都有自己的引用计数器。

  • 任何一个对象,刚创建的时候,初始的引用计数为 1。

    即使用 alloc、new 或者 copy 创建一个对象时,对象的引用计数器默认就是 1。

  • 当没有任何人使用这个对象时,系统才会回收这个对象。也就是说:

    当对象的引用计数器为 0 时,对象占用的内存就会被系统回收。

    如果对象的引用计数器不为 0 时,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出)。

2、引用计数器操作

  • 为保证对象的存在,每当创建引用到对象需要给对象发送一条 retain 消息,可以使引用计数器值 +1 ( retain 方法返回对象本身)。
  • 当不再需要对象时,通过给对象发送一条 release 消息,可以使引用计数器值 -1。
  • 给对象发送 retainCount 消息,可以获得当前的引用计数器值。
  • 当对象的引用计数为 0 时,系统就知道这个对象不再需要使用了,所以可以释放它的内存,通过给对象发送 dealloc 消息发起这个过程。
  • 需要注意的是:release 并不代表销毁 / 回收对象,仅仅是将计数器 -1。
// 创建一个对象,默认引用计数器是 1
RHPerson *person1 = [[RHPerson alloc] init];
NSLog(@"retainCount = %zd", [person1 retainCount]);

// 只要给对象发送一条 retain 消息,引用计数器加1
[person1 retain];
NSLog(@"retainCount = %zd", [person1 retainCount]);

// 只要给对象发送一条 release 消息,引用计数器减1
[person1 release];
NSLog(@"retainCount = %zd", [person1 retainCount]);

// 当 retainCount 等于0时,对象被销毁
[person1 release];

NSLog(@"--------------");
2022-07-11 16:09:24.102850+0800 Interview01-内存管理[8035:264221] retainCount = 1
2022-07-11 16:09:24.103083+0800 Interview01-内存管理[8035:264221] retainCount = 2
2022-07-11 16:09:24.103126+0800 Interview01-内存管理[8035:264221] retainCount = 1
2022-07-11 16:09:24.103231+0800 Interview01-内存管理[8035:264221] -[RHPerson dealloc]
2022-07-11 16:09:24.103259+0800 Interview01-内存管理[8035:264221] --------------
Program ended with exit code: 0

3、dealloc 方法

  • 当一个对象的引用计数器值为 0 时,这个对象即将被销毁,其占用的内存被系统回收。
  • 对象即将被销毁时系统会自动给对象发送一条 dealloc 消息(因此,从 dealloc 方法有没有被调用,就可以判断出对象是否被销毁)
  • dealloc 方法的重写(注意是在 MRC 中)
    • 一般会重写 dealloc 方法,在这里释放相关资源,dealloc 就是对象的遗言
    • 一旦重写了 dealloc 方法,就必须调用 [super dealloc],并且放在最后面调用
- (void)dealloc {
    NSLog(@"%s", __func__);
    [super dealloc];
}

dealloc 使用注意:

  • 不能直接调用 dealloc 方法。

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

    4、野指针和空指针

  • 只要一个对象被释放了,我们就称这个对象为「僵尸对象(不能再使用的对象)」。

  • 当一个指针指向一个僵尸对象(不能再使用的对象),我们就称这个指针为「野指针」。

  • 只要给一个野指针发送消息就会报错(EXC_BAD_ACCESS 错误)。

RHPerson *person1 = [[RHPerson alloc] init];
[person1 release];
[person1 release];
[person1 release];
  • 为了避免给野指针发送消息会报错,一般情况下,当一个对象被释放后我们会将这个对象的指针设置为空指针。

  • 空指针:

    • 没有指向存储空间的指针(里面存的是 nil, 也就是 0)。

    • 给空指针发消息是没有任何反应的。

      RHPerson *person1 = [[RHPerson alloc] init];
      [person1 release];
      person1 = nil;
      [person1 release];
      

二、内存管理思想

1、单个对象内存管理思想

思想一:自己创建的对象,自己持有,自己负责释放
  • 通过 allocnewcopymutableCopy 方法创建并持有对象。
  • 当自己持有的对象不再被需要时,必须调用 releaseautorelease 方法释放对象。
id obj1 = [[NSObject alloc] init];
[obj1 release];

id obj2 = [NSObject new];
[obj2 release];
思想二:非自己创建的对象,自己也能持有
  • 除了用上面方法(alloc / new / copy / mutableCopy 方法)所取得的的对象,因为非自己生成并持有,所以自己不是该对象的持有者。
  • 通过调用 retain 方法,即便是非自己创建的对象,自己也能持有对象。
  • 同样当自己持有的对象不再被需要时,必须调用 release 方法来释放对象。
id obj3 = [NSArray array];
[obj3 retain];
[obj3 release];
  • 无论是否是自己创建的对象,自己都可以持有,并负责释放。
  • 计数器有加就有减。
  • 曾经让对象的计数器 +1,就必须在最后让对象计数器 -1。

2、多个对象内存管理思想

多个对象之间往往是通过 setter 方法产生联系的,其内存管理的方法也是在 setter 方法、dealloc 方法中实现的。所以只有了解了 setter 方法是如何实现的,我们才能了解到多个对象之间的内存管理思想。

#import <Foundation/Foundation.h>

#import "RHRoom.h"

NS_ASSUME_NONNULL_BEGIN

@interface RHPerson : NSObject

{
    RHRoom *_room;
}

- (void)setRoom:(RHRoom *)room;

- (RHRoom *)room;
@end

NS_ASSUME_NONNULL_END
#import "RHPerson.h"

@implementation RHPerson

- (void)setRoom:(RHRoom *)room {
    if (_room != room) {
        [_room release];
        _room = [room retain];
    }
}

- (RHRoom *)room {
    return _room;
}

- (void)dealloc {
    [_room release];
    [super dealloc];
    NSLog(@"%s", __func__);
}

@end

三、 @property 参数

  • 在成员变量前加上 @property,系统就会自动帮我们生成基本的 setter / getter 方法,但是不会生成内存管理相关的代码。
@property(nonatomic) int val;
  • 同样如果在 property 后边加上 assign,系统也不会帮我们生成 setter 方法内存管理的代码,仅仅只会生成普通的 getter / setter 方法,默认什么都不写就是 assign
@property(nonatomic, assign) int val;
  • 如果在 property 后边加上 retain,系统就会自动帮我们生成 getter / setter 方法内存管理的代码,但是仍需要我们自己重写 dealloc 方法。
@property(nonatomic, retain) RHRoom *room;

四、自动释放池

1、自动释放池

当我们不再使用一个对象的时候应该将其空间释放,但是有时候我们不知道何时应该将其释放。为了解决这个问题,Objective-C 提供了 autorelease 方法。

  • autorelease 是一种支持引用计数的内存管理方式,只要给对象发送一条 autorelease 消息,会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里面的「所有对象」做一次 release 操作。

注意:这里只是发送 release 消息,如果当时的引用计数(reference-counted)依然不为 0,则该对象依然不会被释放。

  • autorelease 方法会返回对象本身,且调用完 autorelease 方法后,对象的计数器不变。
NSObject *obj = [NSObject new];
[obj autorelease];
NSLog(@"obj.retainCount = %zd", obj.retainCount);

2、使用 autorelease 有什么好处呢?

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

3、autorelease 的原理实质上是什么?

autorelease 实际上只是把对 release 的调用延迟了,对于每一个 autorelease,系统只是把该对象放入了当前的 autorelease pool 中,当该 pool 被释放时,该 pool 中的所有对象会被调用 release 方法。

4、autorelease 的创建方法

// 第一种方式:使用 NSAutoreleasePool 创建
// 创建自动释放池
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 销毁自动释放池
[pool release];
// 第二种方式:使用 @autoreleasepool 创建
@autoreleasepool {
// 开始代表创建自动释放池
// 结束代表销毁自动释放池
}

5、autorelease 的使用方法

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
RHPerson *p = [[[RHPerson alloc] init] autorelease];
[pool release];
@autoreleasepool {
// 开始代表创建自动释放池
RHPerson *p = [[[RHPerson alloc] init] autorelease];
// 结束代表销毁自动释放池
}

6、autorelease 的注意事项

  • 并不是放到自动释放池代码中,都会自动加入到自动释放池
@autoreleasepool {
// 因为没有调用 autorelease,所以没有加入到自动释放池中
RHPerson *p = [[RHPerson alloc] init];
// 结束代表销毁自动释放池
}
  • 在自动释放池的外部发送 autorelease 不会被加入到自动释放池中
    • autorelease 是一个方法,只有在自动释放池中调用才有效。
@autoreleasepool {
}
// 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会放到释放池
Person *p = [[[Person alloc] init] autorelease];
[p run];
 
// 正确写法
@autoreleasepool {
    Person *p = [[[Person alloc] init] autorelease];
 }
 
// 正确写法
Person *p = [[Person alloc] init];
@autoreleasepool {
    [p autorelease];
}

7、自动释放池的嵌套使用

  • 自动释放池是以栈的形式存在。
  • 由于栈只有一个入口,所以调用 autorelease 会将对象放到栈顶的自动释放池。

栈顶就是离调用 autorelease 方法最近的自动释放池。

@autoreleasepool { // 栈底自动释放池
    @autoreleasepool {
        @autoreleasepool { // 栈顶自动释放池
            Person *p = [[[Person alloc] init] autorelease];
        }
        Person *p = [[[Person alloc] init] autorelease];
    }
}
  • 自动释放池中不适宜放占用内存比较大的对象。
    • 尽量避免对大内存使用该方法,对于这种延迟释放机制,还是尽量少用。
    • 不要把大量循环操作放到同一个 @autoreleasepool 之间,这样会造成内存峰值的上升。
// 内存暴涨
@autoreleasepool {
    for (int i = 0; i < 99999; ++i) {
        Person *p = [[[Person alloc] init] autorelease];
    }
}
// 内存不会暴涨
for (int i = 0; i < 99999; ++i) {
    @autoreleasepool {
        Person *p = [[[Person alloc] init] autorelease];
    }
}

8、autorelease 错误用法

  • 不要连续调用 autorelease
@autoreleasepool {
 // 错误写法, 过度释放
    Person *p = [[[[Person alloc] init] autorelease] autorelease];
 }
  • 调用 autorelease 后又调用 release(错误)。
@autoreleasepool {
    Person *p = [[[Person alloc] init] autorelease];
    [p release]; // 错误写法, 过度释放
}

五、 MRC 中避免循环引用

定义两个类 Person 类和 Dog 类

  • Person 类:
#import <Foundation/Foundation.h>
@class Dog;
 
@interface Person : NSObject
@property(nonatomic, retain)Dog *dog;
@end
  • Dog 类:
#import <Foundation/Foundation.h>
@class Person;
 
@interface Dog : NSObject
@property(nonatomic, retain)Person *owner;
@end

执行以下代码:

int main(int argc, const char * argv[]) {
    Person *p = [Person new];
    Dog *d = [Dog new];
 
    p.dog = d; // retain
    d.owner = p; // retain  assign
 
    [p release];
    [d release];
 
    return 0;
}

就会出现 A 对象要拥有 B 对象,而 B 对应又要拥有 A 对象,此时会形成循环 retain,导致 A 对象和 B 对象永远无法释放。

那么如何解决这个问题呢?

不要让 A retain B,B retain A。
让其中一方不要做 retain 操作即可。
当两端互相引用时,应该一端用 retain,一端用 assign。

原文链接:https://www.cnblogs.com/r360/p/16561589.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:iOS内存管理之MRC - Python技术站

(0)
上一篇 2023年4月18日
下一篇 2023年4月18日

相关文章

  • uniapp ios app离线打包

    前言 进行UniApp 离线打包之前, 建议先认真阅读一遍官方的开发文档。真正阅读过了,可以少踩很多坑。本文介绍离线打包前的准备及iOS工程的一些配置和需要注意的事项。 官方文档:App离线打包iOS插件开发教程iOS原生工程配置 首先,需要去UniApp官网创建一个开发者账号注册开发者登录开发者中心 账号创建成功后就可以去开发者中心创建应用了。 App的创…

    IOS 2023年4月18日
    00
  • 基于AVFoundation实现视频录制的两种方式

    目录 一、前言 二、方案一:AVCaptureSession + AVCaptureMovieFileOutput 1.创建AVCaptureSession 2.设置音频、视频输入 3.设置文件输出源 4.添加视频预览层 5. 开始采集 6. 开始录制 7.停止录制 8.停止采集 三、方案二:AVCaptureSession + AVAssetWriter …

    IOS 2023年4月18日
    00
  • C C++指针面试题零碎整理

    最基础的指针如下: int a; int* p = &a; 答:p指向a的地址,&是取a的地址。*指的是指针中取内容的符号。 2.str[]和str*的区别: char str1[] = “abc”; char str2[] = “abc”; const char str3[] = “abc”; const char str4[] = “ab…

    IOS 2023年4月18日
    00
  • Appuploader 常见错误及解决方法

    问题解决秘籍 遇到问题,第一个请登录苹果开发者官网 检查一遍账号是否有权限,是否被停用,是否过期,是否有协议需要同意,并且在右上角切换账号后检查所有关联的账号是否工作正常,apple账号的邮箱也是个重要的地方,当有ipa上传,账号有发生变化,被停用,apple经常发送一些邮件,去检查邮件通知,根据邮件通知修改调整。只有账号正常没问题,再考虑是否软件哪个地方操…

    IOS 2023年4月18日
    00
  • ios animation 动画学习总结

    目录 一、前言 二、UIView Animation 2.1 简单动画 2.2 关键帧动画 2.3 View 的转换 三、CALayer Animation 一、前言 动画一直是 iOS 开发中很重要的一部分。设计良好,效果炫酷的动画往往能对用户体验的提升起到很大的作用,在这里将自己学习 iOS 动画的体会记录下来,希望能对别人有所帮助。 iOS 的动画框架…

    IOS 2023年4月18日
    00
  • 新人必看| 移动端“动态化”是什么意思?

    在移动开发领域,为了让APP保持最新的版本,同时让业务开发变得更加快捷,动态化技术极其重要。今天就来聊聊移动端动态和开发的由来和各流派的优缺点。 移动端动态化的由来 “动态化”并不是最近几年才产生的名词,而是从从互联网诞生的初期,这个词就已经出现了。大家所认知的早期互联网,其实就是各种各类的“动态网站”,内容数据和页面外观都不是固定的,都是随着服务器端的更新…

    IOS 2023年4月17日
    00
  • ios apns推送 离线锁屏语音播报

    一、背景 公司正在研发的一款App,需要在进行消息推送时支持语音播报。 具体要求: 离线:App在用户未打开时,可收到消息推送 锁屏:用户在设备锁屏状态下,仍可收到消息推送 语音播报:收到消息推送时可同时进行语音播放 苹果的APNs消息推送, 支持在应用未打开及设备锁屏状态下收到推送。 而同时进行语音播报,则需要做一些特殊处理。 目前语音播报的场景有两种: …

    IOS 2023年4月25日
    00
  • iOS 审核浅谈:1.4.1、2.1、2.5.2、2.5.4、4.2.3、5.2.5

    整理下近期被 Apple 残忍虐待的成果。   ps: 可以提供一个视频链接,建议用微软的OneDrive 。审核员方便点。国内那些个地址都需要登录,需要登录才能看视频的场景,同样会被拒      Guideline 1.1 – Safety – Objectionable Content Guideline 1.1 – Safety – Objection…

    IOS 2023年4月18日
    00
合作推广
合作推广
分享本页
返回顶部