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

news2025/4/21 16:53:30

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);  // 标记此对象,表明将会被 retain
        return 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.coloradmin.cn/o/2297856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试覆盖率详解

🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 一、覆盖率概念 覆盖率是用来度量测试完整性的一个手段,是测试技术有效性的一个度量。分为:白盒覆盖、灰盒覆盖和黑盒覆盖;测…

控制玉米株高基因 PHR1 的基因克隆

https://zwxb.chinacrops.org/CN/10.3724/SP.J.1006.2024.33011

windows10本地的JMeter+Influxdb+Grafana压测性能测试,【亲测,避坑】

一、环境,以下软件需要解压、安装到电脑上。 windows10 apache-jmeter-5.6.3 jdk-17.0.13 influxdb2-2.7.11 grafana-enterprise-11.5.1二、配置Influxdb,安装完默认连接http://localhost:8086/。打开连接,配置如下。 开启Influxdb&#xf…

如何在 Java 后端接口中提取请求头中的 Cookie 和 Token

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119qq.com] 📱个人微信&a…

【Python网络爬虫】爬取网站图片实战

【Python网络爬虫】爬取网站图片实战 Scrapying Images on Website in Action By Jackson@ML *声明:本文简要介绍如何利用Python爬取网站数据图片,仅供学习交流。如涉及敏感图片或者违禁事项,请注意规避;笔者不承担相关责任。 1. 创建Python项目 1) 获取和安装最新版…

SAP ABAP VA05增强

SE18 输入增强的BADI名称:BADI_SDOC_WRAPPER 进入后,点击Interface。 进入后,点击显示对象清单。 双击增强类,下面有之前做好的增强类,没有的可以自己创建一个。 IF_BADI_SDOC_WRAPPER~ADAPT_RESULT_COMP 代码 METHOD if_badi_sdoc_wrapper~adapt_result_comp."…

八大排序——简单选择排序

目录 1.1基本操作: 1.2动态图: 1.3代码: 代码解释 1. main 方法 2. selectSort 方法 示例运行过程 初始数组 每轮排序后的数组 最终排序结果 代码总结 1.1基本操作: 选择排序(select sorting)也…

【清晰教程】本地部署DeepSeek-r1模型

【清晰教程】通过Docker为本地DeepSeek-r1部署WebUI界面-CSDN博客 目录 Ollama 安装Ollama DeepSeek-r1模型 安装DeepSeek-r1模型 Ollama Ollama 是一个开源工具,专注于简化大型语言模型(LLMs)的本地部署和管理。它允许用户在本地计算机…

【matlab优化算法-17期】基于DBO算法的微电网多目标优化调度

基于蜣螂DBO算法的微电网多目标优化调度 一、前言 微电网作为智能电网的重要组成部分,其优化调度对于降低能耗、减少环境污染具有重要意义。本文介绍了一个基于Dung Beetle Optimizer(DBO)算法的微电网多目标优化调度项目,旨在通…

如何使用qt开发一个xml发票浏览器,实现按发票样式显示

使用Qt开发一个按发票样式显示的XML发票浏览器,如下图所示样式: 一、需求: 1、按税务发票样式显示。 2、拖入即可显示。 3、正确解析xml文件。 二、实现 可以按照以下步骤进行: 1. 创建Qt项目 打开Qt Creator,创…

解析 JavaScript 面试题:`index | 0` 确保数组索引为整数

文章目录 一、JavaScript 中的数字类型二、按位或运算符 | 的作用(一)对于整数(二)对于小数(三)对于非数字值 三、用于数组索引的意义 在 JavaScript 面试中,常常会涉及到一些看似简单却蕴含着深…

46 map与set

目录 一、序列式容器和关联式容器 二、set系列的使用 (一)set和mutilset参考文档链接 (二)set类模板介绍 1、set类声明 2、set的构造和迭代器 3、set的增删查 (三)multiset类模板 1、multiset和se…

RAGFlow和Dify对比

‌ RAGFlow和Dify都是基于大语言模型(LLM)的应用开发平台,具有相似的功能和应用场景,但它们在技术架构、部署要求和用户体验上存在一些差异。‌‌ RAGFlow和Dify对比 2025-02-13 22.08 RAGFlow‌ ‌技术栈‌:RAGFlow…

Dart 3.5语法 14-16

017自定代码段让变量有默认值 List下标访问和2种for循环遍历_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1RZ421p7BL?spm_id_from333.788.videopod.episodes&vd_source68aea1c1d33b45ca3285a52d4ef7365f&p42原作者链接,此为修订补充版本 014main…

yanshee机器人初次使用说明(备注)-PyCharm

准备 需要: 1,(优必选)yanshee机器人Yanshee 开发者说明 2,手机-联网简单操控 / HDMI线与显示器和键鼠标-图形化开发环境 / 笔记本(VNC-内置图形化开发环境/PyCharm等平台)。 3,P…

面试题:如何在10亿个数中判断某个数是否存在?

参考视频 参考视频: 如何用10只老鼠试出藏在99瓶清水中的那瓶毒药 参考视频

【设计模式】【行为型模式】观察者模式(Observer)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 👍 欢迎点赞、收藏、关注,跟上我的更新节奏 🎵 当你的天空突…

[创业之路-299]:图解金融体系结构

一、金融体系结构 1.1 概述 金融体系结构是一个国家以行政的、法律的形式和运用经济规律确定的金融系统结构,以及构成这个系统的各种类型的银行和非银行金融机构的职能作用和相互关系。以下是对金融体系结构的详细分析: 1、金融体系的构成要素 现代金…

STM32、GD32驱动TM1640原理图、源码分享

一、原理图分享 二、源码分享 /************************************************* * copyright: * author:Xupeng * date:2024-07-18 * description: **************************************************/ #include "smg.h"#define DBG_TAG "smg&…

框架ThinkPHP(小迪网络安全笔记~

免责声明:本文章仅用于交流学习,因文章内容而产生的任何违法&未授权行为,与文章作者无关!!! 附:完整笔记目录~ ps:本人小白,笔记均在个人理解基础上整理,…