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

目录

ARC规则

概要

所有权修饰符

__strong修饰符

__weak修饰符

__unsafe_unretained修饰符

__autoreleasing修饰符


ARC规则

概要

“引用计数式内存管理”的本质部分在ARC中并没有改变,ARC只是自动地帮助我们处理“引用计数”的相关部分。

在编译单位上可以设置ARC有效或无效。

所有权修饰符

ARC有效时,非基本类型上必须附加所有权修饰符,一共有四种:

  • __strong修饰符

  • __weak修饰符

  • __unsafe _unretained修饰符

  • __autoreleasing修饰符

__strong修饰符

__strong修饰符是非基础类型默认的所有权修饰符,也就是说,非基础类型在没有明确指定所有权修饰符时,默认为__strong修饰符。

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

此源代码明确指定了变量的作用域,ARC无效时,该源代码可记述如下:

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

这里增加了调用release方法的代码,同之前ARC有效时的动作一样,如代码所示:附有__strong修饰符的变量obj在超出其变量作用域时,会释放其被赋予的对象。__strong修饰符表示对对象的“强引用”,持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

自己生成并持有对象时:

{id __strong obj = [[NSObject alloc] init];//变量obj为强引用,所以自己持有对象
}
//obj超出其作用域,强引用失效,所以自动释放自己持有的对象。
//对象的所有者不存在,因此废弃该对象

取得非自己生成并持有的对象时:

{id __strong obj = [NSMutableArray array];//obj为强引用,所以自己持有对象
}//因为变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象

附有__strong修饰符的变量之间可以相互赋值\

id __strong obj0 = [[NSObject alloc] init];
id __strong obj1 = [[NSObject alloc] init];
id __strong obj2 = nil;
obj0 = obj1;
obj2 = obj0;
obj1 = nil;
obj0 = nil;
obj2 = nil;

下面来看一下生成并持有对象的强引用:

id __strong obj0 = [[NSObject alloc] init];
//obj0持有对象A的强引用
id __strong obj1 = [[NSObject alloc] init];
//obj1持有对象B的强引用
id __strong obj2 = nil;
//obj2不持有任何对象
obj0 = obj1;
//obj0持有由obj1赋值的对象B的强引用
//obj0被赋值,原先持有的对对象A的强引用失效
//对象A的所有者不存在,因此废弃对象A
//此时持有对象B的强引用的变量为obj0和obj1。
obj2 = obj0;
//obj2持有由obj0赋值的对象B的强引用
//此时,持有对象B的强引用的变量为obj0,obj1和obj2。
obj1 = nil;
//因为nil被赋予了obj1,所以对对象的B的强引用失效
//此时持有对象B的强引用的变量为obj0和obj2。
obj0 = nil;
//因为nil被赋予obj0,所以对对象B的强引用失效
//此时持有对象B的强引用的变量为obj2;
obj2 = nil;
//因为nil被赋予obj2,所以对对象B地强引用失效
//对象B的所有者不存在,因此废弃对象B

可见,__strong修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确地管理其对象的所有者。并且,OC类成员变量也可以在方法参数上使用附有__strong修饰符的变量。

@interface Test : NSObject
{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 setObject:[[NSObject alloc] init]];
}

该例中生成并持有对象的状态记录如下:

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

像这样,可以直接使用于类成员变量以及方法参数中。另外,__strong、__weak、__autoreleasing这三种修饰符,可以保证将附有这些修饰符的自动变量初始化为nil。

因为id类型和对象类型的所有权修饰符默认为__strong修饰符,所以不需要写上这个修饰符。

__weak修饰符

仅仅通过__strong修饰符进行内存管理会发生“循环引用”的问题,如下图所示:

比如前面出现的带有__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 test0 = [[Test alloc] init];id test1 = [[Test alloc] init];[test0 setObject:test1];[test1 setObject:test0];
}

分析一下这里生成并持有对象的状态

{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_
//发生内存泄漏

由于这里在test0和test1所持有的对象在生命周期结束后,还被分别被test1和test0的属性所持有,所以对象在超出生命周期后本应被废弃却没有被废弃。

循环引用容易发生内存泄漏。所谓内存泄漏就是说应当废弃的对象在超出其生存周期后继续存在。

像下面这样,虽然只有一个对象,但在该对象持有自身时,也会发生循环引用(自引用)

id test = [[Test alloc] init];
[test setObject:test];

这时就需要__weak修饰符来避免循环引用。

__weak修饰符与__strong修饰符相反,提供弱引用,不持有对象实例。

当变量加上__weak修饰符时,如果编译以下代码,编译器会发生警告。

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

此源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量obj,变量obj持有对持有对象的弱引用。因此为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即被释放。编译器对此会给出警告。但如果像下面这样,将对象赋给附有__strong修饰符的变量之后再赋值给附有__weak修饰符的变量,就不会发生警告了。

{id __strong obj0 = [[NSObject alloc] init];id __weak obj1 = obj0;
}

下面分析对象的持有状况

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

__weak修饰符的变量不持有对象,所以在超出其变量作用域时,对象即被释放。将先前可能发生循环引用的类成员变量改为附有__weak修饰符的成员变量的话,可以避免循环引用现象。

除此之外,__weak修饰符还有另一优点,就是在持有某对象的弱引用时,如果该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)。如以下代码所示:

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

