热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ObjectiveC的内存管理艺术:入门

文章框架前言呀!!再也不三心二意了,从今往后专心搞iOS!嗯!从大一开始就嚷嚷对iOS热爱的很,结果呢,就毕设写了个垃圾,咦!所以现在起,iOS就是我的主线了!先写这篇垃圾文章壮壮

《Objective-C的内存管理艺术:入门》 文章框架

前言

呀!!再也不三心二意了,从今往后专心搞iOS!嗯!
从大一开始就嚷嚷对iOS热爱的很,结果呢,就毕设写了个垃圾,咦!
所以现在起,iOS就是我的主线了!先写这篇垃圾文章壮壮气势!

本篇内容非常浅显的谈谈Objective-C中的内存管理,适合那些会写点简单的App但从未认真对待过内存管理的初级iOS工程师阅读,在整个内存管理的艺术之路上入个门。

传统内存管理

传统的内存管理我想从C语言说起。当你用malloc函数申请一片空间的时候(或是C++的new),就一定要用free函数释放掉(或是C++的delete)。

但是在面向对象的世界,我们一切打交道的东西是对象,这玩意你可不能说来就来说走就走,来就得初始化一下(构造函数,空间申请+变量初始化),走就得垂死挣扎一下(析构函数,遗产管理+空间释放)。

Objective-C是一门面向对象的语言,所以其与之对应的方法就是allocinitdeallocdealloc就是析构函数。其构造函数相当于先调用allocinit,说白了就是他把空间申请和变量初始化分的很清楚,alloc就是空间申请,init就是变量初始化,这俩通常是成对儿出现的。你问只allocinit行不行,可以!没问题!只不过你的私有成员表示很郁闷:谁来给我们赋初值啊?

定义

  • 构造函数
    也就是构造的时候,在objective-c中定义构造函数时不用你去申请空间,只是写好初始化方法init就好了

- (instanceType) init {
[super init]; // 别忘了要把祖宗也都构造上
if(self){
// 需要初始化的东西
}
}

  • 析构函数
    析构函数,在对象被释放之前,对象总要那么挣扎几下才会安心的去的。不过你也可以让你的对象没有任何想说的直接挂掉,这就可能会出现问题:这财产归谁啊?还是彻底不要了?不能扔那不管啊!

- (void) dealloc {
// 遗嘱
[super dealloc]; // 株连九族
}

注意,这里超类的析构函数要最后调用。

调用

  • 构造函数

ClassName* instance = [[ClassName alloc] init]; // 先构造再初始化

如果说你的构造函数不需要什么参数,直接走默认

ClassName* instance = [ClassName new]; // 等价于上面的

如果你很奔放的不需要初始化

ClassName* instance = [ClassName alloc]; // 完全没问题! 没毛病~

  • 析构函数

[instance dealloc];

至此,在Objective-C中最基本的动态内存管理算是介绍完了。你要是不看后面的直接这么玩,没问题!

那么问题来了:遇到复杂的情况,这种传统模式就很捉急。
比如我要在指针A中引用空间M引用B也要引用对象M对象C也来凑热闹引用了对象M。哇!这对象M简直是香饽饽。A说了:“这盘香饽饽是我做的,我要吃,谁最后吃完谁收拾剩饭”;B说了:“这盘香饽饽我要吃,谁最后吃完谁收拾剩饭”;C说:“我不吃,我就看看。”

好,最后这盘香饽饽什么时候收拾?对应在程序里面也就是什么时候调用[M dealloc]

谁最后吃完谁收拾呗,说得好听!谁知道程序里面谁最后吃完啊,对应着用上面的方式A B C里面都引用了MAB谁最后吃完那完全是看心情的,都任性的很呢!

引用计数管理

引用计数是个好方法,能解决上面的香饽饽问题。也就是这种机制可以让程序知道到底是谁最后吃完了香饽饽。
其基本思想:用一个计数器,来一个吃香饽饽的就+1,吃完一个就-1,减到0了就释放。

手动引用计数(MRC, Manual Reference Counting)

或称为MRR(Manual Retain Release)。这算是最基本的引用计数操作。

  • +1
    对应的方法就是retain。但除此之外,由allocnewcopymutableCopy这样直接生成对象的方法会直接让引用计数自动+1,加给谁?就是=左边的那位。
    所以像前面的这种

ClassName* instance = [[ClassName alloc] init];

instance所指向的对象引用计数会直接+1。
你说我没有左边那位是不是也要+1呀?是!
如果你直接这样写:

[[ClassName alloc] init]

等你去编译的时候,编译器就给你改成

id tmp = [[ClassName alloc] init]
[tmp autorelease]

是的,他就偏往=左边临时放一位,id相当于C语言中的void *指针,这里不谈。autorelease方法本篇也不谈,你可以暂时把它看成release也就是我下面-1部分要说的。
就像前面的香饽饽情形中,香饽饽M是A做的,A和B都要吃,C只看不吃:

ClassName* pointerA = [[ClassName alloc] init]; // 引用计数+1
ClassName* pointerB = [pointerA retain]; // 引用计数+1
ClassName* pointerC = pointerA; // 引用计数不改变,C:我就静静的看着你们吃

引用计数为2,也就是有两位正在享用香饽饽。

  • -1
    吃完了,就别直接收拾了,得看看其他人还有没有吃完是我们中华民族的传统美德,为了经这种美德延续下去,你调用个release就行啦(但别忘了,赋nil)。
    A吃完了:

