Effective Objective-C 2.0 读书笔记——内存管理(上)

Effective Objective-C 2.0 读书笔记——内存管理(上)

文章目录

  • Effective Objective-C 2.0 读书笔记——内存管理(上)
    • 引用计数
      • 属性存取方法中的内存管理
      • autorelease
      • 保留环
    • ARC
      • ARC必须遵循的方法命名原则
      • ARC 的自动优化:消除冗余的 autorelease 与 retain
      • 变量的内存管理语义
        • __strong(默认)
        • __weak
        • __unsafe_unretained
        • __autoreleasing
      • ARC清理实例变量
      • 覆写内存管理方法

在Objective-C中,内存管理是程序开发中不可或缺的一部分,而自引用计数( ARC)是一种自动化的内存管理技术。本文是对自动引用计数的简单学习,特此进行记录

引用计数

引用计数是一种技术,用于管理对象的引用计数,即对象被引用的次数。当一个对象的引用计数大于0时,表示该对象被持有,不可被释放;当引用计数为0时,表示对象需要被释放。

自引用计数是一种技术,用于管理对象的引用计数,即对象被引用的次数。当一个对象的引用计数大于0时,表示该对象被持有,不可被释放;当引用计数为0时,表示对象需要被释放。

文章用一个办公室关灯的例子十分贴切:

image-20240917202038743

  1. 第一个人进入办公室,“需要照明的人数” 加1。计数值从0 变成了1,因此要开灯。
  2. 之后每当有人进入办公室,“需要照明的人数” 就加1。如计数值从1变成2。
  3. 每当有人下班离开办公室,“需要照明的人数” 就减1。如计数值从2 变成1。
  4. 最后一个人下班离开办公室时,“需要照明的人数” 减1。计数值从1变成了0,因此要 关灯。

image-20240917202235863

// 生成并持有对象
id obj = [[NSObject alloc] init];// 持有对象
[obj retain];// 释放对象
[obj release];// 废弃对象
[obj dealloc];

属性存取方法中的内存管理

在手动计数,我们要使用以下方法对属性进行手动计数

- (void)setFoo:(id)foo {if (_foo != foo) {[foo retain];//对传入的新对象 foo 调用 retain,使其引用计数加 1[_foo release];//释放当前实例变量 _foo 中原来存储的对象_foo = foo;//传入的新对象 foo 赋值给实例变量 _foo}
}

当执行完 _foo = foo; 后,_foofoo 都指向同一个对象,因此对 _foo 进行 release 就相当于对该对象(也就是 foo 指向的对象)进行 release

autorelease

关于这个方法,书中给出了这个例子

- (NSString *)stringValue {NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];return str;
}

由于我们内存管理的原则——谁创建谁释放,在这个方法之中被创建的对象str必须在stringValue之中得到release,但是很明显如果在return语句执行之前进行release的话,则没有返回值。在return之后写release语句则根本不会执行,那么怎么办呢?我们可以将程序修改成以下形式

- (NSString *)stringValue {NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];return [str autorelease];  // 放入自动释放池中
}

它会在稍后释放对象,从而给调用者留下了足够长的时间 ,使其可以在需要时先保留返回值。实际上,释放操作会在清空最外层的自动释放池 (参见第 34 条)时 执行,除非你有自己的自动释放池 ,否则这个时机指的就是当前线程的下一次事件循环

保留环

image-20250208182130032

保留环顾名思义——就是呈环状相互引用的多个对象,这将导致内存泄漏,因为循环中的对象其保留计数不会降为0。对于循环中的每个对象来说,至少还有另外 一个对象引用着它 。图里的每个对象都引用了 另外两个对象之中的一个。在这个循环里,所有对象的保留计数都是1。

在垃圾收集环境中,通常将这种情况认定为"孤岛" (island of isolation),通常我们使用弱引用解决这个问题

ARC

由于ARC 会自动执行retainreleaseautorelease等操作,所以直接在ARC下调用这些内存管理方法是非法的。具体来说,不能调用下列方法:
• retain
• release
• autorelease
• dealloc
直接调用上述任何方法都会产生编译错误,因为ARC要分析何处应该自动调用内存管理方法,所以如果手工调用的话,就会干扰其工作。

