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

news2024/12/14 4:36:36

目录

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.coloradmin.cn/o/2259108.html

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

相关文章

An error happened while trying to locate the file on the Hub and we cannot f

An error happened while trying to locate the file on the Hub and we cannot find the requested files in the local cache. Please check your connection and try again or make sure your Internet connection is on. 关于上述comfy ui使用control net预处理器的报错问…

angular19-官方教程学习

周日了解到angular已经更新到19了,想按官方教程学习一遍,工欲善其事必先利其器,先更新工具: 安装新版版本 卸载老的nodejs 20.10.0,安装最新的LTS版本 https://nodejs.org 最新LTS版本已经是22.12.0 C:\Program File…

计算机毕业设计Python+Vue.js游戏推荐系统 Steam游戏推荐系统 Django Flask 游 戏可视化 游戏数据分析 游戏大数据 爬虫 机

温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…

上海亚商投顾:创业板指震荡调整 机器人概念股再度爆发

上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。 一.市场情绪 沪指昨日冲高回落,深成指、创业板指盘中跌超1%,尾盘跌幅有所收窄。机器人概念股逆势爆…

粘贴可运行:Java调用大模型(LLM) 流式Flux stream 输出;基于spring ai alibaba

在Java中,使用Spring AI Alibaba框架调用国产大模型通义千问,实现流式输出,是一种高效的方式。通过Spring AI Alibaba,开发者可以轻松地集成通义千问模型,并利用其流式处理能力,实时获取模型生成的文本。这…

【CSS in Depth 2 精译_070】11.3 利用 OKLCH 颜色值来处理 CSS 中的颜色问题(下):从页面其他颜色衍生出新颜色

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第四部分 视觉增强技术 ✔️【第 11 章 颜色与对比】 ✔️ 11.1 通过对比进行交流 11.1.1 模式的建立11.1.2 还原设计稿 11.2 颜色的定义 11.2.1 色域与色彩空间11.2.2 CSS 颜色表示法 11.2.2.1 RGB…

Ajax--实现检测用户名是否存在功能

目录 (一)什么是Ajax (二)同步交互与异步交互 (三)AJAX常见应用情景 (四)AJAX的优缺点 (五)使用jQuery实现AJAX 1.使用JQuery中的ajax方法实现步骤&#xf…

【PSINS】以速度和位置作为观测量(即6维观测量)的组合导航滤波,EKF实现,提供可直接运行的MATLAB代码

原有的代码是以位置作为观测量的,这里提供位置+速度,共6维的观测量,状态量还是15维不变 文章目录 源代码运行结果PS源代码 源代码如下: % 【PSINS】位置与速度为观测的153,EKF。从速度观测的EKF153改进而来 % 2024-12-11/Ver1:位置与速度为观测量% 清空工作空间,清除命…

探索云原生安全解决方案的未来

我们是否充分意识到云端所面临的网络安全威胁? 在当今互联互通的世界中,维护安全的环境至关重要。云的出现扩大了潜在威胁的范围,因为它催生了机器身份(称为非人类身份 (NHI))及其秘密。随着组织越来越多地转向云原生…

无法正常启动此程序,因为计算机丢失wlanapi.dll

wlanapi.dll丢失怎么办?有没有什么靠谱的修复wlanapi.dll方法_无法启动此程序,因为计算机中丢失wlanapi.dll-CSDN博客 wlanapi.dll是 Windows 操作系统中的一个动态链接库文件,主要与 Windows 无线 LAN (WLAN) API 相关。该DLL提供了许多必要的函数&…

ADC -DMA

文章目录 前言一、ADC配置修改二、配置DMA三、DMA中断四、完整参考代码总结 前言 提示:这里可以添加本文要记录的大概内容: 项目需要: 在原有基础上改进,采用DMA中断采集数据 提示:以下是本篇文章正文内容&#xff0…

Referer头部在网站反爬虫技术中的运用

网站数据的安全性和完整性至关重要。爬虫技术,虽然在数据收集和分析中发挥着重要作用,但也给网站管理员带来了挑战。为了保护网站数据不被恶意爬取,反爬虫技术应运而生。本文将探讨HTTP头部中的Referer字段在反爬虫技术中的应用,并…

docker搭建haproxy实现负载均衡

华子目录 获取haproxy镜像建立haproxy容器的数据卷获取haproxy的配置文件编写yaml文件运行测试 获取haproxy镜像 [rootdocker-node1 ~]# docker pull haproxy:2.3建立haproxy容器的数据卷 conf目录为数据卷 [rootdocker-node1 ~]# mkdir /var/lib/docker/volumes/conf/获取h…

阿里云数据库MongoDB版助力极致游戏高效开发

客户简介 成立于2010年的厦门极致互动网络技术股份有限公司(以下简称“公司”或“极致游戏”),是一家集网络游戏产品研发与运营为一体的重点软件企业,公司专注于面向全球用户的网络游戏研发与运营。在整个产业链中,公…

深入探索前端调试神器vConsole

深入探索前端调试神器vConsole 在前端开发过程中,调试工具的重要性不言而喻。而vConsole作为一款轻量级的、可嵌入的JavaScript调试面板,为前端开发者提供了一个便捷的调试解决方案。本文将带你深入了解vConsole的基本概念、作用,并通过丰富…

子网划分实例

看到有人问这个问题: 想了一下,这是一个子网划分的问题: 处理方法如图: 这是一个子网划分的问题 设备1用三层交换机,端口设置为路由模式,设备2和设备3为傻瓜交换机模式 设备2和设备3下挂设备都是26为掩码&…

【机器人】振动分析和控制工具之Bode图

Bode 图完整介绍 Bode 图由两个部分组成: 幅值图 (Magnitude Plot):描述系统对不同频率输入信号的增益大小(幅值响应)。相位图 (Phase Plot):描述系统输出信号相对于输入信号的相位差。 Bode 图的横轴是频率&#x…

Rerender A Video 技术浅析(五):对象移除与自动配色

Rerender A Video 是一种基于深度学习和计算机视觉技术的视频处理工具,旨在通过智能算法对视频进行重新渲染和优化。 一、对象移除模块 1. 目标检测 1.1 概述 目标检测是对象移除的第一步,旨在识别视频中需要移除的对象并生成相应的掩码(m…

经典的网络安全技术

以我的理解,“黑客”大体上应该分为“正”、“邪”两类,正派黑客依靠自己掌握的知识帮助系统管理员找出系统中的漏洞并加以完善,而邪派黑客则是通过各种黑客技能对系统进行攻击、入侵或者做其他一些有害于网络的事情,因为邪派黑客…

Figma入门-实战列表页

Figma入门-实战列表页 前言 在之前的工作中,大家的原型图都是使用 Axure 制作的,印象中 Figma 一直是个专业设计软件。 最近,很多产品朋友告诉我,很多原型图都开始用Figma制作了,并且很多组件都是内置的&#xff0c…