【iOS】ARC学习

这篇具有很好参考价值的文章主要介绍了【iOS】ARC学习。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

在学习ARC之前,先来复习一下内存管理以及autorelease的实现

一、autorelease实现

先来看一下GNUstep源代码:
【iOS】ARC学习,ios,学习,cocoa
autorelease其本质就是调用NSAutoreleasePool 对象的addObject 类方法,就是将对象加到自动释放池中
【iOS】ARC学习,ios,学习,cocoa
接下来再看一下废弃自动释放池的一些功能函数
【iOS】ARC学习,ios,学习,cocoa

二、苹果的实现

可使用showPools输出现在的NSAutoreleasePool的状况输出到控制台

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    id obj2 = [[NSObject alloc] init];
    id obj3 = [[NSObject alloc] init];

    [obj autorelease];
    [obj2 autorelease];
    [obj3 autorelease];

    _objc_autoreleasePoolPrint();
    [pool drain];

【iOS】ARC学习,ios,学习,cocoa

三、内存管理的思考方式

引用计数式内存管理的思考方式就是思考ARC所引起的变化

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

(ARC环境下特有)

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

__strong修饰符

_ strong修饰符是id类型和对象类型默认的所有权修饰符
也就是说id obj = [[NSObject alloc] init]; = id __strong obj = [[NSObject alloc] init];

再来看下面这段代码

//ARC有效
{
	id __strong obj = [[NSObject alloc] init]
}

此源代码指定了变量的作用域,当obj超出其变量作用域时, obj会被废弃,同时自动释放其被赋予的对象([[NSObject alloc] init])

而在MRC中,等效的代码为

//ARC无效
{
	id obj = [[NSObject alloc] init]
	[obj release];
}

因为ARC无效的时候,obj超出变量作用域时,变量并不会被自动废弃,对象也会仍然存在,需要我们手动减少对象的引用计数[obj release]去销毁对象

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

具体如下:
【iOS】ARC学习,ios,学习,cocoa
这里需要注意的一点是此处obj确实持有了对象,并且对象的引用计数为1,但是在目前版本的Xcode的MRC环境中,

    {
        id obj = [NSMutableArray array];
        NSLog(@"%lu", [obj retainCount]);
    }

在MRC环境下输出的值应该为0,因为array方法表明取得非自己生成并持有的对象,也就是说obj并不持有对象,但是输出如下:
【iOS】ARC学习,ios,学习,cocoa
我们来解释一下输出为1的原因:

当我们调用 retainCount 方法时,对于从自动释放池获取的对象,它会临时retained一次,以防止对象被过早释放而导致访问过期数据。

1.array方法创建了一个对象,并将其加到自动释放池中,此时retain count为0
2.obj指向自动释放池中的那个对象,并没有对对象进行retain操作,只是持有了一个指向他的指针
3.调用 [obj retainCount] 时 ,a.编译器会临时保留(retain)对象 b.获得并输出其retain count值 c.释放对象

所以尽管对象最初的 retain count 为 0,但由于 retainCount 方法的实现机制,它会临时保留对象来避免崩溃,这导致我们看到的输出 retain count 为 1。

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

   id __strong obj0 = [[NSObject alloc] init];//对象A
   id __strong obj1 = [[NSObject alloc] init];//对象B
   id __strong obj2 = nil;
    obj0 = obj1;//obj0持有有obj1赋值的对象B的强引用,obj0被赋值。所以原先持有的a的强引用失效,此时b的强引用变量为obj1,obj0

由此__strong 修饰符的变量,不仅只在变量作用域中,在赋值上也能够正确地管理其对象的所有者。

类的成员变量也可以使用strong修饰

【iOS】ARC学习,ios,学习,cocoa
【iOS】ARC学习,ios,学习,cocoa
重点是当Test对象释放时,Test对象的obj_成员变量也会随之被释放

__weak修饰符

使用weak可以使我们取得对象但是并不持有对象
下面有一个代码例子进行解释

    id a = [[NSObject alloc] init];
    id __weak b = a;
    id c = a;
    NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)a));
    NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)c));
    a = nil;
    NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)c));
    NSLog(@"%@", b);
    NSLog(@"%@", c);
    c = nil;
    NSLog(@"%@", b);
    NSLog(@"%@", c);