执行结果如下:

下面分析一下对象的持有情况:

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

像这样,使用_weak修饰符可避免循环引用。并且可以通过检查附有__weak修饰符的变量是否为nil,来判断被赋值的对象是否已废弃。

但是,__weak修饰符只能用于iOS5以上及OS X Lion以上版本的应用程序。在iOS4以及OS X Snow Leopard的应用程序中可使用__unsafe_unretained修饰符来代替。

__unsafe_unretained修饰符

__unsafe_unretained修饰符是一个不安全的所有权修饰符。附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。将自己生成并持有的对象赋值给附有__unsafe_unretained修饰符的变量中,编译器会给出适当的警告。附有__unsafe_unretained修饰符的变量,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。在如下代码中__weak修饰符和__unsafe_unretained修饰符就有一些差异了。

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

代码的执行结果如下:

来分析一下对象的持有情况:

 

id __unsafe_unretained obj1 = nil;
{id __strong obj0 = [[NSObject alloc] init];//因为obj变量为强引用,所以自己持有对象obj1 = obj0;//虽然obj0变量赋值给obj1,但是obj1变量既不持有对象的强引用也不持有弱引用NSLog(@"A: %@", obj1);
}//因为obj0变量超出作用域,强引用失效,所以自动释放自己持有的对象//因为对象无持有者,所以废弃该对象
NSLog(@"B:%@", obj1);
//obj1变量表示的对象已经被废弃(悬垂指针)!
//错误访问

也就是说,最后一行的NSLog只是碰巧正常运行,虽然访问了已经被废弃的对象,但程序在个别运行状况下才会崩溃。

在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确定被赋值的对象确实存在。

__autoreleasing修饰符

ARC有效时,不能使用autorelease方法,也不能使用NSAutoreleasePool类。虽然不能直接使用autorelease,但实际上ARC有效时autorelease功能是起作用的。

ARC无效时:

NSAutoreleasePool *pool = [[NSAutoreleasePoll alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];

ARC有效时:

@autoreleasepool {id __autoreleasing obj = [[NSObject alloc] init];
}

指定"@autoreleasepool块"来替代“NSAutoreleasePool类对象生成、持有以及废弃“这一范围。ARC有效时,要通过将对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。对象赋值给附有__autoreleasing修饰符的变量等价于在ARC无效时调用对象的autorelease方法,即对象被注册到autoreleasepool。

一般不显式地附加__autoreleasing修饰符。

在使用alloc/new/copy/mutableCopy以外的方法来取得对象时,编译器会检查方法名是否已alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool。另外,init方法返回值的对象不注册到autoreleasepool。

@autoreleasepool {id __strong obj = [NSMutableArray array];
}

分析一下对象的所有状况

@autoreleasepool {//取得非自己生成并持有的对象id __strong obj = [NSMutableArray array];//因为变量obj为强引用,所以自己持有对象//该对象由编译器判断其方法名后自动注册到autoreleasepool
}//因为变量obj超出其作用域,强引用失效,自动释放自己持有的对象//同时随着@autoreleasepool块的结束,注册到autoreleasepool中的所有对象被自动释放//对象的所有者不存在,对象被废弃
分析一下为什么取得非自己生成并持有对象时,不使用__autoreleasing修饰符也能使对象注册到autoreleasepool。以下为源代码示例:+ (id)array {return [[NSMutableArray alloc] init];
}

也可以写成:

+ (id)array {id obj = [[NSMutableArray alloc] init];return obj;
}

这里id obj的所有权修饰符默认为__strong,当return是的对象变量超出作用域时,该强引用对应的自己持有的对象会被自动释放。但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。

另外,在访问附有__weak修饰符的变量时,也必定要访问注册到autoreleasepool的对象。

id __weak obj1 = obj0;
NSLog(@"class = %@", [obj1 class]);

以下源代码与此相同

id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);

为什么访问附有__weak修饰符的变量时必须访问注册到autoreleasepool的对象呢?因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象可能被废弃。如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。因此在使用附有__weak修饰符的变量时就必定要使用注册到autoreleasepool中的对象。

