前文:引用计数

请先看我的这篇文章:
引用计数

内存管理

1.1 alloc/retain/release/dealloc的实现

在这里,我们使用开源软件GNUstep(GNUstep的源代码虽不能说与苹果的Cocoa完全相同,胆识从使用者角度来看,两者的行为和实现方法是一样的。)来说明。

alloc实现

在Objective-C中,对象的创建和初始化通常是由类的工厂方法和初始化方法来完成的。这些方法的实现通常包含了为对象分配内存和初始化对象的代码。

以NSObject类的alloc方法为例,其源代码如下:

+ (instancetype)alloc {return [self allocWithZone:NULL];
}

这个方法是一个类方法,返回一个新分配的对象。它实际上是调用了类的allocWithZone:方法并传入了一个NULL的参数。

而allocWithZone:方法的源代码如下:

+ (instancetype)allocWithZone:(struct _NSZone *)zone {return NSAllocateObject(self, 0, zone);
}

这个方法也是一个类方法,返回一个新分配的对象。它实际上是调用了NSAllocateObject函数来为对象分配内存。

关于NSAllocateObject函数,其实现如下:

struct obj_layout {NSUInteger retained;
};inline id;
NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone)
{int size = 计算容纳对象所需内存的大小;id new = NSZoneMalloc(Zone,size);memset (new, 0, size);new = (id)&((struct obj_layout *) new)[1];
}

NSAllocateObject函数通过调用NSZoneMalloc函数来分配存放对象所需要的内存空间,之后将空间置0,最好返回作为对象使用的指针。

以下是去掉NSZone后简化了的源代码:

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);
}

alloc类方法用struct obj_layout 中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置0后返回。以下用图示来展示有关GNUstep的实现。

retain

对象的引用计数可用retainCount实例方法获得。
下面是GNUstep源代码。

- (NSUInteger) retainCount
{return NSExtraRefCount (self) + 1;
}inline NSUInteger
NSExtraRefCount (id anOnject)
{return ((struct obj_layout *) anObject)[-1].retained;
}

由地址寻找到对象内存头部,从而访问其中的retained变量。

因为分配时全部置0,所以retained 为0。由NSExtraRefCount(self)+1得出,retainCount为1。可以推测出retain方法使retained变量加1,而release方法使retained变量减1。

[obj retain];

下面看一下像上面那样调用出的retain实例方法。

- (id) retain
{NSIncermentExtraRefCount(self);return self;
}inline void
NSIncrementExtraRefCount(id anObject)
{if (((struct obj_layout *)anObject[-1].retained == UINT_MAX - 1)[NSException raise:NSInternalInconsistencyException format:@"NSIncrementExtraRefCount () asked to increment too far"]);((struct obj_layout *) anObject)[-1].retained++;
}

这段代码实现了 Objective-C 对象的引用计数机制中 retain 方法的具体实现。当我们调用一个对象的 retain 方法时,其引用计数值会加1,表示有一个新的对象持有了该对象的引用,从而防止该对象在被其它对象持有的引用都被释放后被系统回收。

具体实现中,retain 方法内部会调用 NSIncrementExtraRefCount 函数来实现引用计数的增加。NSIncrementExtraRefCount 函数会将传入的对象的 retained 域加1,该域存储了当前对象被引用的次数。如果 retained 域已经达到最大值(即 UINT_MAX - 1),则会抛出一个内部不一致的异常。

在这段代码中,通过将传入的对象指针 anObject 转换为结构体指针 ((struct obj_layout *) anObject),然后访问其前一个元素 [-1],即访问对象所在内存块的前一个元素,这个前一个元素就是对象的布局结构体,通过访问这个布局结构体的 retained 域,可以得到当前对象的引用计数值,进而实现对其进行增加的操作。

release

以下为release方法的实现。

- (id) release
{if (NSDecrementExtraRefCountWasZero(self))[self dealloc];
}BOOL
NSDecrementExtraRefCountWasZero(id anObject)
{if (((struct obj_laout *) anObject)[-1].retained == 0) {return YES;} else {((struct obj_laout *) anObject)[-1].retained--;return NO;}
}

