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时,表示对象需要被释放。
文章用一个办公室关灯的例子十分贴切:
- 第一个人进入办公室,“需要照明的人数” 加1。计数值从0 变成了1,因此要开灯。
- 之后每当有人进入办公室,“需要照明的人数” 就加1。如计数值从1变成2。
- 每当有人下班离开办公室,“需要照明的人数” 就减1。如计数值从2 变成1。
- 最后一个人下班离开办公室时,“需要照明的人数” 减1。计数值从1变成了0,因此要 关灯。
// 生成并持有对象
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;
后,_foo
和 foo
都指向同一个对象,因此对 _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 条)时 执行,除非你有自己的自动释放池 ,否则这个时机指的就是当前线程的下一次事件循环
保留环
保留环顾名思义——就是呈环状相互引用的多个对象,这将导致内存泄漏,因为循环中的对象其保留计数不会降为0。对于循环中的每个对象来说,至少还有另外 一个对象引用着它 。图里的每个对象都引用了 另外两个对象之中的一个。在这个循环里,所有对象的保留计数都是1。
在垃圾收集环境中,通常将这种情况认定为"孤岛" (island of isolation),通常我们使用弱引用解决这个问题
ARC
由于ARC 会自动执行retain
、release
、autorelease
等操作,所以直接在ARC下调用这些内存管理方法是非法的。具体来说,不能调用下列方法:
• retain
• release
• autorelease
• dealloc
直接调用上述任何方法都会产生编译错误,因为ARC要分析何处应该自动调用内存管理方法,所以如果手工调用的话,就会干扰其工作。
ARC必须遵循的方法命名原则
拥有(retained)返回值
如果方法名以 alloc
、new
、copy
或 mutableCopy
开头,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
使person
的 retain count +1,但调用者仍然负责在适当的时候释放person
。
调用方式:
EOCPerson *personOne = [EOCPerson newPerson];
// 由于 newPerson 返回的是 "owned" 对象,调用者需要释放:
[personOne release];
(2) somePerson 方法
+ (EOCPerson*) somePerson {EOCPerson *person = [[EOCPerson alloc] init];return person;
}
- 该方法的名字 没有以
new
、alloc
、copy
或mutableCopy
开头,所以它返回的对象 不归调用者所有。 - 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 下,这段代码执行后:
personOne
在作用域结束后被 ARC 释放。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 操作,以减少不必要的调用和提升性能。
相关文章:

Effective Objective-C 2.0 读书笔记——内存管理(上)
Effective Objective-C 2.0 读书笔记——内存管理(上) 文章目录 Effective Objective-C 2.0 读书笔记——内存管理(上)引用计数属性存取方法中的内存管理autorelease保留环 ARCARC必须遵循的方法命名原则ARC 的自动优化࿱…...

蓝桥杯-洛谷刷题-day5(C++)(为未完成)
1.P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布 i.题目 ii.代码 #include <iostream> #include <string> using namespace std;int N, Na, Nb; //0-"剪刀", 1-"石头", 2-"布", 3-"蜥", 4-"斯"࿱…...