最后,id的指针例如id *obj也是非显式地使用__autoreleasing修饰符的例子,id *obj,即id __autoreleasing *obj。同样,对象的指针NSObject **obj便成为了NSObject * __autoreleaseing *obj。

像这样,id的指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符。

比如,为了得到详细的错误信息,经常会在方法的参数重传递NSError对象的指针,而不是函数返回值。Cocoa框架中,大多方法也是用这种方式。如NSString的stringWithContentsOfFile:encoding:error类方法等。使用该方法的代码如下所示:

NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];

id指针或对象的指针会默认附加上__autoreleasing修饰符。

作为alloc/new/copy/mutableCopy方法返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成并持有的对象。因此,使用附有__autoreleasing修饰符的变量作为对象取得参数,与除alloc/new/copy/mutableCopy外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。

比如performOperationWithError方法的源代码应该是下面这样:

-- (BOOL)performOperationWithError:(NSError* __autoreleasing *)error {//错误发生*error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];return NO;}

因为声明为NSError* __autoreleasing *类型的error作为 *error被赋值,所以能够返回注册到autoreleasepool中的对象。

然而,下面这样的源代码会产生编译器错误

NSError *error = nil;
NSError **pError = &error;

因为赋值给对象指针时,所有权修饰符必须一致。

前面的方法参数中使用了附有__autoreleasing修饰符的对象指针类型,然而调用方却使用了附有__strong修饰符的对象指针类型。那为什么该源代码没有警告就顺利通过编译了呢?实际上,编译器自动将该源代码转化成了下面形式:

NSError __strong *error = nil;
NSError __autoreleasing *tep = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tep;

当然也可以显式地指定方法参数中对象指针类型的所有权修饰符。

- (BOOL) performOperationWithError:(NSError* __strong *)error;

像该源代码的声明一样,对象不注册到autoreleasepool也能传递,但是为了在使用参数取得对象时,贯彻内存管理的思考方式,我们要将参数声明为附有__autoreleasing修饰符的对象指针类型。

另外,虽然可以非显式地指定__autoreleasing修饰符,但在显式地指定__autoreleasing修饰符时,必须注意对象变量要为自动变量(包括局部变量、函数以及方法参数)。

另外,ARC无效时,可将NSAutoreleasePool对象嵌套使用。

NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc] init];NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];id obj = [[NSObject alloc] init];[obj autorelease];[pool2 drain];[pool1 drain];
[pool0 drain];

同样,@autoreleasepool块也能够嵌套使用

@autoreleasepool {@autoreleasepool {@autoreleasepool {id __autoreleasing obj = [[NSObject alloc] init];}}
}

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

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

相关文章

MySQL-DQL之数据多表操作

文章目录 一. 多表操作1. 表与表之间的关系2. 外键约束3. 创建外键约束表(一对多操作) 二. 多表查询1. 多表查询① 交叉连接查询(基本不会使用-得到的是两个表的乘积) [了解](不要记住)② 交集运算:内连接查询(join)③ 差集运算:外…

《经验分享 · 软考系统分析师》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…

宝塔面板Linux版本常用命令

宝塔面板(BT Panel)是一款简单易用的服务器管理工具,广泛应用于Linux服务器的管理。尽管宝塔提供了图形化界面,但在某些情况下,使用命令行操作更加高效。以下是宝塔面板Linux版本常用的命令,包括安装、管理…

ElasticSearch - 理解doc Values与Inverted Index倒排索引

文章目录 概述倒排索引:从图书馆的索引卡片谈起倒排索引的工作原理 docValues:从数据库的列式存储说起docValues的工作原理 docValues与倒排索引的对比两者的联系:组合使用,优化搜索与分析 小结 概述 在使用 Elasticsearch 进行大…

2.【每日算法】

1. NC140 排序 题目连接 快排 #include <vector> class Solution { public:/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** 将给定数组排序* param arr int整型vector 待排序的数组* return int整型vector*/v…

Acer宏碁Swift3笔记本S40-20,SF314-56G原厂Win10系统工厂模式安装包,带Recovery恢复还原

适用电脑型号&#xff1a;S40-20、SF314-56、SF314-56G(原装OEM预装系统) 链接&#xff1a;https://pan.baidu.com/s/1q77Br-hcmn9iJraGVVKQ7Q?pwdrw1r 提取码&#xff1a;rw1r Acer宏碁原装出厂windows10系统自带所有驱动、Office办公软件、出厂主题壁纸、系统属性专属联…

人工智能|自然语言处理——机器翻译评价指标Bleu和Rouge

在机器翻译任务中&#xff0c;BLEU 和 ROUGE 是两个常用的评价指标&#xff0c;BLEU 根据精确率(Precision)衡量翻译的质量&#xff0c;而 ROUGE 根据召回率(Recall)衡量翻译的质量 BLEU&#xff08;Bilingual Evaluation Understudy&#xff09;&#xff1a; BLEU是一种用于评…