【iOS】ARC学习,ios,学习,cocoa
可以看到在ARC环境下a,b,c都指向了对象,但是引用计数只有2,这是因为weak是指向对象的指针但并不持有对象,并不会使引用计数加1

当我们将weak修饰符改为strong时,就会出现如下结果

    id a = [[NSObject alloc] init];
    id __strong b = a;
    id c = a;
    NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)a));
    NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)c));
    a = nil;
    NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)c));
    NSLog(@"%@", b);
    NSLog(@"%@", c);
    c = nil;
    NSLog(@"%@", b);
    NSLog(@"%@", c);

【iOS】ARC学习,ios,学习,cocoa

循环引用

内存管理中会发生循环引用的问题,此时就需要用到__weak修饰符
【iOS】ARC学习,ios,学习,cocoa

正确的内存释放过程
首先B对象是A对象的一个属性,也就是A持有B,现在要释放掉A,需要给A发送一个release消息,这时A的引用计数变为0,就要走delloc方法,delloc方法会对A所持有的全部对象发送release消息,当然也包括B,也就是对B进行release,此时B的引用计数也变为0,然后执行delloc,最后A与B都被释放掉了

- (void)dealloc {
    [_b release]; // 释放持有的 B 实例
    _b = nil;
    [super dealloc];
}

【iOS】ARC学习,ios,学习,cocoa
循环引用的产生:
解释:对象之间互相持有,形成闭环,导致谁也无法被正确释放
循环引用容易发生内存泄漏。所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。
【iOS】ARC学习,ios,学习,cocoa

过程:

  • a.例如我们现在想让A释放,也就是让B给A发送release消息,此时B的属性强持有A,所以需要B在delloc方法中对A进行release
  • b.我们想要让B执行delloc,就需要就持有B的A对象发送release消息给B
  • c.想要A发送release消息给B,就需要A执行delloc方法
  • d.想要A执行delloc方法就需要持有A的B对象发送release消息

如此循环往复,对象之间都在等对方给自己发送release消息,导致谁也无法执行,如此往复便造成了循环引用

当然循环引用并不只出现在变量中,还出现在协议与block中,后面在学习的过程中会专门写博客记录

接下来我们谈论一下如何解决这类问题

因为我们知道weak可以使变量取得但并不持有对象,也就是说不会增加对象的引用计数,我们将对象中的属性用weak修饰符修饰就可以解决这个问题
【iOS】ARC学习,ios,学习,cocoa

【iOS】ARC学习,ios,学习,cocoa

使用weak时因为变量不持有对象,因此不会造成相互引用,当对象释放后weak变量会自动置为nil,也避免了野指针的情况

#pragma mark 持有对象的弱引用 MRC 在MRC下,没有__weak这样的自动nil置化特性    使用weak持有某对象弱引用时,对象被废弃,弱引用变量自动只为nil
    id __weak obj1 = nil;
    {
        id __strong obj0 = [[NSObject alloc] init];
        obj1 = obj0;;
        NSLog(@"%@", obj1);
    }
    NSLog(@"%@", obj1);

【iOS】ARC学习,ios,学习,cocoa

__unsafe_unretained修饰符

__unsafe_unretained是一个不安全的所有权修饰符,在MRC下使用来避免循环引用
【iOS】ARC学习,ios,学习,cocoa

但是与weak相比,其会产生悬垂指针

因此我们在使用**__unsafe_unretained必须保证对象存在**

什么时候使用__unsafe_unretained

  • 其与weak相比,可能会有一些更好的性能,追求极致性能便可以使用__unsafe_unretained修饰
  • 以及在早版本的iOS中需要使用__unsafe_unretained来代替__weak.
  • 比如我们在访问单例或者全局变量时就可以使用这个修饰

__autoreleasing修饰符