[instanceA release]; // 引用计数-1
instanceA = nil;

B吃完了:

[instanceB release]; // 引用计数-1
instance = nil;

当引用计数减为0时,就释放空间,也就是收拾剩饭。
为什么要后面赋nil:因为release之后,指针还是指向那片空间,如果等那个空间被释放了,一不小心又通过那个指针(这时这个指针有了一个响当当的名字:悬垂指针,详见注释2)访问了一下那片空间,那就很危险了。如果那片被回收的区域没有被其他程序覆写,那还好,你可能还侥幸能访问到那里的东西。但如果那片空间被其他人覆写了,这就是非法访问了,非法访问的后果就是crash!
C说了:我要搞事情!

[instanceC release];
instanceC = nil; // 搞完事就跑路真刺激!

行不行?还真可以!无论是编译阶段还是运行时他都不会报错的。这就等于C在饭局中冒充一个用餐的喊了句:我吃完啦!最后计数器一算:噢,客人都吃完走了,可以收拾饭桌了。结果最后真正还在吃的那位:MMP!
程序中是这样的:

[instanceA release]; // 引用计数-1
instanceA = nil;
[instanceC release]; // 引用计数-1,此时引用计数已经为0了,自动调用dealloc
instanceB = nil;
[instanceB release]; // 很有可能还没执行到这一步程序已经崩溃了,因为B先生开始发疯:我的饭呢?!
instanceC = nil;

就算B先生恰好也吃完了,在他说“我吃完了”的时候,发现没人鸟他了,这时B先生又疯了!所以程序又崩了。
这个疯了在程序里指的就是B先生变成了悬垂指针。

另外,如果你想查看引用计数就调用[instance retainCount],它会返回一个无符号整形,也就是引用计数的值。
你会发现当减到0的时候,如果你再调用retainCount的话,其值仍然为1,首先你这样做是非常危险的,因为他已经被释放了,你现在是在访问悬垂指针。如果能访问到值而没有崩溃当然是因为那块被回收的空间还没有被覆写,其次它的计数值没有变为0是因为没有必要了,这块空间已经被释放了,也没必要去给它计算多一次,而且这个1本是你不该知道的!

至此,你可以发现MRC还是很屌的,只要你别犯贱,一般不会出现什么问题了,也完美解决了之前的香饽饽问题。

自动引用计数(ARC, Automatic Reference Counting)

自动引用计数的出现减少了很多的内存管理代码,从而交给编译器自动去做。是的,这玩意儿是编译器层面的(但不完全是,也需要运行时的帮助,但认识它,透过它编译时所做的事就够了)。

所有权修饰符
  • __strong
    强修饰符,默认修饰。修饰对象属性的时候写成@property(strong)

{
id creator = [[NSObject alloc] init]; // 这个也是__strong修饰的,默认修饰
id __strong strOngRef= creator;
// something else
}

相当于MRC中的

{
id creator = [[NSObject alloc] init];
id strOngRef= [creator retain];
// something else
[strongRef release];
[creator release];
}

意思就是,只要是给我strong引用赋值的,我都会retain那个对象。
而且那个__strong可以不写出来,因为它是在ARC下默认的所有权修饰符。
而且在这个修饰的变量在其生命周期结束后,会自动调用release。并不是动态的调用release,而是ARC会帮你在那个生命周期结束的位置自动写上release

  • __weak
    弱修饰符,修饰对象属性的时候写成@property(weak)。带有此修饰符的指针变量所指向的对象被释放后自动赋nil。本修饰符在iOS4以上支持,若是之前的版本就用__unsafe_unretain修饰符代替(对应的属性修饰为@property(assign))。但是它不会自动赋nil,所以它是不安全的。

{
id creator = [[NSObject alloc] init];
id __weak weakRef = creator;
// something else
}

相当于MRC中的

{
id creator = [[NSObject alloc] init];
id weakRef = creator;
// something else
weakRef = nil; // 若引用所指向的对象的引用计数为0后就自动赋nil
[creator release];
}

总的来说就是ARC帮你在编译时写了MRC要写的东西,MRC中的那些方法在ARC是不支持的(retainreleaseautoreleasedeallocretainCount不可以人为调用,因为ARC已经帮你写好了)。所以并不是说有了ARC就完全不用担心内存管理,有些需要在运行时管理的内存还是不能太依赖ARC。它只是帮你从那些一般的MRC操作中释放出来。

结语

可以发现从传统到MRC再到ARC是一层一层进化而来,在传统上添加一个引用计数器就是MRC,编译器接手引用计数就变成了ARC。
除此之外上述之外,还有autoreleasepool,本文不提。有兴趣的朋友自行翻资料吧,这篇文章只是在Objective-C内存管理方面带你入个门。

注释

  1. 野指针(Wild Pointer):在声明指针变量时,有些开发语言并不会帮你自动赋nil。所以这个指针可能在刚声明的时候是指向一个迷の区域的。这样的指针就是野指针。
  2. 悬垂指针(Dangling Pointer):悬垂指针就是本指向一个曾经有对象(该指针曾经指向的对象)的空间,但现在是被回收的空间。
  3. 内存泄漏(Memory Leak):内存泄漏指的是内存中存在一些对象实体,他们占用空间,却没有任何指针变量指向他们,所以他们不会被释放掉。

推荐阅读
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
author-avatar
心灵de倾斜
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有