Python跳动的爱心

系列文章 序号直达链接表白系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码8Python普通的玫瑰花代码9Python炫酷的玫瑰花代码10Python多…

极验决策引擎如何凭借独特优势,弯道超车传统风控?

前言 市场上的规则决策引擎产品众多&#xff0c;但大多局限于IP、设备、账号等层面&#xff0c;提供的是现成的风控标签和规则。然而&#xff0c;真正的风控&#xff0c;需要的不仅仅是标签和规则。 极验的业务规则决策引擎与众不同&#xff0c;这款决策引擎以界面流程编排为…

windows如何使用ssh连接kali

声明&#xff1a; 昨天晚上看了小羽老师的直播课&#xff0c;心血来潮自己也想搞一下这个ssh&#xff0c;中途安装遇到了不少问题&#xff0c;电脑也是重启了好多次&#xff0c;遇到bug就重启也是解决bug的一种方法. 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&…

SpringMvc完整知识点一

SpringMVC概述 定义 SpringMVC是一种基于Java实现MVC设计模型的轻量级Web框架 MVC设计模型&#xff1a;即将应用程序分为三个主要组件&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&#xff09;。这种分离…

路由器、二层交换机与三层交换机的区别与应用

路由器、二层交换机和三层交换机是常见的网络设备&#xff0c;常常协同工作。它们都可以转发数据&#xff0c;但在功能、工作层级以及应用场景上存在差异。 1. 工作层级 三者在OSI模型中的工作层级不同&#xff1a; 路由器&#xff1a; 工作在 网络层&#xff08;第三层&#…

Spring Boot 指定外部配置路径

优先级 外部 > 内部 目录结构&#xff1a; conf/… app.jar 启动命令 java -jar --spring.config.locationfile:/conf/ app.jar

(css)element中el-select下拉框整体样式修改

(css)element中el-select下拉框整体样式修改 重点代码&#xff08;颜色可行修改&#xff09; // 修改input默认值颜色 兼容其它主流浏览器 /deep/ input::-webkit-input-placeholder {color: rgba(255, 255, 255, 0.50); } /deep/ input::-moz-input-placeholder {color: rgba…

SEC_ASA 第一天作业

拓扑&#xff1a; 实验需求&#xff1a; 注意&#xff1a;在开始作业之前必须先读“前言”&#xff0c;以免踩坑&#xff01;&#xff01;&#xff01;&#xff08;☞敢点我试试&#xff09; 按照拓扑图配置VLAN连接。 注意&#xff1a;ASA防火墙的 Gi0/1口需要起子接口&#x…

「Mac玩转仓颉内测版45」小学奥数篇8 - 排列组合计算

本篇将通过 Python 和 Cangjie 双语讲解如何计算排列与组合。这道题目旨在让学生学会使用排列组合公式解决实际问题&#xff0c;并加深对数学知识和编程逻辑的理解。 关键词 小学奥数Python Cangjie排列与组合 一、题目描述 编写一个程序&#xff0c;计算从 n 个不同元素中取…

Ungoogled Chromium127编译指南 Windows篇 - 获取源码(七)

1. 引言 在完成所有必要工具的安装和配置后&#xff0c;我们进入了Ungoogled Chromium编译过程的第一个关键阶段&#xff1a;获取源代码。本文将详细介绍如何正确获取和准备Ungoogled Chromium的源代码&#xff0c;为后续的编译工作打下基础。 2. 准备工作 2.1 环境检查 在…

APP、小程序对接聚合广告平台,有哪些广告变现策略?

开发者对接聚合广告平台&#xff0c;可以让自身流量价值最大化&#xff0c;获得更多的广告曝光机会&#xff0c;对接单一的广告联盟容易造成广告填充不足&#xff0c;收益不稳定的问题。#APP广告变现# APP开发者根据应用的生命周期、用户特征和产品定位&#xff0c;选择最适合…

人脸识别Adaface之libpytorch部署

目录 1. libpytorch下载2. Adaface模型下载3. 模型转换4. c推理4.1 前处理4.2 推理4.3 编译运行4.3.1 写CMakeLists.txt4.3.2 编译4.3.3 运行 1. libpytorch下载 参考&#xff1a; https://blog.csdn.net/liang_baikai/article/details/127849577 下载完成后&#xff0c;将其解…

Elasticsearch高性能实践

前言 本方案主要从运维层面分析es是实际生产使用过程中的参数优化&#xff0c;深入理解es各个名词及含义&#xff0c;深入分析es的使用过程中应注意的点&#xff0c;详细解释参数设置的原因以及目的&#xff0c;主要包括系统层面&#xff0c;参数层面。除此之外&#xff0c;优…