这段代码实现了 Objective-C 对象的引用计数机制中 release 方法的具体实现。当retained变量大于0时减1,等于0时调用dealloc实例方法,废弃对象。

具体引用计数的访问与retain方法相同。

dealloc

以下为delloc实例方法的实现。

- (void)dealloc
{NSDealloocateObject(self);
}
inline void
NSDeallocateObject(id anObject)
{struct obj_layout *o = &((struct obj_layout *)anObject0[-1];free(o);
}

上述代码仅废弃由alloc分配的代码块。

1.2 苹果的实现

因为NSObject类的源代码没有公开,此处利用Xcode的调试器和iOS大概追溯出其实现过程。

在NSObject类的alloc类方法上设置断点,追踪程序的执行。以下列出了执行所调用的方法和函数。

+alloc
+allocWithZone:
class_createInstance
calloc

其中:
class_createInstance 是一个 Objective-C Runtime API,用于创建一个指定类的实例对象。它的声明如下:

id class_createInstance(Class cls, size_t extraBytes);

其中,cls 是要创建实例对象的类,extraBytes 是要为这个对象分配的额外字节数,通常情况下这个参数为 0。函数返回一个指向新创建实例对象的指针。

其中调用了calloc函数分配内存块。

同样的方法,看一下retainCout/retain/release实例方法实现。

-retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
-retain
__CFDoExternRefOperation
CFBasicHashAddValue
-release
__CFDoExternRefOperation
CFBasicHashRemoveValue
(CFBasicHashRemoveValue 返回0时,-release调用dealloc)

其中:
__CFDoExternRefOperation 是 Core Foundation 框架中的一个 C 函数,用于执行外部引用计数操作。在 Core Foundation 中,有一类对象叫做外部引用对象(External References),这些对象的引用计数不由 Core Foundation 管理,而是由外部程序来管理。当 Core Foundation 操作这些对象时,需要调用外部程序提供的函数来对对象的引用计数进行操作,这就是 __CFDoExternRefOperation 函数的作用。

__CFDoExternRefOperation 函数的声明如下:

void __CFDoExternRefOperation(CFTypeRef cf, void (*op)(CFTypeRef cf1, void *context), void *context, Boolean isDeallocating);

该函数接收四个参数:

cf:外部引用对象的指针。
op:指向外部程序提供的引用计数操作函数的指针。这个函数接收两个参数,第一个参数是外部引用对象的指针,第二个参数是上下文信息指针。
context:指向上下文信息的指针,传递给 op 函数。
isDeallocating:表示是否正在释放对象的标志。
在 __CFDoExternRefOperation 函数中,我们首先对传入的参数进行了非空判断,然后根据 isDeallocating 参数的值选择调用外部程序提供的引用计数操作函数 op 的不同版本。如果 isDeallocating 参数为 true,就调用 op 函数的释放版本,否则就调用 op 函数的保留版本。在调用 op 函数之前,我们先对对象进行了锁定操作,防止在操作期间发生对象被释放的情况。

需要注意的是,__CFDoExternRefOperation 函数只是一个内部函数,外部程序一般不会直接调用它。外部程序通常会使用 Core Foundation 提供的一些宏或函数来管理外部引用对象的引用计数,例如 CFMakeExternalRefCounted()、CFRetain() 和 CFRelease() 等函数。

下面是简化了的__CFDoExternRefOperation函数的源代码。

int  __CFDoExternRefOperation(uintptr_t op, id obj) {CFBasicHashRef table = 取得对像对应的散列表(obj);int count;switch(op) {case OPERATTON_retainCount:count = CFBasicHashGetCountOfKey(table, obj);return count;case OPERATTON_retain:CFBasicHashAddValue(table, obj);return obj;case OPERATTON_release:count = CFBasicHashRemoveValue(table, obj);return 0 == count;}
}

__CFDoExternRefOperation函数按retainCount/retain/release操作进行分发,调用不同的函数。NSObject类的retainCount/retain/release实例方法也许如下面代码所示。

- (NSUInteger) retainCount
{return (NSInteger) __CFDoExternRefOperation(OPERATTON_retainCount, self);
}- (id) retain
{return (id) __CFDoExternRefOperation(OPERATTON_retain, self);
}- (void) release
{return  __CFDoExternRefOperation(OPERATTON_release, self);
}

可以从__CFDoExternRefOperation函数以及由此函数调用的各个函数名来看,苹果的实现大概就是采用散列表(引用计数表)来管理引用计数。如图:

这样实现与GNUstep相比的好处:

通过内存块头部管理引用计数的好处如下:

  • 少量代码即可完成
  • 能够统一管理引用计数内存块与对象的内存块

通过引用计数表管理引用计数的好处如下:

  • 对象用内存块的分配无需考虑内存块头部。
  • 引用计数表各记录中存有内存地址,可从各个记录追溯到各对象的内存块。

另外,在利用工具检测内存泄露时,引用计数表的各记录也有助于检测各对象的持有者是否存在。

1.3 autorelease

autorelease方法可以使取得的对象存在,但自己不持有对象。autorelease提供这样的功能,是对象在超出指定的生存范围时能够自动并正确地释放(release方法)。

另外,编程人员可以设定变量的作用域。

autrelease的具体使用方法如下:

  • 生成并持有NSAutoreleasePool对象;
  • 调用已分配对象的autorrelease实例方法;
  • 废弃NSAutoreleasePoll对象。

在Cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有和废弃处理。

1.4 autorelease实现

autorelease的实现。GNUstep的源代码。

[obj autorelease];

此源代码调用NSObject类的autorelease实例方法。

- (id) autorelease
{[NSAutoreleasePool addObject:self];
}

autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法。

下面看NSAutoreleasePool类的实现。由于NSAutoreleasePool类的源代码比较复杂,所以我们假象一个简化的源代码进行说明。

+ (void) addObject:(id)anObj
{NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool对象;if (pool != nil) {[pool addObject:anObj];} else {NSLog(@"NSAutoreleasePool对象非存在状态下调用autorelease");}
}

addObject类方法调用正在使用的NSAutoreleasePool对象的addobject实例方法。以下源代码中,被赋予pool变量的即为正在使用的NSAutoreleasePool对象。

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

如果嵌套生成或持有的NSAutoreleasePool对象,理所当然会使用最内侧的对象。下例中,pool2为正在使用的NSAutoreleasePool对象。

NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc] init];NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];id obj = [[NSObject alloc] init];[obj autorelease];[pool2 drain];[pool1 drain];
[pool0 drain];