conda 修复 libstdc++.so.6: version `GLIBCXX_3.4.30‘ not found 简便方法
ImportError: /data/home/hum/anaconda3/envs/ipc/bin/../lib/libstdc.so.6: version GLIBCXX_3.4.30 not found (required by /home/hum/anaconda3/envs/ipc/lib/python3.11/site-packages/paddle/base/libpaddle.so) 1. 检查版本 strings /data/home/hum/anaconda3/envs/ipc/…...

数据结构之队列,哈希表
一 队列(先进先出) 1.定义:从一端进行数据插入,另一端进行删除的线性存储结构 队列类型 常见操作 - 入队(Enqueue):将新元素添加到队列的尾部。若队列有空间,新元素会成为队列的新尾部元素;若…...

讯方·智汇云校华为授权培训机构的介绍
官方授权 华为授权培训服务伙伴(Huawei Authorized Learning Partner,简称HALP)是获得华为授权,面向公众(主要为华为企业业务的伙伴/客户)提供与华为产品和技术相关的培训服务,培养华为产业链所…...

【16届蓝桥杯寒假刷题营】第1期DAY4
1.披萨和西蓝花 - 蓝桥云课 1. 披萨和西蓝花 问题描述 在接下来的 N 天里(编号从 1 到 N),坤坤计划烹饪披萨或西兰花。他写下一个长度为 N 的字符串 A,对于每个有效的 i,如果字符 Ai 是 1,那么他将在第 i…...
【Linux】cron计划任务定时执行命令
在Linux系统中,crontab 是一种用于设置周期性执行任务的工具,通过编辑 crontab 文件,用户可以指定在特定时间自动运行命令或脚本。以下是关于 crontab 的详细介绍: 1. crontab 基本结构 每个 crontab 任务由一行配置组成…...
rdian是一个结构体,pdian=^Rdian,list泛型做什么用?
不明白不让编译的原因,记录下之遇到注意原油。 var mylist:TList<string>; mylist1:TList<Pdian>; mydian:Pdian; i:Integer; mylist2:TList<Rdian>; mydian2:rdian; arr:array of Rdian; begin mylist:TList…...
【05】RUST错误处理
文章目录 错误处理panic代码运行ResutResult中的一些方法介绍传播错误`?`运算符错误处理 建议是尽量用Result由调用者自行决定是否恢复,不恢复也可直接在Err中调用panic。代码分支不可能走的分支可panic。 需要panic的情况: 有害状态:当一些假设、保证、协议或不可变性被打…...

WinForm 防破解、反编译设计文档
一、引言 1.1 文档目的 本设计文档旨在阐述 WinForm 应用程序防破解、反编译的设计方案,为开发团队提供详细的技术指导,确保软件的知识产权和商业利益得到有效保护。 1.2 背景 随着软件行业的发展,软件破解和反编译现象日益严重。WinForm…...

1 推荐系统概述
推荐系统概述 1 推荐系统的意义平台方信息生产者(物品)信息消费者(用户)推荐和搜索的区别 2 推荐系统架构系统架构算法架构 3 推荐系统技术栈算法画像层召回/粗排精排重排序 工程 1 推荐系统的意义 信息生产者(平台方…...

Redis初阶笔记
1. 认识Redis Redis是一个基于内存运行的缓存中间件,有着多种的数据类型可供使用。Redis的使用主要是为关系性数据库(MySQL等)分担压力,在高并发环境下MySQL执行命令的压力是很大的,容易宕机,所以需要中间件…...

electron.vite 项目创建以及better-sqlite3数据库使用
1.安装electron.vite npm create quick-start/electronlatest中文官网:https://cn.electron-vite.org/ 2. 安装项目依赖 npm i3.修改 electron-builder 配置文件 appId: com.electron.app productName: text33 directories:buildResources: build files:- !**/.v…...

【新品解读】AI 应用场景全覆盖!解码超高端 VU+ FPGA 开发平台 AXVU13F
「AXVU13F」Virtex UltraScale XCVU13P Jetson Orin NX 继发布 AMD Virtex UltraScale FPGA PCIE3.0 开发平台 AXVU13P 后,ALINX 进一步研究尖端应用市场,面向 AI 场景进行优化设计,推出 AXVU13F。 AXVU13F 和 AXVU13P 采用相同的 AMD Vir…...

Proxmox VE 8.3 qm 方式导入ESXi Linux OVA UEFI模式虚拟机
前言 实现esxi ova uefi 虚拟机导入到pve,Linux UEFI 都支持 创建一个105虚拟机 qm 参数使用参考,以下可以根据自己的实际情况执行调整 esxi 导出虚拟机参考 #vmid (100 - 999999999) vmid=105# qm vm name...

OpenAI 放王炸,将发布整合多项技术的 GPT-5,并免费无限使用,该模型有哪些技术亮点
对于 ChatGPT 的免费用户,将可以无限制地访问 GPT-5,但仅限于标准的智能级别。该级别会设定滥用限制,以防止不当使用(意思就是你得付费嘛)。 OpenAI CEO Sam Altman 今天在 X 上透露了 GPT-4.5 和 GPT-5 的最新发展计划。 OpenAI 将发布代…...
【前端框架与库】「深入理解 Vue 插槽」:类型、用法与实际场景解析,增强组件复用性的利器
深入理解 Vue 插槽 [TOC](深入理解 Vue 插槽) 前言一、插槽的几种类型1. 默认插槽(Default Slot)2. 具名插槽(Named Slot)3. 作用域插槽(Scoped Slot) 二、插槽的作用与实际使用场景三、延伸知识总结 前言 …...

对比 LVS 负载均衡群集的 NAT 模式和 DR 模式,比较其各自的优势 与基于 openEuler 构建 LVS-DR 群集
一、 对比 LVS 负载均衡群集的 NAT 模式和 DR 模式,比较其各自的优势 NAT 模式 部署简单:NAT 模式下,所有的服务器节点只需要连接到同一个局域网内,通过负载均衡器进行网络地址转换,就可以实现负载均衡功能。不需要对…...

matplotlib绘制频率分布直方图
1.给了数据,让统计这些数据的分布 from matplotlib import pyplot as plt from matplotlib import rcParams import random as r# 直方图用来统计每个区间数量多少rcParams[font.sans-serif] [SimHei] rcParams[axes.unicode_minus] Falseplt.figure(figsize(20,8), dpi80)#…...

相得益彰,Mendix AI connector 秒连DeepSeek ,实现研发制造域场景
在当今快速发展的科技领域,低代码一体化平台已成为企业数字化转型的关键工具,同时,大型语言模型(LLM)如 DeepSeek 在自动生成代码和提供智能建议方面表现出色。 Mendix 于近期发布的 GenAI 万能连接器,目前…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...