ARC必须遵循的方法命名原则

拥有(retained)返回值
如果方法名以 allocnewcopymutableCopy 开头,ARC 默认认为该方法返回的对象是“拥有的”(即调用者获得一个 +1 的所有权),调用者负责在不需要时释放它。

示例:

EOCPerson *person = [[EOCPerson alloc] init];
// person 的 retain count +1,调用者需要 release
[person release];

如果方法名 不以 这些前缀开头(如 somePerson),那么返回的对象会被 autorelease,调用者不需要手动 release,否则可能会导致程序崩溃(过度释放)。


2. 示例代码解析

(1) newPerson 方法

objc复制编辑
+ (EOCPerson*) newPerson {EOCPerson *person = [[EOCPerson alloc] init];return person;
}
  • 该方法的名字 new 开头,所以它返回的对象是 归调用者所有的
  • alloc 使 personretain count +1,但调用者仍然负责在适当的时候释放 person

调用方式:

EOCPerson *personOne = [EOCPerson newPerson];
// 由于 newPerson 返回的是 "owned" 对象,调用者需要释放:
[personOne release];

(2) somePerson 方法

+ (EOCPerson*) somePerson {EOCPerson *person = [[EOCPerson alloc] init];return person;
}
  • 该方法的名字 没有以 newalloccopymutableCopy 开头,所以它返回的对象 不归调用者所有
  • ARC 会自动在返回对象上调用 autorelease,确保对象在方法返回后仍然有效,但在适当的时候自动释放。

MRC(手动引用计数) 下,它等价于:

return [person autorelease];

调用方式:

EOCPerson *personTwo = [EOCPerson somePerson];
// personTwo 被自动 autorelease,调用者不需要手动 release
  • 如果手动调用 [personTwo release],可能会导致程序崩溃!

但是如果调用此类方法想要获取一个长时间持有的对象的话(例如用这类方法赋值给一个属性),我们还是需要对这个进行retain操作

EOCPerson *tmp = [EOCPerson personWithName: @"Bob Smith"]; 
_myperson = [tmp retain];

3. 调用代码分析

-(void) doSomething {EOCPerson *personOne = [EOCPerson newPerson];EOCPerson *personTwo = [EOCPerson somePerson];
}

doSomething 方法里:

  • personOne 通过 newPerson 方法创建,归调用者所有,所以 doSomething 结束时,ARC 需要释放它(如果是 MRC,调用者需要手动 release)。
  • personTwo 通过 somePerson 方法创建,它是 autorelease 对象,不需要手动释放,ARC 会自动管理它。

在 ARC 下,这段代码执行后:

  1. personOne 在作用域结束后被 ARC 释放。
  2. personTwo 在合适的时间点自动释放。

ARC 的自动优化:消除冗余的 autorelease 与 retain

除了自动调用 retain 与 release 之外,ARC 还能进行一些手工难以实现的优化。书中举了这样一个例子:

假设有一个方法 personWithName:,它内部是这样写的:

+ (EOCPerson*) personWithName:(NSString*) name {EOCPerson *person = [[EOCPerson alloc] init];person.name = name;return objc_autoreleaseReturnValue(person);
}

关于objc_autoreleaseReturnValue(person)这个函数,是一个用于优化的函数,其具体作用如下:

检测调用者是否马上会对返回的对象调用 retain
在某些情形下(例如调用方在赋值时,因为属性是 strong,所以会自动执行一次 retain 操作),实际上调用者会对返回的对象立即执行 retain。这种情况下,原先的 autorelease 操作就显得“多余”了。

优化过程
如果检测到调用者马上会执行 retain,那么 objc_autoreleaseReturnValue 会设置一个标志位,并不真正执行 autorelease;这样就避免了不必要的 autorelease 和随后的 retain 操作。

那么对于这个方法的调用者来说,我们在调用者侧的代码如下

