iOS 进阶—— iOS内存管理
1 似乎每个人在学习 iOS 过程中都考虑过的问题
- alloc retain release delloc 做了什么?
- autoreleasepool 是怎样实现的?
- __unsafe_unretained 是什么?
- Block 是怎样实现的
- 什么时候会引起循环引用,什么时候不会引起循环引用?
所以我将在本篇博文中详细的从 ARC 解释到 iOS 的内存管理,以及 Block 相关的原理、源码。
2 从 ARC 说起
说 iOS 的内存管理,就不得不从 ARC(Automatic Reference Counting / 自动引用计数) 说起, ARC 是 WWDC2011 和 iOS5 引入的变化。ARC 是 LLVM 3.0 编译器的特性,用来自动管理内存。
与 Java 中 GC 不同,ARC 是编译器特性,而不是基于运行时的,所以 ARC 其实是在编译阶段自动帮开发者插入了管理内存的代码,而不是实时监控与回收内存。
ARC 的内存管理规则可以简述为:
- 每个对象都有一个『被引用计数』
- 对象被持有,『被引用计数』+1
- 对象被放弃持有,『被引用计数』-1
- 『引用计数』=0,释放对象
3 你需要知道
- 包含 NSObject 类的 Foundation 框架并没有公开
- Core Foundation 框架源代码,以及通过 NSObject 进行内存管理的部分源代码是公开的。
- GNUstep 是 Foundation 框架的互换框架
GNUstep 也是 GNU 计划之一。将 Cocoa Objective-C 软件库以自由软件方式重新实现
某种意义上,GNUstep 和 Foundation 框架的实现是相似的
通过 GNUstep 的源码来分析 Foundation 的内存管理
4 alloc retain release dealloc 的实现
4.1 GNU – alloc
查看 GNUStep 中的 alloc 函数。
GNUstep/modules/core/base/Source/NSObject.m alloc:
- + (id) alloc
- {
- return [self allocWithZone: NSDefaultMallocZone()];
- }
- + (id) allocWithZone: (NSZone*)z
- {
- return NSAllocateObject (self, 0, z);
- }
GNUstep/modules/core/base/Source/NSObject.m NSAllocateObject:
- struct obj_layout {
- NSUInteger retained;
- };
- NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
- {
- int size = 计算容纳对象所需内存大小;
- id new = NSZoneCalloc(zone, 1, size);
- memset (new, 0, size);
- new = (id)&((obj)new)[1];
- }
NSAllocateObject 函数通过调用 NSZoneCalloc 函数来分配存放对象所需的空间,之后将该内存空间置为 nil,最后返回作为对象而使用的指针。
我们将上面的代码做简化整理:
GNUstep/modules/core/base/Source/NSObject.m alloc 简化版本:
- struct obj_layout {
- NSUInteger retained;
- };
- + (id) alloc
- {
- int size = sizeof(struct obj_layout) + 对象大小;
- struct obj_layout *p = (struct obj_layout *)calloc(1, size);
- return (id)(p+1)
- return [self allocWithZone: NSDefaultMallocZone()];
- }
alloc 类方法用 struct obj_layout 中的 retained 整数来保存引用计数,并将其写入对象的内存头部,该对象内存块全部置为 0 后返回。
一个对象的表示便如下图:
4.2 GNU – retain
GNUstep/modules/core/base/Source/NSObject.m retainCount:
- - (NSUInteger) retainCount
- {
- return NSExtraRefCount(self) + 1;
- }
- inline NSUInteger
- NSExtraRefCount(id anObject)
- {
- return ((obj_layout)anObject)[-1].retained;
- }
GNUstep/modules/core/base/Source/NSObject.m retain:
- - (id) retain
- {
- NSIncrementExtraRefCount(self);
- return self;
- }
- inline void
- NSIncrementExtraRefCount(id anObject)
- {
- if (((obj)anObject)[-1].retained == UINT_MAX - 1)
- [NSException raise: NSInternalInconsistencyException
- format: @"NSIncrementExtraRefCount() asked to increment too far”];
- ((obj_layout)anObject)[-1].retained++;
- }
以上代码中, NSIncrementExtraRefCount 方法首先写入了当 retained 变量超出最大值时发生异常的代码(因为 retained 是 NSUInteger 变量),然后进行 retain ++ 代码。
4.3 GNU – release
和 retain 相应的,release 方法做的就是 retain --。
GNUstep/modules/core/base/Source/NSObject.m release
- - (oneway void) release
- {
- if (NSDecrementExtraRefCountWasZero(self))
- {
- [self dealloc];
- }
- }
- BOOL
- NSDecrementExtraRefCountWasZero(id anObject)
- {
- if (((obj)anObject)[-1].retained == 0)
- {
- return YES;
- }
- ((obj)anObject)[-1].retained--;
- return NO;
- }
4.4 GNU – dealloc
dealloc 将会对对象进行释放。
GNUstep/modules/core/base/Source/NSObject.m dealloc:
- - (void) dealloc
- {
- NSDeallocateObject (self);
- }
- inline void
- NSDeallocateObject(id anObject)
- {
- obj_layout o = &((obj_layout)anObject)[-1];
- free(o);
- }
4.5 Apple 实现
在 Xcode 中 设置 Debug -> Debug Workflow -> Always Show Disassenbly 打开。这样在打断点后,可以看到更详细的方法调用。
通过在 NSObject 类的 alloc 等方法上设置断点追踪可以看到几个方法内部分别调用了:
retainCount
- __CFdoExternRefOperation
- CFBasicHashGetCountOfKey
retain
- __CFdoExternRefOperation
- CFBasicHashAddValue
release
- __CFdoExternRefOperation
- CFBasicHashRemoveValue
可以看到他们都调用了一个共同的 __CFdoExternRefOperation 方法。
该方法从前缀可以看到是包含在 Core Foundation,在 CFRuntime.c 中可以找到,做简化后列出源码:
CFRuntime.c __CFDoExternRefOperation:
- int __CFDoExternRefOperation(uintptr_t op, id obj) {
- CFBasicHashRef table = 取得对象的散列表(obj);
- int count;
- switch (op) {
- case OPERATION_retainCount:
- count = CFBasicHashGetCountOfKey(table, obj);
- return count;
- break;
- case OPERATION_retain:
- count = CFBasicHashAddValue(table, obj);
- return obj;
- case OPERATION_release:
- count = CFBasicHashRemoveValue(table, obj);
- return 0 == count;
- }
- }
所以 __CFDoExternRefOperation 是针对不同的操作,进行具体的方法调用,如果 op 是 OPERATION_retain,就去掉用具体实现 retain 的方法。
从 BasicHash 这样的方法名可以看出,其实引用计数表就是散列表。
key 为 hash(对象的地址) value 为 引用计数。
下图是 Apple 和 GNU 的实现对比:
5 autorelease 和 autorelaesepool
在苹果对于 NSAutoreleasePool 的文档中表示:
每个线程(包括主线程),都维护了一个管理 NSAutoreleasePool 的栈。当创先新的 Pool 时,他们会被添加到栈顶。当 Pool 被销毁时,他们会被从栈中移除。
autorelease 的对象会被添加到当前线程的栈顶的 Pool 中。当 Pool 被销毁,其中的对象也会被释放。
当线程结束时,所有的 Pool 被销毁释放。
对 NSAutoreleasePool 类方法和 autorelease 方法打断点,查看其运行过程,可以看到调用了以下函数:
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- // 等同于 objc_autoreleasePoolPush
- id obj = [[NSObject alloc] init];
- [obj autorelease];
- // 等同于 objc_autorelease(obj)
- [NSAutoreleasePool showPools];
- // 查看 NSAutoreleasePool 状况
- [pool drain];
- // 等同于 objc_autoreleasePoolPop(pool)
[NSAutoreleasePool showPools] 可以看到当前线程所有 pool 的情况:
- objc[21536]: ##############
- objc[21536]: AUTORELEASE POOLS for thread 0x10011e3c0
- objc[21536]: 2 releases pending.
- objc[21536]: [0x101802000] ................ PAGE (hot) (cold)
- objc[21536]: [0x101802038] ################ POOL 0x101802038
- objc[21536]: [0x101802040] 0x1003062e0 NSObject
- objc[21536]: ##############
- Program ended with exit code: 0
在 objc4 中可以查看到 AutoreleasePoolPage:
- objc4/NSObject.mm AutoreleasePoolPage
- class AutoreleasePoolPage
- {
- static inline void *push()
- {
- 生成或者持有 NSAutoreleasePool 类对象
- }
- static inline void pop(void *token)
- {
- 废弃 NSAutoreleasePool 类对象
- releaseAll();
- }
- static inline id autorelease(id obj)
- {
- 相当于 NSAutoreleasePool 类的 addObject 类方法
- AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 实例;
- }
- id *add(id obj)
- {
- 将对象追加到内部数组
- }
- void releaseAll()
- {
- 调用内部数组中对象的 release 方法
- }
- };
- void *
- objc_autoreleasePoolPush(void)
- {
- if (UseGC) return nil;
- return AutoreleasePoolPage::push();
- }
- void
- objc_autoreleasePoolPop(void *ctxt)
- {
- if (UseGC) return;
- AutoreleasePoolPage::pop(ctxt);
- }
AutoreleasePoolPage 以双向链表的形式组合而成(分别对应结构中的 parent 指针和 child 指针)。
thread 指针指向当前线程。
每个 AutoreleasePoolPage 对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。
next 指针指向下一个 add 进来的 autorelease 的对象即将存放的位置。
一个 Page 的空间被占满时,会新建一个 AutoreleasePoolPage 对象,连接链表。
6 __unsafe_unretained
有时候我们除了 __weak 和 __strong 之外也会用到 __unsafe_unretained 这个修饰符,那么我们对 __unsafe_unretained 了解多少?
__unsafe_unretained 是不安全的所有权修饰符,尽管 ARC 的内存管理是编译器的工作,但附有 __unsafe_unretained 修饰符的变量不属于编译器的内存管理对象。赋值时即不获得强引用也不获得弱引用。
来运行一段代码:
- id __unsafe_unretained obj1 = nil;
- {
- id __strong obj0 = [[NSObject alloc] init];
- obj1 = obj0;
- NSLog(@"A: %@", obj1);
- }
- NSLog(@"B: %@", obj1);
运行结果:
- 2017-01-12 19:24:47.245220 __unsafe_unretained[55726:4408416] A:
- 2017-01-12 19:24:47.246670 __unsafe_unretained[55726:4408416] B:
- Program ended with exit code: 0
对代码进行详细分析:
- id __unsafe_unretained obj1 = nil;
- {
- // 自己生成并持有对象
- id __strong obj0 = [[NSObject alloc] init];
- // 因为 obj0 变量为强引用,
- // 所以自己持有对象
- obj1 = obj0;
- // 虽然 obj0 变量赋值给 obj1
- // 但是 obj1 变量既不持有对象的强引用,也不持有对象的弱引用
- NSLog(@"A: %@", obj1);
- // 输出 obj1 变量所表示的对象
- }
- NSLog(@"B: %@", obj1);
- // 输出 obj1 变量所表示的对象
- // obj1 变量表示的对象已经被废弃
- // 所以此时获得的是悬垂指针
- // 错误访问
所以,最后的 NSLog 只是碰巧正常运行,如果错误访问,会造成 crash
在使用 __unsafe_unretained 修饰符时,赋值给附有 __strong 修饰符变量时,要确保对象确实存在
相关文章:

Google工作原理
今天在晚上看到一个图,讲解google的工作原理,感觉写的不错。贴过来方便以后深入的研究。 转载于:https://www.cnblogs.com/muyuge/archive/2010/07/06/6152590.html
如何利用ArcGis修改shp数据字段名称
最近在处理一批地理信息数据,其中涉及到对shp文件属性字段的修改,在这里做个记录,以防大家再走弯路。 工具: Arcgis软件shp文件 第1步:打开ArcCatalog,选择左上角的链接文件夹,选择你存放数据…

学java为什么要报java培训班?
学java为什么要报java培训班?对于没有基础的小白来说,选择报java培训班是最合适不过的,自学是没有任何规划的,学到的技术都是模棱两可,工作入职后是存在很大风险的,具体的来看看下面的详细介绍吧。 学java为什么要报j…

Tensorflow 全网最全学习资料汇总之框架平台的综合对比【3】
作为机器学习领域、尤其是 Python 生态圈最受欢迎的框架平台,TensorFlow 具有许多吸引开发者的优点。其中最显而易见的是谷歌的技术支持和完善的社区(庞大用户群)。这些都为 TensorFlow 的普及打下了基础。但是,开发者需要了解 Te…

空间两点间的距离
空间两点间的距离公式推导,有图有真相 转载于:https://www.cnblogs.com/graphics/archive/2010/07/08/1773966.html
如何利用ArcGis把经纬度转成shp数据
这段时间在处理一批地理信息数据,由于部分数据是经纬度坐标,如下图所示: 这样,面对的第一个问题,就是把这批数据转换成shp格式。下面做一个记录,与大家分享。 工具: ArcGIS 软件 Step1&#x…

新手参加java培训都学什么
互联网的强大使得很多IT技术变得越来越吃香,java技术就是其中的一种,很多人都开始学习java技术,下面小编就为大家分享一些新手参加java培训都学什么?希望能够给零基础的学员带来一些帮助。 新手参加java培训都学什么? 1、对于新手学习java的…

第三百三十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—深度优先与广度优先原理...
第三百三十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—深度优先与广度优先原理 网站树形结构 深度优先 是从左到右深度进行爬取的,以深度为准则从左到右的执行(递归方式实现)Scrapy默认是深度优先的 广度优先 是以层级来执行的…

读懂ConnectString 中 enlist 设置的含义
因为上次遇到在webservice中处理事务的问题,偶然在调试程序的时候对OracleConnection的连接字符串enlist设置的一个有趣的发现。以前看过一篇文章,不记得是什么文章了,文章中说对enlist最好设置为false,当时也没有怎么去深究为什么…

你知道这些 985、211 院校的隶属吗?
前段时间为准备继续深造计算机方向的同学们整理了一些资料,包括: 全国第四轮学科评估结果 – 计算机科学与技术全国第四轮学科评估结果 – 软件工程你知道大陆地区的985、211院校都有哪些吗?你真的知道「专业硕士」与「学术硕士」的11个区别…

新手UI设计师必需要掌握的知识和技能
近几年,许多企业对于UI设计师这个岗位的需求量越来越大,UI设计师的发展空间可见越来越好,想要学好UI设计,必须要掌握足够的知识和技能,下面小编就为大家分享一下新手UI设计师必需要掌握的知识和技能,希望能…
SharePoint 2010中的客户端AJAX应用——ASP.NET AJAX模板
WCF Data Services是SharePoint 2010中一个极具吸引力的新特性。然而,因为它的强大,直接对其进行编程仍然会有点痛苦。幸运的是,一个新的相关技术 —— ASP.Net AJAX模板 – 可以完美的与WCF Data Service进行集成,并允许我们快速…

如何利用Gephi可视化浏览的网站关系
Gephi 是进行数据可视化的一套开源工具。其利用图(有向图、无向图、动态图等)的形式来展现数据,方便我们对数据进行探索。今天给大家介绍利用 Gephi 来可视化我们浏览网站之间关系。 首先,安装 Gephi 的 Http 代理插件 HttpGraph…

nginx 启动脚本
#vim /etc/rc.d/init.d/nginx #为nginx提供SysV init脚本#!/bin/sh## nginx - this script starts and stops the nginx daemon## chkconfig: - 85 15 # description: Nginx is an HTTP(S) server, HTTP(S) reverse \# proxy and IMAP/POP3 proxy server# …

参加前端培训主要学习什么语言
web前端近几年很多人都在学习中,但是想要学好web前端技术,基础是非常重要的,参加web前端培训机构可以进行系统的学习,下面就给大家详细的介绍一下参加前端培训主要学习什么语言? 参加前端培训主要学习什么语言?前端的基础就是HT…
嘿,程序员,你该学点经济学了!
前言: 笔者一直认为,一个好的程序员。不仅仅是代码敲得好,其它方面的知识和能力相同非常重要。特别是随着年龄的增长。非常多人也慢慢的往管理层发展。这个时候沟通与协调能力变得更加重要,而一些策划。推广方面的知识也相同是必不…

记录一次自己调试代码的过程
今年年初我们做了一套防窃电的软件,其中通讯采取的是串口方式。前段时间,根据现场的反馈,我们增加了蓝牙通讯的功能。系统界面如下图所示: 今天,现场人员反馈说:“解析的数据出现问题”,所以我在…

CBitmapButton的使用(转)
CBitmapButton的使用 CBitmapButton作为MFC的控件类,并不为很多人所使用,因为现在网上遍布着从CButton派生的各种各样的按钮类,其中最为著名的就是CButtonST类了。但是最近在CSDN上看到几个问题都是使用CBitmapButton类,但是由于…

web前端干货:详细了解JS前端开发框架都有哪些
1. Foundation框架 Foundation框架总体来看要比Bootstrap略显高大上一点,但他们俩的设计理念都是非常清楚的,Bootstrap有引导的意思,尝试处理你项目中的一切所需。Foundation有基础、地基及支柱的意思,给项目中强有力的创造与支持…

Platform Builder 5下WinCE 5.0目录结构
Platform Builder 5下WinCE 5.0目录结构 Platform Builder 5已经自带WinCE 5.0,安装过程会指定WinCE 5.0的安装路径,默认为X:\WINCE500,WINCE500即为WinCE 5.0的根目录。根目录下主要有以下几个目录:PUBLIC, PLATFORM, PRIVATE, P…

记录一次自己清理数据的过程
今天接到一个任务,从原始数据(在不同监测点对白纹伊蚊,18周的监测数据)中提取监测点列表,然后从网上爬取各个监测点的空间信息(经纬度),并把这些经纬度数据转换成墨卡托坐标…

man nfsd(rpc.nfsd中文手册)
本人译作集合:http://www.cnblogs.com/f-ck-need-u/p/7048359.html rpc.nfsd(8) System Managers Manual rpc.nfsd(8)NAMErpc.nfsd - NFS服务进程SYNOPSIS/usr/sbin/rpc.nfsd [options] nprocDESCRIPTIONrpc.nfsd程序…

Java学习从入门到精通的学习建议
想要学好java技术,首先打好基础很重要,不论学什么基础都是重中之重,学习Java更是如此。如:基础语法、核心类库、面向对象编程、异常、集合、IO流等基础如果学不好,那么后边更深入的语法也不容易学会。所以在学基础部分…

Automatically highlight current page in menu via Javascript
Please check the link, http://www.richnetapps.com/automatically_highlight_current_page_in/ 转载于:https://www.cnblogs.com/OceanChen/archive/2010/08/10/1796410.html

基于Vue的小日历(支持按周切换)
基于Vue的日历小功能,可根据实际开发情况按每年、每月、每周、进行切换 <template><div class"date"><!-- 年份 月份 --><div class"month"><p>{{ currentYear }}年{{ currentMonth }}月</p></div>&…

股市币市:数据分析与交易所公告(20190225)
沪深300 1. 沪深300分位数数据 2. 沪深300股指图 3. 沪深300分位数图 4. 沪深300筹码分布图 数据来源: https://finance.sina.com.cn/stock/ BTC比特币 1. 比特币分位数数据 2. 比特币交易图 3. 比特币分位数图 4. 比特币筹码分布图 数据来源: htt…

哪些人适合参加UI设计培训
UI设计在最近几年受到了很多人的关注,想要学习UI设计技术的人越来越多,大部分选择报UI设计培训班进行学习,有些人想要通过自学来学,那么到底哪些人适合参加UI设计培训呢?来看看下面的详细介绍。 哪些人适合参加UI设计培训? 1. 零…

java常用的7大排序算法汇总
这段时间闲了下来,就抽了点时间总结了下java中常用的七大排序算法,希望以后可以回顾! 1.插入排序算法 插入排序的基本思想是在遍历数组的过程中,假设在序号 i 之前的元素即 [0..i-1] 都已经排好序,本趟需要找到 i 对应…

深度优先搜索算法在RPG游戏迷宫中的应用
在RPG游戏中我们经常会看到一些迷宫,我之前玩仙剑一的时候就经常在几个迷宫里绕来绕去也绕不出来,玩仙三由于游戏视角可以转,更是费劲。这里我们使用深度优先算法达到遍历一个迷宫的目的。 首先定义一个有序元组A:{左,上ÿ…

又有六所大学考研预调剂系统已开放!
距离 19 考研初试成绩的公布已经有一周了,成绩不错的同学就安心准备复试吧,全力备考,一定要拿到属于你的录取通知书!成绩不满意,擦线或者排名靠后的同学,复试、调剂两手准备,注定咱们要花更多的…