alloc retain release delloc 做了什么?
autoreleasepool 是怎样实现的?
__unsafe_unretained 是什么?
Block 是怎样实现的
什么时候会引起循环引用,什么时候不会引起循环引用?
所以我将在本篇博文中详细的从 ARC 解释到 iOS 的内存管理,以及 Block 相关的原理、源码。
说 iOS 的内存管理,就不得不从 ARC(Automatic Reference Counting / 自动引用计数) 说起, ARC 是 WWDC2011 和 iOS5 引入的变化。ARC 是 LLVM 3.0 编译器的特性,用来自动管理内存。
与 Java 中 GC 不同,ARC 是编译器特性,而不是基于运行时的,所以 ARC 其实是在编译阶段自动帮开发者插入了管理内存的代码,而不是实时监控与回收内存。
ARC 的内存管理规则可以简述为:
每个对象都有一个『被引用计数』
对象被持有,『被引用计数』+1
对象被放弃持有,『被引用计数』-1
『引用计数』=0,释放对象
包含 NSObject 类的 Foundation 框架并没有公开
Core Foundation 框架源代码,以及通过 NSObject 进行内存管理的部分源代码是公开的。
GNUstep 是 Foundation 框架的互换框架
GNUstep 也是 GNU 计划之一。将 Cocoa Objective-C 软件库以自由软件方式重新实现
某种意义上,GNUstep 和 Foundation 框架的实现是相似的
通过 GNUstep 的源码来分析 Foundation 的内存管理
查看 GNUStep 中的 alloc 函数。
GNUstep/modules/core/base/Source/NSObject.m alloc:
GNUstep/modules/core/base/Source/NSObject.m NSAllocateObject:
NSAllocateObject
函数通过调用 NSZoneCalloc
函数来分配存放对象所需的空间,之后将该内存空间置为 nil,最后返回作为对象而使用的指针。
我们将上面的代码做简化整理:
GNUstep/modules/core/base/Source/NSObject.m alloc 简化版本:
alloc 类方法用 struct obj_layout 中的 retained
整数来保存引用计数,并将其写入对象的内存头部,该对象内存块全部置为 0 后返回。
一个对象的表示便如下图:
GNUstep/modules/core/base/Source/NSObject.m retainCount:
GNUstep/modules/core/base/Source/NSObject.m retain:
以上代码中, NSIncrementExtraRefCount
方法首先写入了当 retained
变量超出最大值时发生异常的代码(因为 retained
是 NSUInteger 变量),然后进行 retain ++
代码。
和 retain 相应的,release 方法做的就是 retain --
。
GNUstep/modules/core/base/Source/NSObject.m release
dealloc 将会对对象进行释放。
GNUstep/modules/core/base/Source/NSObject.m dealloc:
在 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:
所以 __CFDoExternRefOperation
是针对不同的操作,进行具体的方法调用,如果 op 是 OPERATION_retain
,就去掉用具体实现 retain 的方法。
从 BasicHash
这样的方法名可以看出,其实引用计数表就是散列表。
key 为 hash(对象的地址) value 为 引用计数。
下图是 Apple 和 GNU 的实现对比: