【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(二)

自动引用计数

  • 前言
  • ARC规则
    • 所有权修饰符
      • **__strong修饰符**
      • __weak修饰符
      • __unsafe_unretained修饰符
      • __autoreleasing修饰符
    • 规则
    • 属性
    • 数组

前言

上一篇我们主要学习了一些引用计数方法的内部实现,现在我们学习ARC规则。


ARC规则

所有权修饰符

OC中,为了处理对象,可以将变类型定义为id类型或各种对象类型。

对象类型: 即OC类的指针,例如“NSObject* ”
id类型: 用于隐藏对象类型的类名部分,相当于C语言中的(void *)

ARC有效时,id类型和对象类型同C语言其他类型不同,必须附加上所有权修饰符

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

__strong修饰符

__strong修饰符是id类型和对象类型默认的所有权修饰符。也就是说

id obj = [[NSObject alloc] init];id __strong obj = [[NSObject alloc] init];

这两种代码是一样的。

但是,当ARC无效时,该如何实现__strong修饰符呢。

{id obj = [[NSObject alloc] init];[obj release]
}

如上述代码所示,附有__strong修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。

因此,我们可以通过在最后调用release代码,实现这一功能。

如“strong”所示,__strong修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时废弃。随着强引用的失效,引用的对象会随之释放。

对于自己生成并持有对象的源代码来说,对象的所有者和对象的生存周期都是明确的,那么如果是取得非自己生成并持有的对象呢。

{id__strong obj = [NSMutableArray array];
}

这里我们通过NSMutableArray类的array类方法学习。

{//取得非自己生成并持有的对象id __strong obj = [NSMutableArray array];//变量obj为强引用,所以自己持有对象。
}
//变量obj超出其作用于,强引用失效,自动释放自己持有的对象。

可见取得非自己生成但是持有的对象的生存周期也是明确的

即使是OC类成员变量,也可以在方法参数上,使用附有__strong修饰符的变量。

@interface Test : NSObject
{id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end@implementation Test
- (id)init
{self = [super init];return self;
}
- (void)setObject:(id __strong)obj
{obj_ = obj;
}
@end

下面我们进行使用:

{id __strong test = [[Test alloc] init];//test持有Test对象的强引用[test setObject:[[NSObject alloc] init];//Test对象的obj_成员,持用NSObjcet对象的强引用。
}
/*因为test变量超出其作用域,强引用失效所以自动释放Test对象。Test对象的所有者不存在,因此废弃该对象。废弃Test对象的同时,Test对象的obj_成员也被废弃,NSObjcet对象的强引用失效自动释放NSObjcet对象所有者不存在,废弃该对象。*/

通过这种方法,无需额外工作便可以使用于类成员变量以及方法参数中。

修饰符可以保证将附有这些修饰符的自动变量初始化为nil。

id __strong ojb0;
//这两种初始化方式相同
id __strong obj0 == nil;

通过__strong修饰符,不必再次键入retain或者release即可实现OC内存管理的思考方式。

并且,id类型和对象类型的所有权修饰符默认为__strong修饰符,所以不需要写上"__strong"。这一设定使得ARC有效以及简单的编程遵循了OC内存管理的思考方式。

__weak修饰符

如果仅使用__strong修饰符,容易发生循环引用的问题,这对项目是毁灭性的。
如以下这种情况:

{id test0 = [[Test alloc] init];//test0持有Test对象A的强引用id test1 = [[Test alloc] init];//test1持有Test对象B的强引用[test0 setObject:test1];/*Test对象A的obj_成员变量持用Test对象B的引用此时,持有Test对象B的强引用的变量为Test对象A的obj_和test1。*/[test1 setObject:test0];/*Test对象B的obj_成员变量持用Test对象A的引用此时,持有Test对象A的强引用的变量为Test对象B的obj_和test0。*/
}/*
因为 test0 变量超出其作用域,强引用失效,
所以自动释放 Test 对象 A。
因为 test1 变量超出其作用域,强引用失效,
所以自动释放 Test 对象 B。
此时,持有 Test 对象 A 的强引用的变量为
Test 对象 B 的 obj_。
此时,持有 Test 对象 B 的强引用的变量为
Test 对象 A 的 obj_。
发生内存泄漏!
*/

如下图所示:
请添加图片描述
循环引用容易发生内存泄漏:即应当废弃的对象在超出其生存周期后继续存在。
上述代码分别将对象A赋给test0,对象B赋给test1后,在超出作用域后无法正确被释放。

为了避免以上这种情况,我们可以采用__weak修饰符。

__weak修饰符:提供弱引用,不能持有对象实例。

id __weak obj = [[NSObject alloc] init];

会出现以下警告。
请添加图片描述

变量 obj 持有对持有对象的弱引用。因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。

如果使用以下代码,将对象赋值给附有__strong修饰符的变量之后,在赋值附有__weak修饰符的变量,就不会发生警告。

{//自己生成并且持有对象id __strong obj0 = [[NSObject alloc] init];//obj0变量为强引用,所以自己持有对象id __weak obj1 = obj2;//obj1变量持有生成对象的弱引用
}
//因为obj0变量超出其作用域,强引用失效,所以自动释放自己持有的对象
//因为对象的所有者不在,所以会自动废弃obj1

因此上述代码只需要将可能发生循环引用的类成员变量改成附有__weak修饰符的成员变量,即可避免循环引用的问题。如下修:

@interface Test : NSObject
{id __weak obj_;
}
- (void)setObject:(id __strong)obj;
@end

此时对象引用情况如图所示:
请添加图片描述
__weak修饰符还有另一优点:在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效切处于nil被赋值的状态(空弱引用)。

id __weak obj1 = nil;{id __strong obj0 = [[NSObject alloc] init];obj1 = obj0;NSLog(@"A: %@", obj1);}/*obj0变量超出其作用域,强引用失效所以自动释放自己持有的对象因为对象无持用者,所以废弃该对象废弃对象的同时持有该对象弱引用的obj1变量的弱引用失效,nil赋值给obj1*/
NSLog(@"B: %@", obj1);

源代码的结果如下:
请添加图片描述
像这样,使用__weak修饰符即可避免循环引用。通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。

__unsafe_unretained修饰符

__unsafe_unretained修饰符正如其名,是不安全的所有权修饰符。
附有该修饰符的变量不属于编译器的内存管理对象。
与附有__weak修饰符的变量一样,因此自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即释放。但是当废弃时并不会自动置nil。

id __unsafe_unretained obj1 = nil;{id __strong obj0 = [[NSObject alloc] init];obj1 = obj0;NSLog(@"A: %@", obj1);}
NSLog(@"B: %@", obj1);

以上代码偶尔会运行成功,但更多情况下访问一个空对象会报错。

__autoreleasing修饰符

在 ARC 有效时,用 @autoreleasepool 块替代 NSAutoreleasePool 类,用附有 __autoreleasing 修饰符的变量替代 autorelease 方法

/* ARC无效 */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];/*  有效 */
@autoreleasepool {id __autoreleasing obj = [[NSObject alloc] init];
}

请添加图片描述
但是,通常情况下我们不会显示的附加__autoreleasing修饰符和__strong修饰符。

当使用alloc/new/copy/mutableCopy以外的方法来取得丢下时,该对象会自动被注册到autorelease方法中。

访问附有__weak修饰符的变量时,必须访问注册到autoreleasepool的对象。这是因为__weak修饰符纸持有对象的弱引用,而对象有可能被废弃,但是如果把要访问的对象注册到autoreleasepool中,在@autoreleasepool块结束之前都能确保该对象存在。因此:
使用附有__weak修饰符的变量时必定要使用注册到autoreleasepool中的对象

当我们显示的制定__autoreleasing修饰符时,必须注意对象变量要为自动变量(包括局部变量,函数以及方法参数)

无论 ARC 是否有效,调试用的非公开函数 _objc_autoreleasePoolPrint()都可使用。

该函数可以用于打印当前自动释放池中的所有对象信息。

规则

当ARC有效时,需要遵守的规则:

  • 不能使用 retain/release/retainCount/autorelease
  • 不能使用 NSAllocateObject/NSDeallocateObject
  • 须遵守内存管理的方法命名规则
  • 不要显式调用 dealloc
  • 使用 @autoreleasepool 块替代 NSAutoreleasePool
  • 不能使用区域(NSZone
  • 对象型变量不能作为 C 语言结构体(struct/union)的成员
  • 显式转换 “id” 和 “void *

不能使用 retain/release/retainCount/autorelease

内存管理是编译器的工作,因此没必要使用内存管理的方法。

设置ARC有效时,无需(禁止)再次键入retain或release代码。

实际上,再次键入retain和release代码时会报错,所以应该是禁止键入。
同样的,retainCount和release也会引起编译错误。

不能使用 NSAllocateObject/NSDeallocateObject

在ARC有效时,禁止使用NSAllocateObject函数。同retain方法一样,会引起编译报错。同一释放对象的NSDeallocateObject函数也不可使用。

须遵守内存管理的方法命名规则
当ARC无效时,用于对象生成/持有的方法必须遵守以下的命名规则。

使用alloc/new/copy/mutableCopy时,必须返回给调用方所应当持有的对象。

但是当ARC有效时,init开始的方法必须是实例方法,并且要返回对象。返回的对象应为id类型或该方法声明类的对象类型,或者是该类型的父类或者子类。该返回对象并不注册到autoreleasepool上。基本知识对alloc方法返回值的对象进行初始化处理并返回该对象。如下所示:

-(void) initWithObject:(id) obj;

对象型变量不能作为 C 语言结构体(struct/union)的成员

struct Data {NSMutableArray *array;
};

以上代码会报错

显式转换 “id” 和 “void *

//id和void*互转时需要通过__bridge转换id obj = [[NSObject alloc] init];void *p = (__bridge void *)obj;id o = (__bridge id)p;

__bridge_retained 转换可使要转换赋值的变量也持有所赋值的对象。

/* ARC无效 */
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

__bridge_retained 转换变为了 retain。变量 obj 和变量 p 同时持有对象。

void *p = 0;
{id obj = [[NSObject alloc] init];p = (__bridge_retained void *)obj;
}
NSLog(@"class=%@", [(__bridge id)p class]);

变量作用域结束时,虽然随着持有强引用的变量 obj 失效,对象随之释放,但由于 __bridge_retained 转换使变量 p 看上去处于持有该对象的状态,因此该对象不会被废弃。

__bridge_transfer 转换提供与此相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。

id obj = (__bridge_transfer id)p;
//上述代码在ARC无效时如下表达:
/* ARC无效 */
id obj = (id)p;
[obj retain];
[(id)p release];

同 __bridge_retained 转换与 retain 类似,__bridge_transfer 转换与 release 相似。在给 id obj 赋值时 retain 即相当于 __strong 修饰符的变量。

属性

当ARC有效时,以下可作为这种属性声明中使用的属性来用

请添加图片描述
以上各种属性赋值给指定的属性中就相当于赋值给附加各属性对应的所有权修饰符的变量中。

只有 copy 属性不是简单的赋值,它赋值的是通过 NSCopying 接口的 copyWithZone: 方法复制赋值源所生成的对象。

另外,在声明类成员变量时,如果同属性声明中的属性不一致则会引起编译错误。比如下面这种情况。

id obj;//默认为__strong@property (nonatomic, weak) id obj;
//会出现报错//需要改成以下形式
id __weak obj;

数组

使用修饰符赋值数组的使用与变量相同。

id objs[10];id __weak objs[10];

__unsafe_unretained 修饰符以外的 __strong/__weak/__autoreleasing 修饰符保证其指定的变量初始化为 nil。同样地,附有 __strong/__weak/__autoreleasing 修饰符变量的数组也保证其初始化为 nil
下面我们就来看看数组中使用附有 __strong 修饰符变量的例子。

{id objs[2];objs[0] = [[NSObject alloc] init];objs[1] = [NSMutableArray array];
}

数组超出其变量作用域时,数组中各个附有 __strong 修饰符的变量也随之失效,其强引用消失,所赋值的对象也随之释放。这与不使用数组的情形完全一样。

将附有 __strong 修饰符的变量作为动态数组来使用时又如何呢?在这种情况下,根据不同的目的选择使用 NSMutableArrayNSMutableDictionaryNSMutableSet 等 Foundation 框架的容器。这些容器会恰当地持有追加的对象并为我们管理这些对象。

像这样使用容器虽然更为合适,但在 C 语言的动态数组中也可以使用附有 __strong 修饰符的变量,只是必须要遵守一些事项。以下按顺序说明。

声明动态数组用指针。

id __strong *array = nil;

声明动态数组时,我们需要显式的指定为__strong修饰符。

id __strong *array = nil;

由于 “id * 类型” 默认 为 “id __autoreleasing * 类型”,所以有必要显式指定为 strong 修饰符。另外,虽然保证了附有 __strong 修饰符的 id 型变量被初始化为 nil,但并不保证附有 __strong 修饰符的 id 指针型变量被初始化为 nil

使用类名如下述描述:

NSObject * __strong *array = nil;

其次,使用 calloc 函数确保想分配的附有 __strong 修饰符变量的容量占有的内存块。

array = (id __strong *)calloc(entries, sizeof(id));

该源代码分配了 entries 个所需的内存块。由于使用附有 __strong 修饰符的变量前必须先将其初始化为 nil,所以这里使用使分配区域初始化为 0 的 calloc 函数来分配内存。不使用 calloc 函数,在用 malloc 函数分配内存后可用 memset 等函数将内存填充为 0。

但是,像下面的源代码这样,将 nil 代入到 malloc 函数所分配的数组各元素中来初始化是非常危险的。

array = (id __strong *)malloc(sizeof(id) * entries);
for (NSUInteger i = 0; i < entries; ++i)array[i] = nil;

这是因为由 malloc 函数分配的内存区域没有被初始化为 0,因此 nil 会被赋值给附有 __strong 修饰符的并被赋值了随机地址的变量中,从而释放一个不存在的对象。在分配内存时推荐使用 calloc 函数。

像这样,通过 calloc 函数分配的动态数组就能完全像静态数组一样使用。

array[0] = [[NSObject alloc] init];

但是,在动态数组中操作附有 __strong 修饰符的变量与静态数组有很大差异,需要自己释放所有的元素。

当我们要废弃数组时,不能如下直接free。会使数组各元素的值的对象无法释放,引起内存泄漏。如下述代码所示。

free(array);

这是因为:在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理

如以下源代码所示,一定要将 nil 赋值给所有元素中,使得元素所赋值对象的强引用失效,从而释放那些对象。在此之后,使用 free 函数废弃内存块。

for (NSUInteger i = 0; i < entries; ++i)array[i] = nil;
free(array);

同初始化时的注意事项相反,即使用 memset 等函数将内存填充为 0 也不会释放所赋值的对象。这非常危险,只会引起内存泄漏。对于编译器,必须明确地使用赋值给附有 __strong 修饰符变量的源代码。所以请注意,必须将 nil 赋值给所有数组元素。

并且,memcpy和realloc函数也会有危险,因为数组元素所赋值的对象有可能被保留在内存中或是重复被废弃,所以也禁止使用。


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

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

相关文章

可信空间数据要素解决方案

可信空间数据要素解决方案 一、引言 随着数字经济的蓬勃发展&#xff0c;数据已成为重要的生产要素。可信空间数据要素解决方案旨在构建一个安全、可靠、高效的数据流通与应用环境&#xff0c;促进数据要素的合理配置和价值释放&#xff0c;推动各行业的数字化转型和创新发展…

mysql删除表后重建表报错Tablespace exists

版本 mysql:8.0.23 复现步骤 1、删除表 DROP TABLE IF EXISTS xxx_demo; 2、新建表 CREATE TABLE xxx_demo (id bigint NOT NULL AUTO_INCREMENT COMMENT 主键id,creator varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT COMMENT 创建者,c…

【Leetcode-Hot100】缺失的第一个正数

题目 解答 有一处需要注意&#xff0c;我使用注释部分进行交换值&#xff0c;报错&#xff1a;超出时间限制。有人知道是为什么吗&#xff1f;难道是先给nums[i]赋值后&#xff0c;从而改变了后一项的索引&#xff1f; class Solution(object):def firstMissingPositive(sel…

从单模态到多模态:五大模型架构演进与技术介绍

前言 1. ResNet — 残差神经网络背景核心问题与解决方案原理模型架构ResNet 系列变体技术创新与影响 2. ViT — Vision Transformer背景核心思想发展历程Transformer的起源&#xff1a;ViT的出现&#xff1a;ViT的进一步发展&#xff1a; 模型架构技术创新与影响 3. Swin Trans…

JavaScript事件循环

目录 JavaScript 执行机制与事件循环 一、同步与异步代码 1. 同步代码&#xff08;Synchronous Code&#xff09; 2. 异步代码&#xff08;Asynchronous Code&#xff09; 二、事件循环&#xff08;Event Loop&#xff09; 1. 核心组成 2. 事件循环基本流程 3. 运行机制…

Java Collection(7)——Iterable接口

1.Iterator接口 1.1 Iterator接口和其他集合类的关系 Java集合类中&#xff0c;Iterable接口属于顶层接口&#xff0c;除Map接口外&#xff0c;其他都实现了Iterable接口&#xff0c;这意味着它们都可以重写和使用Iterable接口中的方法 1.2 Iterable接口简介 在JDK1.7以前&a…

若依微服务版启动小程序后端

目录标题 本地启动&#xff0c;dev对应 nacos里的 xxx-xxx-dev配置文件 本地启动&#xff0c;dev对应 nacos里的 xxx-xxx-dev配置文件

STM32基础教程——DMA+ADC多通道

目录 前言 ​编辑 技术实现 连线图 代码实现 技术要点 实验结果 问题记录 前言 DMA(Direct Memory Access)直接存储器存取&#xff0c;用来提供在外设和存储器 之间或者存储器和存储器之间的高速数据传输。无需CPU干预&#xff0c;数据可以通过DMA快速地移动&#xff0…

23黑马产品经理Day01

今天过了一遍23黑马产品经理的基础视频 问题思考维度 抓住核心用户 为什么需要抓住核心用户&#xff1f; 主要原因&#xff1a;用户越来越细分&#xff0c;保持市场竞争力&#xff0c;产品开发推广更聚焦 做产品为什么要了解用户&#xff1a;了解用户的付费点&#xff0c;…

C/C++ 通用代码模板

✅ C 语言代码模板&#xff08;main.c&#xff09; 适用于基础项目、算法竞赛或刷题&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <math.h>// 宏定义区 #define MAX_N 1000 #defi…

【数据结构_7】栈和队列(上)

一、概念 栈和队列&#xff0c;也是基于顺序表和链表实现的 栈是一种特殊的线性表&#xff0c;其只允许在固定的一段进行插入和删除元素操作。 遵循后进先出的原则 此处所见到的栈&#xff0c;本质上就是一个顺序表/链表&#xff0c;但是&#xff0c;实在顺序表/链表的基础…

git UserInterfaceState.xcuserstate 文件频繁更新

1> 退出 Xcdoe&#xff0c;打开终端&#xff08;Terminal&#xff09;&#xff0c;进入到你的项目目录下。 2> 在终端键入 git rm --cached <YourProjectName>.xcodeproj/project.xcworkspace/xcuserdata/<YourUsername>.xcuserdatad/UserInterfaceState.x…

【Ai】MCP实战:手写 client 和 server [Python版本]

什么是mcp MCP 是一个开放协议&#xff0c;它为应用程序向 LLM 提供上下文的方式进行了标准化。你可以将 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 为设备连接各种外设和配件提供了标准化的方式一样&#xff0c;MCP 为 AI 模型连接各种数据源和工具提供了标准化的接口…

ESP8266/32作为AVR编程器(ISP programmer)的使用介绍

ESP8266作为AVR编程器( ISP programmer)的使用介绍 &#x1f33f;ESP8266自带库例程&#xff1a;https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266AVRISP&#x1f4cd;支持ESP8266/32的ESP_AVRISP其它开源工程&#xff08;个人没有再去验证&#xff09;&…

08-JVM 面试题-mk

文章目录 1.JVM 的各部分组成2.运行时数据区2.1.什么是程序计数器?2.2.你能给我详细的介绍Java堆吗?2.3.能不能解释一下方法区?2.3.1常量池2.3.2.运行时常量池2.4.什么是虚拟机栈?2.4.1.垃圾回收是否涉及栈内存?2.4.2.栈内存分配越大越好吗?2.4.3.方法内的局部变量是否线…

Vue3 nextTick

nextTick 是 Vue 中非常重要的一个 API&#xff0c;它允许你在 DOM 更新周期后执行延迟回调。 核心源码位置 Vue3 的 nextTick 实现主要在 packages/runtime-core/src/scheduler.ts 文件中。 基本实现 const resolvedPromise Promise.resolve() as Promise<any> let …

DISCO:利用大型语言模型提取反事实

DISCO: Distilling Counterfactuals with Large Language Models - ACL Anthologyhttps://aclanthology.org/2023.acl-long.302/ 1. 概述 尽管在自然语言处理(NLP)领域针对各种推理任务取得了巨大进展(Wang 等, 2018, 2019a;Xu 等, 2020),但数据集偏差仍然是构建鲁棒模型…

【Django】框架-路由系统核心概念解析

1. 最基本路由关系 路由是URL地址与处理逻辑&#xff08;视图函数&#xff09;的对应关系。 本质&#xff1a;将用户请求的URL路径映射到具体的处理程序&#xff08;如Django视图函数&#xff09;。 示例&#xff1a; # urls.py urlpatterns [ path(home/, views.home_…

理解 results = model(source, stream=True) 的工作原理和优势

1. 核心概念解析 (1) streamTrue 的作用 生成器模式&#xff1a;当处理视频或图像序列时&#xff0c;streamTrue 会将结果包装成一个 生成器&#xff08;Generator&#xff09;&#xff0c;逐帧生成 Results 对象&#xff0c;而不是一次性返回所有结果。内存优化&#xff1a;…

重新定义“边缘”:边缘计算如何重塑人类与数据的关系

在数字化浪潮中&#xff0c;云计算曾是科技界的宠儿&#xff0c;但如今&#xff0c;边缘计算正在悄然改变游戏规则。它不仅是一种技术进步&#xff0c;更是对人类与数据关系的一次深刻反思。本文将探讨边缘计算如何从“中心化”走向“分布式”&#xff0c;以及它如何在效率、隐…