EOCPerson *tmp = [EOCPerson personWithName:@"Mat Galloway"];
_myPerson = objc_retainAutoreleasedReturnValue(tmp);

这里 objc_retainAutoreleasedReturnValue 的作用与前面的函数相对应:

  • 检测返回对象上是否设置了标志(表明前面已经发现“将被 retain”的情况),
  • 如果标志已置位,则直接返回对象而不执行额外的 retain 操作;
  • 否则,就调用普通的 retain。

对于这两个特殊的优化方法,书中给出他们的伪代码实现

id objc_autoreleaseReturnValue(id object) {if (/* caller will retain object */) {set_flag(object);  // 标记此对象,表明将会被 retainreturn object;} else {return [object autorelease];}
}id objc_retainAutoreleasedReturnValue(id object) {if (get_flag(object)) {clear_flag(object);return object;    // 已经标记,直接返回而不需要额外的 retain} else {return [object retain];}
}

设置与检测一个标志位的操作通常比调用 autorelease 和 retain 更高效。ARC 利用这种技术可以使得内存管理的开销降低,从而提升程序整体性能。

变量的内存管理语义

ARC也会处理局部变量与实例变量的内存管理。默认情况下,每个变量都是指向对象的强引用。一定要理解这个问题,尤其要注意实例变量的语义,因为对于某些代码来说,其语义和手动管理引用计数时不同。例如,有下面这段代码:

@interface EOCClass : NSObject (
id _object;
}
@implementation EOCClass
- (void) setup (_object = [EOCotherClass new]; }
@end

在手动管理引用计数时,实例变量_object 并不会自动保留其值,而在ARC环境下则会 这样做。也就是说,若在ARC 下编译setup 方法,则其代码会变为:

-(void) setup {id tmp = [EOCOtherClass new];_object = [tmp retain];[tmp release];
}

如果不用ARC,那么需要像下面这样来写:

-(void) setobject: (id) object {[_object release];_object = [object retain];
}	

在 ARC 下,Objective‑C 引入了几个修饰符来标识变量对对象的所有权,这些修饰符直接影响编译器如何管理变量所引用对象的内存。常见的修饰符包括:

__strong(默认)
  • 语义
    对象变量默认是 __strong 的,也就是说,当一个对象被赋值给一个 __strong 变量时,该变量会持有对象,使得对象的引用计数增加。

  • 作用
    保证只要变量存在,对象不会被销毁。当该变量离开作用域或被赋予新值时,原先引用的对象会自动释放(编译器会自动插入 release 操作)。

  • 示例

    NSObject *obj = [[NSObject alloc] init];  // obj 是 __strong 的,retain count 自动 +1
    // 当 obj 离开作用域后,编译器会自动调用 release
    
__weak
  • 语义
    声明为 __weak 的变量不会对所引用的对象进行所有权保持,即不会增加对象的引用计数。

  • 作用
    主要用于打破循环引用(例如在 delegate 或块(block)中),当所引用的对象被释放时,__weak 变量会自动置为 nil,防止野指针问题。

  • 示例:

    NSURL *url = [NSURL URLWithString:@"http://www.example.com/"];
    EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    __weak EOCNetworkFetcher *weakFetcher = fetcher;
    [fetcher startWithCompletion:^(BOOL success) {NSLog(@"Finished fetching from %@", weakFetcher.url);
    }];
    
__unsafe_unretained
  • 语义
    与 __weak 类似,也不增加引用计数,但不同的是 __unsafe_unretained 变量不会在所引用对象被销毁时自动置为 nil,因此存在野指针风险。

  • 作用
    主要用于兼容旧代码或在性能上要求极高且确信生命周期管理正确的场景中。

  • 示例:

    __unsafe_unretained NSObject *unsafeObj = someStrongObj;
    // 如果 someStrongObj 被释放,unsafeObj 不会自动置 nil,继续访问会导致崩溃
    
__autoreleasing
  • 语义
    这种修饰符通常用于方法参数,表示传入的对象在方法返回时将被放入自动释放池。

  • 作用
    用于处理输出参数,使得返回给调用者的对象不必立即释放,而是在当前自动释放池清空时被释放。

  • 示例:

    - (BOOL)error:(NSError * __autoreleasing *)error;
    

ARC清理实例变量

在使用MRC时,我们会在- (void)dealloc之中手动释放所有被持有的实例变量

- (void)dealloc {[_foo release];[_bar release];[super dealloc];
}

尽管 ARC 可以自动管理所有 Objective‑C 对象的内存,但对于非 Objective‑C 对象仍需要开发者手动清理。例如:

  • Core Foundation 对象:这些对象不受 ARC 管理,需要在 dealloc 中调用 CFRelease。
  • 由 malloc 分配的内存:这类内存同样需要手动调用 free 来释放。

在 ARC 环境下,如果你需要清理这些资源,你可以自己实现 dealloc 方法,但注意不要调用 [super dealloc],因为 ARC 会自动为你调用超类的 dealloc。示例代码可能如下:

- (void)dealloc {CFRelease(_coreFoundationObject);free(_heapAllocatedMemoryBlob);// 不要调用 [super dealloc],ARC 会自动调用超类的 dealloc
}

覆写内存管理方法

在 MRC 下,有时我们会覆写 release 方法(例如在单例中为了防止对象被释放,将 release 改成空操作),但在 ARC 下我们不被允许重写或者是直接调用内存管理方法,因为我们前面有说到ARC会执行各项的相关优化,重写或者直接调用会产生问题。ARC 通过特殊函数(如 objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue)来优化那些成对出现的 autorelease 与 retain 操作,以减少不必要的调用和提升性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/18074.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyTorch 混合精度训练中的警告处理与代码适配指南

在最近的 PyTorch 项目开发中,遇到了两个与混合精度训练相关的警告信息。这些警告主要涉及 torch.cuda.amp 模块的部分 API 已被标记为弃用(deprecated)。本文将详细介绍这些警告的原因、解决方法以及最佳实践。 警告内容 警告 1: torch.cud…

STM32自学记录(九)

STM32自学记录 文章目录 STM32自学记录前言一、DMA杂记二、实验1.学习视频2.复现代码 总结 前言 DMA 一、DMA杂记 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预&…

鸿蒙Harmony-UIAbility内状态-LocalStorage详细介绍

鸿蒙Harmony-UIAbility内状态-LocalStorage详细介绍 1.1 Localstorage的概念 LocalStorage是页面级的UI状态存储,通过Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例,LocalStorage也可以在UIAbility内,页面间共享状态 1.2 Lo…

SiliconCloud 支持deepseek,送2000w token

SiliconCloud SiliconCloud 邀请奖励持续进行,2000 万 Tokens 送不停! 邀请好友赚 2000 万 Tokens:每成功邀请一位新用户通过手机号码注册,您将获得 2000 万 Tokens;注册即送 2000 万 Tokens:受邀好友作为…

ubuntu服务器部署

关闭欢迎消息 服务器安装好 ubuntu 系统后,进行终端登录,会显示出很多的欢迎消息 通过在用户的根目录下执行 touch .hushlogin 命令,再次登录终端就不会出现欢迎消息 修改hostname显示 修改 /etc/hostname 文件内容为主机名,保…

排序算法——人无完人

没有哪一个排序方法是完美的,对于不同的需求,排序算法各有自己的优势。金无足赤,人无完人。 (这里不再重复所讲排序算法的实现,网上已有很多好的教学。) 排序方法除了依靠时间复杂度和空间复杂度来区分&am…

3D模型可视化引擎HOOPS Visualize在桌面端的支持有哪些特点?

在数字化转型日益加速的今天,工业和工程领域的专业人员面临着越来越复杂的设计和数据分析需求。3D模型可视化技术应运而生,并成为了加速设计和制造流程的关键工具。作为业界领先的3D可视化引擎之一,HOOPS Visualize提供了一系列强大的桌面端支…

傅里叶变换推导

基本模型 假设在二维直角坐标系中,可以用相互垂直的基向量和表示: 假设: 假设在上的投影为,那么: 所以: 用公式表达: 但是在实际中,基向量和不一定长度都是1,重新推导一…

数据科学之数据管理|python for office

现如今,随着计算机的逐渐普及。现代化办公成为每个职场人必备的技能,本文档就来介绍,如何使用pytohn实现自动化办公。然而,自动化办公有时并不能减少工作量。自动化办公更适合批量处理文档。单一的文件,小金不建议使用…

【前端框架】Vue3 中 `setup` 函数的作用和使用方式

在 Vue 3 里,setup 函数是组合式 API 的核心入口,为开发者提供了更灵活、高效的组件逻辑组织方式。以下为你详细介绍其作用和使用方式: 作用 1. 初始化响应式数据 在 setup 函数中,我们能够使用 ref 和 reactive 等函数来创建响…

MySQL无法连接到本地localhost的解决办法2024.11.8

问题描述:我的MySQL可以远程连接服务器,但无法连接自己的localhost。 错误提示: 2003 - Cant connet to MySQL server on localhost(10061 "Unknown error")查找问题原因: 1. 检查环境变量是否正确:发现没…

STM32HAL库快速入门教程——常用外设学习(2)

目录 一、STM32HAL库开发(8)——CubeMX配置DMA 1.1、什么是DMA? 1.2、内存内存之间的传输(单次) ​编辑 1.3、内存外设之间的传输(ADC) 二、STM32HAL库开发(9)——…

LabVIEW与小众设备集成

在LabVIEW开发中,当面临控制如布鲁克OPUS红外光谱仪这类小众专业设备的需求,而厂家虽然提供了配套软件,但由于系统中还需要控制其他设备且不能使用厂商的软件时,必须依赖特定方法通过LabVIEW实现设备的控制。开发过程中&#xff0…

PyQt组态软件 拖拽设计界面测试

PyQt组态软件测试 最近在研究PyQt,尝试写个拖拽设计界面的组态软件,目前实现的功能如下: 支持拖入控件,鼠标拖动控件位置 拖动控件边缘修改控件大小支持属性编辑器,修改当前选中控件的属性 拖动框选控件,点选控件 控…

AI如何与DevOps集成,提升软件质量效能

随着技术的不断演进,DevOps和AI的融合成为推动软件开发质量提升的重要力量。传统的DevOps已经为软件交付速度和可靠性打下了坚实的基础,而随着AI技术的加入,DevOps流程不仅能提升效率,还能在质量保障、缺陷预测、自动化测试等方面…

Mac配置Flutter开发环境

1、访问 Flutter 官网,下载安装Flutter SDK 2、将 Flutter 添加到 PATH 环境变量 找到用户文件夹中的.zshrc隐藏文件(隐藏文件显示方式:shiftcommand.),打开.zshrc文件,添加Flutter SDK路径,注…

Linux系统使用ollama本地安装部署DeepSeekR1 + open-webui

Linux系统使用ollama本地安装部署DeepSeekR1 open-webui 1. 首先,下载安装ollama #下载安装脚本并执行 curl -fsSL https://ollama.com/install.sh | sh #安装完成后查看ollama版本 ollama --version2. 使用ollama下载deepseek #不同的参数规格对硬件有不同的要…

【Kubernetes】常用命令全解析:从入门到实战(中)

🐇明明跟你说过:个人主页 🏅个人专栏:《Kubernetes航线图:从船长到K8s掌舵者》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、什么是k8s 2、K8s的核心功能 二、资…

[ComfyUI]腾讯开源黑科技Sonic,插件更新,更加可控啦

一、Sonic更新介绍 大家还记得我前分享过腾讯开源的Sonic这个项目吧,通过照片声音就可以生成非常不错的数字人开口说话的视频。 当时我就挺满意的,不过那时候输出还只能输出正方形的视频,这点就让我留有遗憾。 今天我再去翻作者的项目官网…

设计模式Python版 命令模式(上)

文章目录 前言一、命令模式二、命令模式示例 前言 GOF设计模式分三大类: 创建型模式:关注对象的创建过程,包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。结构型模式:关注类和对象之间的组合&…