下面看一下addObject实例方法的实现。

- (void) add object:(id) anObj
{[array addObject:anObj];
}

实际的GNUstep实现使用的是连接列表,这同在NSMutableArray对象中追加对象参数是一样的。

如果调用NSObject类的autorelease实例方法,该对象将被追加到正在使用的NSAutoreleasePool对象的数组里。

[pool drain];

以下为通过drain实例方法废弃正在使用的NSAutoreleasePool对象的过程。

- (void) drain
{[self dealloc];
}- (void) dealloc
{[self emptyPool];[array release];
}- (void) emtyPool
{for (id obj in array) {[obj release];}
}

【iOS】ARC学习相关推荐

  1. ios开发学习-手势交互(Gesture)效果源码分享

    qianqianlianmeng ios开发学习-手势交互(Gesture)效果源码分享 All Around Pull View 介绍:实现视图四个方向(上下左右)都能够拖动更新(pull to r ...

  2. ios开发学习笔记--Core Motion

    iOS开发学习笔记之CoreMotion-运动传感器 官网文档:CoreMotion Framework Reference 一.     简介 现在的苹果手机都基本有运动传感器,能够过获取到设备的加 ...

  3. ios网络学习------4 UIWebView的加载本地数据的三种方式

    ios网络学习------4 UIWebView的加载本地数据的三种方式 分类: IOS2014-06-27 12:56 959人阅读 评论(0) 收藏 举报 UIWebView是IOS内置的浏览器, ...

  4. IOS之学习笔记十五(协议和委托的使用)

    1.协议和委托的使用 1).协议可以看下我的这篇博客 IOS之学习笔记十四(协议的定义和实现) https://blog.csdn.net/u011068702/article/details/809 ...

  5. 开源中国iOS客户端学习——(八)网络通信AFNetworking类库

    AFNetworking是一个轻量级的iOS网络通信类库,继ASI类库不在更新之后开发者们有一套不错选择: AFNetworking类库×××和使用教程: https://github.com/AFN ...

  6. iOS手势学习UIGestureRecognizer cocos2d 手势推荐

    iOS手势学习UIGestureRecognizer & cocos2d 手势推荐 手势识别类型: UILongPressGestureRecognizer  // 长按 UIPanGestu ...

  7. IOS开发学习笔记-----UILabel 详解

    IOS开发学习笔记-----UILabel 详解 01 //创建uilabel 02 UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectMa ...

  8. [iOS]ARC下循环引用的问题

    转载自:http://blog.cnbang.net/tech/2085/ [iOS]ARC下循环引用的问题 2013-8-30 最初 最近在开发应用时碰到使用ASIHttpRequest后在某些机器 ...

  9. iOS开发学习48 OC的lambda block

    iOS开发学习48 lambda表达式 一.block 简介 二.block使用 1. block的写法大概就是这样: 2. 带参数的话可以这样写: 3. 如果不写入参,可以写: 4. 当然返回也可以 ...

  10. 开源中国iOS客户端学习

    开源中国iOS客户端学习 续写前言 <开源中国iOS客户端学习>续写前系列博客    http://blog.csdn.net/column/details/xfzl-kykhd.html ...