ARC中不能使用autorelease方法以及NSAutoreleasePool类,但是实际上ARC有效时autorelease功能还是有作用的

  1. 指定“@autoreleasepol 块”来替代“NSAutoreleasePool 类对象生成、持有以及废弃”这一范围
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
  1. 为对象附加__autoreleasing修饰符代替autorelease方法,等价于在ARC无效时调用autorelease即将对象注册到autoreleasepool
    【iOS】ARC学习,ios,学习,cocoa
    但是我们一般不会显式地添加__autoreleasing,因为编译器会检查方法名是否以alloc/new/ copy/mutableCopy 开始,如果不是则自动将返回值的对象注册到autoreleasepool。
    例如:
@autoreleasepool f
id __strong obj = [NSMutableArray array];
}

访问附有__weak 修饰符的变量时,实际上必定要访问注册到autoreleasepool的对象

因为weak修饰符只持有对象的弱引用,因此访问引用对象时对象可能被遗弃。所以我们将对象注册到autoreleasepool可以确保对象存在
当将对象注册在autoreleasepool中,autoreleasepool会临时保留这个对象,直到作用域结束

另外在书上讲id *obj = id __autoreleasing *obj,以此类推NSObject **obj便成为了NSObject * _autoreleasing *obj,这里我们需要知道NSObject *__autoreleasing t1与NSObject __autoreleasing *t1有本质的不同:

  • 前者指向对象的对象会在被赋值时加入到自动释放池
  • 后者常在NSError错误处理中见到

在自动引用计数(ARC)管理的 Objective-C 环境中,当你使用双重指针(比如 NSError **error)作为方法参数时,ARC 会假定这个指针指向的对象是 __autoreleasing

总结来说,NSObject *__autoreleasing t2 是一个自动释放的对象指针,而 NSObject
__autoreleasing *t1 是指向一个自动释放对象指针的指针。

我们以一个代码例子来实验一下

@interface ViewController : UIViewController
@property (nonatomic, weak)NSObject *Obj1;
@property (nonatomic, weak)NSObject *Obj2;

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self test1];


}

- (void)test1 {
    NSLog(@"%@   %@", self.Obj1, self.Obj2);
    [self test2];
    NSLog(@"%@   %@", self.Obj1, self.Obj2);

}

- (void)test2 {
    NSObject *t1 = [[NSObject alloc] init];
    NSObject *__autoreleasing t2 = [[NSObject alloc] init];
    self.Obj1 = t1;
    self.Obj2 = t2;
    NSLog(@"%@   %@", self.Obj1, self.Obj2);

}

【iOS】ARC学习,ios,学习,cocoa
这段代码中因为test2中,t1超出变量作用域,同时self.obj1是被weak修饰的,并不持有对象,对象超出作用域自动销毁,因此第三行输出null。
而t2由于使用了 __autoreleasing,它的生命周期被延长到当前的自动释放池结束

它的生命周期被延长到当前的自动释放池结束"这句话的含义是:

  • 使用__autoreleasing修饰的对象不会在创建它的作用域(通常是一个函数或@autoreleasepool块)结束时被立即释放。
  • 相反,这个对象会被自动添加到当前的Autorelease Pool中,延长了它的生命周期。
  • 直到当前的Autorelease Pool被销毁时,这个对象才会被最终释放。
@interface ViewController : UIViewController
@property (nonatomic, strong)NSObject *Obj1;
@property (nonatomic, strong)NSObject *Obj2;

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self test1];


}

- (void)test1 {
    NSLog(@"%@   %@", self.Obj1, self.Obj2);
    [self test2];
    NSLog(@"%@   %@", self.Obj1, self.Obj2);

}

- (void)test2 {
    NSObject *t1 = [[NSObject alloc] init];
    NSObject *__autoreleasing t2 = [[NSObject alloc] init];
    self.Obj1 = t1;
    self.Obj2 = t2;
    NSLog(@"%@   %@", self.Obj1, self.Obj2);

}

【iOS】ARC学习,ios,学习,cocoa
而这段代码中obj用了strong修饰,即使t1,t2作为局部变量超出了作用域,但是self.Obj1仍然持有这个对象,因此这个对象并不会被销毁,因此其仍然存在

四、ARC规则

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

不要显式调用dealloc

