iOS 与 OSX 内存管理中的引用计数

iOS 与 OSX 内存管理中的引用计数

IOS小彩虹2021-08-15 14:21:21180A+A-

什么是引用计数?

引用计数是计算机科学中的一种技术,通过这种技术,每个对象在实例化时都被分配了一个计数值,因此应用程序可以知道哪些对象仍在使用。在对象的生命周期中,其它对象如果需要使用该对象,就声明对该它的所有权,然后增加计数值;当该对象完成了任务之后释放(release)所有权,然后减少计数值。一个对象的计数值减为 0 时,就会从内存中销毁(deallocate)。举个例子:

- (void)demonstration {
    MyClass *foo = [[MyClass alloc] init];
    [foo performSomeMethod];
    [foo release];
}

在上面的代码中,我们做了三件事:初始化一个对象,调用它的某个方法,最后释放它。在 iOS 5 发布之前,开发者们在开发应用时都需要这么做。这种内存管理的方式被称为手动引用计数,即 MRC(Manual Reference Counting) 或 MRM(Manual Reference Management)。

在看了上面的代码之后,你的第一印象可能觉得这么做并没有什么大不了,因为它很简单。然而当代码量持续增加,并且更多开发者参与到项目中时,开发者更有可能在这里出错。

手动引用计数

所以 MRC 中具体包含了哪些内容呢?

alloc

MyClass *foo = [[MyClass alloc] init];

这段代码是 Objective-C 中最最基础的。要创建一个对象,首先需要初始化它。当调用 alloc来创建一个对象时,系统会为该对象分配内存空间,并将它的引用计数设为 1。

release

[foo release];

对象调用 release 会使它的引用计数减 1。当对象的引用计数减为 0 时,系统会将该对象从内存移除,并释放内存空间以供其它对象使用。

retain

[foo retain];

对象调用 retain 时,会通知系统为它的引用计数加 1。调用 retain 意味着其它对象想要持有 foo

我们假设两个不同的对象都持有 foo,当第一个对象对 foo 调用 release 时,foo 的引用计数会从 2 减为 1。第二个对象仍然可以使用 foo,而无需担心 carsh 或产生一个悬空指针。

copy

MyClass *bar = [foo copy];

copyretain 的原理很相似,它可以复制一份原来的对象,不同之处在于引用计数。如果复制 foo 时它的引用计数为 4,复制得到的对象 bar 的引用计数只会为 1。

autorelease

[foo autorelease];

当一个对象的作用域超出了它所声明的范围,就需要对其调用 autorelease。它会告诉系统,我们并不希望立即销毁这个对象,而是在 autoreleasepool被清空的时候再去销毁这个对象。

ps;iOS开发交流技术群:欢迎你的加入,lsp,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

autorelase 通常当我们在一个方法内部声明一个对象并将其返回给其调用者时使用。另一个情景是对象在 for 循环中实例化,并且该循环中有一个 autoreleasepool 时,也可以使用 autorelease

- (MyClass *)foo {
 MyClass *foo = [[MyClass alloc] init];
 [foo autorelease];
}

autoreleasepool

- (void)example {
    for (int i = 0, i < 10, i++) {
        @autoreleasepool {
            MyClass *foo = [[MyClass alloc] init];
            [foo autorelease];
        }
    }
}

上面的代码中,我们实例化了 foo 对象,并对其调用了 autorelease。这些操作被包裹在 autoreleasepool 中,外层还有一个 for 循环。

这么做的好处在于,for 循环中实例化的所有对象,可以在每一轮循环结束时自动被释放。当这一切发生时 autoreleasepool 会自己进行销毁,并释放所有对象,恢复到原来干净整洁的状态。

dealloc

- (void)dealloc {
    [foo release];
    [bar release];
    [fubar release];
    [super dealloc];
}

dealloc 是所有继承自 NSObject 的对象最后会调用的方法。你可以把 dealloc 想象成清理那些引用计数大于 0 的遗留对象的地方。

手动引用计数的缺点

现在你应该能更好的理解手动管理引用计数所需要做的工作。下面介绍两个常见场景,关于微小的人为失误可能导致运行时 crash 的情况。

悬空指针

MyClass *foo = [[MyClass alloc] init];
[foo release];
[foo doSomething];

之前提到过,如果对象的引用计数减为 0,系统会将该对象从内存移除。该地址的内存空间清空之后,可能保留着仍为空的状态,也可能有其它对象占据了这块空间。

但是要记住一点, foo 指针仍然指向这块内存。所以当 doSomething 方法被调用时,实际上会让 nil 或其它占据这块内存的对象去调用这个方法,这样做常常会引起 crash。

内存泄漏

MyClass *foo = [[MyClass alloc] init];
[foo retain];
[foo retain];
[foo release];

这有点像悬空指针的反面情况,当对象调用 release 的次数少于调用 retain 的次数时,就会发生内存泄漏。如果对象的引用计数一直不减为 0,系统就无法把该对象的资源分配给其它对象。

你的应用中有些对象永远没有被释放,看上去好像没有多大的问题,不过如果这样的对象太多,就会导致应用的内存被耗光,产生一些奇怪的问题,并最终导致 crash。这也是我们在 dealloc 中执行清空操作的原因。

自动化的魔法
直到今天,所有 iOS 与 OS X 应用仍然使用引用计数,唯一不同在于我们不再需要进行手动管理,因为编译器都帮我们做好了。

这里严肃声明一点,所有我之前提到的语句,编译器在编译时会帮我们自动插入,包括 retain、release、copy、autorelease、autoreleasepool。如果你回去看看我提供的演示代码,想象上面五种调用都被注释了,简而言之 ARC 都帮我们处理好了

GitHub 中上传了一些样例,你可以下载看看其中内存管理的语法。这只是一个简单的 OS X 控制台程序,其中 ARC 已经关闭了。示例函数的调用已经都被注释了,如果你想要看看它们的运行效果,只需要取消注释,编译并运行。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们