最新文章

  1. 爬虫之lxml模块中etree.tostring函数的使用
  2. python软件界面-用Html来写Python桌面软件的UI界面-htmlPy
  3. HashMap 的 7 种遍历方式与性能分析!「修正篇」
  4. 动态规划——搬寝室(hdu1421)
  5. VS2008启动调试,出现“ 已经找到网站 正在等待回应”
  6. c语言求1到20的各个阶乘,c语言求阶乘(c语言求1到20的阶乘)
  7. 车辆方向盘转角传动比标定方法
  8. C# 打印PDF文件
  9. 幼儿园趣味舞蹈课教案
  10. ORA-01950: 对表空间 'USERS' 无权限
  11. 只有程序猿才能看懂的段子,不笑你拿小拳拳捶我!!!
  12. 饥荒控制台输入没用_《饥荒》控制台正确使用教程 如何使用控制台
  13. NLP-信息抽取-三元组-联合抽取-多任务学习-2019:CasRel【关系三元组抽取:一种新的级联二元标注框架】【没用CRF】【基于Lic2019比赛】【数据集:NYT、WebNLG】
  14. Docker一些使用问题的解决方法
  15. 文件随机重命名的方法
  16. 小白学python.1
  17. nao机器人接力比赛
  18. AndroidStudio百度地图开发之显示地图
  19. 什么是质量?你真的了解吗?
  20. 看了必懂的并查集原理(转载)

热门文章

  1. R语言学习 - 富集分析泡泡图
  2. [实变函数]2.3 开集 (open set), 闭集 (closed set), 完备集 (complete set)
  3. 论文解读: PP-YOLOE: An evolved version of YOLO
  4. 从Solidworks中导出的.wrl文件无法打开?
  5. java 堆中的永久代_JVM中的堆的新生代、老年代、永久代详解
  6. adb 截屏和录屏命令,经典好文
  7. docker中的Jenkins修改密码或者忘记密码
  8. 欢迎进入半颗心脏博客导航一站式搜索(所有博客的汇总帖)
  9. python携程gevent_python gevent 协程
  10. JDBC最详讲解(快速入门)