dealloc 方法在大多数情况下还适用于删除已注册的代理或观察者对象。
【iOS】ARC学习,ios,学习,cocoa

五、属性

【iOS】ARC学习,ios,学习,cocoa文章来源地址https://www.toymoban.com/news/detail-840898.html

到了这里,关于【iOS】ARC学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ios客户端学习笔记(七):iOS客户端的UI设计

    iOS客户端的UI设计是指在iOS操作系统上开发应用程序时所涉及的用户界面设计,包括应用程序的布局、颜色、字体、图标等元素的设计。良好的UI设计应该能够提高用户体验,使用户能够轻松地使用应用程序。 在iOS客户端的UI设计中,需要考虑以下几个方面: 应用程序布局应

    2023年04月26日
    浏览(46)
  • 【iOS】GCD学习

    本博客撰写一下小蓝书的GCD Grand Central Dispatch(GCD)是 异步执行任务的技术之一 。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者 只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务 。由于线程管理是作为系统

    2024年02月21日
    浏览(34)
  • 【iOS】GCD深入学习

    关于GCD和队列的简单介绍请看:【iOS】GCD学习 本篇主要介绍GCD中的方法。 我们有时候需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作,当然操作组里也可以包含一个或者多个任务 这就需要用到 dispatch_barrier_async 方法在两个操作组间形成栅栏

    2024年02月14日
    浏览(35)
  • iOS学习—添加震动效果

    导入:#import AudioToolbox/AudioToolbox.h 在需要出发震动的地方写上代码: AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);//默认震动效果 如果想要其他震动效果,可参考: // 普通短震,3D Touch 中 Pop 震动反馈 AudioServicesPlaySystemSound(1520); // 普通短震,3D Touch 中 Peek 震动反馈 AudioServicesPla

    2023年04月25日
    浏览(30)
  • IOS-闭包学习-Swift

    闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。 闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕

    2024年01月24日
    浏览(45)
  • iOS学习01

    MAC指南 MAC下搭建C语言开发环境 数据类型 运算符和表达式 条件控制语句,循环控制语句 函数,数组,指针,字符串 复杂数据类型 文件操作 while可能一次不执行,do while至少执行一次。 结构和联合union区别: 这种情况下联合就好:结构体大部分相同 枚举就是罗列值,比如月。

    2023年04月25日
    浏览(33)
  • iOS-长截图 (学习记录)

    长截图 —— 也就是可以截到超出屏幕的长图。在实现长图之前,我们先要了解一下iOS的绘制普通截图操作。在此基础上,来实现长截图。 UIGraphicsBeginImageContextWithOptions 该函数开始创建图片上下文,它有三个参数: size :想要渲染图片的大小尺寸 opaque :所要创建图片的背景

    2024年02月12日
    浏览(42)
  • iOS学习 --- Xcode 15 下载iOS_17.0.1_Simulator失败解决方法

    1.去开发者官网下载安装包 https://developer.apple.com/download/all/?q=iOS%2017 使用浏览器下载。 2.打开终端通过命令添加到xcode 命令如下: sudo xcode-select -s /Applications/Xcode.app(输入开始密码) xcodebuild -runFirstLaunch (等待一小会) xcrun simctl runtime add \\\"/Users/xxxxx/Downloads/iOS_17.0.1_Simulator_Runtime

    2024年02月05日
    浏览(45)
  • java io流 学习笔记

    通过IO我们能对硬盘文件进行读和写。(网络数据的传输也涉及到io)。 按照 流的方向 进行分类:分为输入、输出流。 往内存中去:叫做 输入(Input) 。或者叫做 读(Read) 。 从内存中出来:叫做 输出(Output) 。或者叫做 写(Write) 。 按照 读取数据方式 不同进行分类: 按照 字节 的

    2024年02月14日
    浏览(34)
  • iOS学习—制作全局遮罩

     在.h文件中线声明show()方法 - (void)show; .m文件中添加全屏遮罩,在遮罩上添加了一个选择框并添加了底部弹出的动画,可自行在其中添加tableview、pickerview等其他视图,并添加了点击选择框视图外区域隐藏

    2024年02月13日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包