iOS内存管理.docx

上传人:b****4 文档编号:12264561 上传时间:2023-04-17 格式:DOCX 页数:21 大小:112.17KB
下载 相关 举报
iOS内存管理.docx_第1页
第1页 / 共21页
iOS内存管理.docx_第2页
第2页 / 共21页
iOS内存管理.docx_第3页
第3页 / 共21页
iOS内存管理.docx_第4页
第4页 / 共21页
iOS内存管理.docx_第5页
第5页 / 共21页
点击查看更多>>
下载资源
资源描述

iOS内存管理.docx

《iOS内存管理.docx》由会员分享,可在线阅读,更多相关《iOS内存管理.docx(21页珍藏版)》请在冰豆网上搜索。

iOS内存管理.docx

iOS内存管理

iOS进阶iOS内存管理

我将在本篇博文中详细的从ARC解释到iOS的内存管理,以及Block相关的原理、源码。

作者:

佚名来源:

iOS大全|2017-03-0710:

15

 收藏

  分享

1似乎每个人在学习iOS过程中都考虑过的问题

∙allocretainreleasedelloc做了什么?

∙autoreleasepool是怎样实现的?

∙__unsafe_unretained是什么?

∙Block是怎样实现的

∙什么时候会引起循环引用,什么时候不会引起循环引用?

所以我将在本篇博文中详细的从ARC解释到iOS的内存管理,以及Block相关的原理、源码。

2从ARC说起

说iOS的内存管理,就不得不从ARC(AutomaticReferenceCounting/自动引用计数)说起,ARC是WWDC2011和iOS5引入的变化。

ARC是LLVM3.0编译器的特性,用来自动管理内存。

与Java中GC不同,ARC是编译器特性,而不是基于运行时的,所以ARC其实是在编译阶段自动帮开发者插入了管理内存的代码,而不是实时监控与回收内存。

 

ARC的内存管理规则可以简述为:

∙每个对象都有一个『被引用计数』

∙对象被持有,『被引用计数』+1

∙对象被放弃持有,『被引用计数』-1

∙『引用计数』=0,释放对象

3你需要知道

∙包含NSObject类的Foundation框架并没有公开

∙CoreFoundation框架源代码,以及通过NSObject进行内存管理的部分源代码是公开的。

∙GNUstep是Foundation框架的互换框架

GNUstep也是GNU计划之一。

将CocoaObjective-C软件库以自由软件方式重新实现

某种意义上,GNUstep和Foundation框架的实现是相似的

通过GNUstep的源码来分析Foundation的内存管理

4allocretainreleasedealloc的实现

4.1GNU–alloc

查看GNUStep中的alloc函数。

GNUstep/modules/core/base/Source/NSObject.malloc:

1.+ (id) alloc 

2. 

3.{ 

4. 

5.return [self allocWithZone:

 NSDefaultMallocZone()]; 

6. 

7.}  

8.  

9. 

10.+ (id) allocWithZone:

 (NSZone*)z 

11. 

12.{ 

13. 

14.return NSAllocateObject (self, 0, z); 

15. 

16.}  

GNUstep/modules/core/base/Source/NSObject.mNSAllocateObject:

1.struct obj_layout { 

2. 

3.NSUInteger retained; 

4. 

5.}; 

6. 

7.  

8. 

9.NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone) 

10. 

11.{ 

12. 

13.int size = 计算容纳对象所需内存大小; 

14. 

15.id new = NSZoneCalloc(zone, 1, size); 

16. 

17.memset (new, 0, size); 

18. 

19.new = (id)&((obj)new)[1]; 

20. 

21.}  

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

我们将上面的代码做简化整理:

GNUstep/modules/core/base/Source/NSObject.malloc简化版本:

1.struct obj_layout { 

2. 

3.NSUInteger retained; 

4. 

5.}; 

6. 

7.  

8. 

9.+ (id) alloc 

10. 

11.{ 

12. 

13.int size = sizeof(struct obj_layout) + 对象大小; 

14. 

15.struct obj_layout *p = (struct obj_layout *)calloc(1, size); 

16. 

17.return (id)(p+1) 

18. 

19.return [self allocWithZone:

 NSDefaultMallocZone()]; 

20. 

21.}  

alloc类方法用structobj_layout中的retained整数来保存引用计数,并将其写入对象的内存头部,该对象内存块全部置为0后返回。

一个对象的表示便如下图:

4.2GNU–retain

GNUstep/modules/core/base/Source/NSObject.mretainCount:

1.- (NSUInteger) retainCount 

2. 

3.{ 

4. 

5.return NSExtraRefCount(self) + 1; 

6. 

7.} 

8.  

9. 

10.inline NSUInteger 

11. 

12.NSExtraRefCount(id anObject) 

13. 

14.{ 

15. 

16.return ((obj_layout)anObject)[-1].retained; 

17. 

18.}  

GNUstep/modules/core/base/Source/NSObject.mretain:

1.- (id) retain 

2. 

3.{ 

4. 

5.NSIncrementExtraRefCount(self); 

6. 

7.return self; 

8. 

9.} 

10. 

11.  

12. 

13.inline void 

14. 

15.NSIncrementExtraRefCount(id anObject) 

16. 

17.{ 

18. 

19.if (((obj)anObject)[-1].retained == UINT_MAX - 1) 

20. 

21.[NSException raise:

 NSInternalInconsistencyException 

22. 

23.format:

 @"NSIncrementExtraRefCount() asked to increment too far”]; 

24. 

25.((obj_layout)anObject)[-1].retained++; 

26. 

27.}  

以上代码中,NSIncrementExtraRefCount方法首先写入了当retained变量超出最大值时发生异常的代码(因为retained是NSUInteger变量),然后进行retain++代码。

4.3GNU–release

和retain相应的,release方法做的就是retain--。

GNUstep/modules/core/base/Source/NSObject.mrelease

1.- (oneway void) release 

2. 

3.{ 

4. 

5.if (NSDecrementExtraRefCountWasZero(self)) 

6. 

7.{ 

8. 

9.[self dealloc]; 

10. 

11.} 

12. 

13.} 

14. 

15.  

16. 

17.BOOL 

18. 

19.NSDecrementExtraRefCountWasZero(id anObject) 

20. 

21.{ 

22. 

23.if (((obj)anObject)[-1].retained == 0) 

24. 

25.{ 

26. 

27.return YES; 

28. 

29.} 

30. 

31.((obj)anObject)[-1].retained--; 

32. 

33.return NO; 

34. 

35.}  

4.4GNU–dealloc

dealloc将会对对象进行释放。

GNUstep/modules/core/base/Source/NSObject.mdealloc:

1.- (void) dealloc 

2. 

3.{ 

4. 

5.NSDeallocateObject (self); 

6. 

7.} 

8.  

9. 

10.inline void 

11. 

12.NSDeallocateObject(id anObject) 

13. 

14.{ 

15. 

16.obj_layout o = &((obj_layout)anObject)[-1]; 

17. 

18.free(o); 

19. 

20.}  

4.5Apple实现

在Xcode中设置Debug->DebugWorkflow->AlwaysShowDisassenbly打开。

这样在打断点后,可以看到更详细的方法调用。

通过在NSObject类的alloc等方法上设置断点追踪可以看到几个方法内部分别调用了:

retainCount

1.__CFdoExternRefOperation 

2.CFBasicHashGetCountOfKey  

retain

1.__CFdoExternRefOperation 

2.CFBasicHashAddValue  

release

1.__CFdoExternRefOperation 

2.CFBasicHashRemoveValue  

可以看到他们都调用了一个共同的__CFdoExternRefOperation方法。

该方法从前缀可以看到是包含在CoreFoundation,在CFRuntime.c中可以找到,做简化后列出源码:

CFRuntime.c__CFDoExternRefOperation:

1.int __CFDoExternRefOperation(uintptr_t op, id obj) { 

2. 

3.CFBasicHashRef table = 取得对象的散列表(obj); 

4. 

5.int count; 

6. 

7.  

8. 

9.switch (op) { 

10. 

11.case OPERATION_retainCount:

 

12. 

13.count = CFBasicHashGetCountOfKey(table, obj); 

14. 

15.return count; 

16. 

17.break; 

18. 

19.case OPERATION_retain:

 

20. 

21.count = CFBasicHashAddValue(table, obj); 

22. 

23.return obj; 

24. 

25.case OPERATION_release:

 

26. 

27.count = CFBasicHashRemoveValue(table, obj); 

28. 

29.return 0 == count; 

30. 

31.} 

32. 

33.}  

所以__CFDoExternRefOperation是针对不同的操作,进行具体的方法调用,如果op是OPERATION_retain,就去掉用具体实现retain的方法。

从BasicHash这样的方法名可以看出,其实引用计数表就是散列表。

key为hash(对象的地址)value为引用计数。

下图是Apple和GNU的实现对比:

5autorelease和autorelaesepool

在苹果对于NSAutoreleasePool的文档中表示:

每个线程(包括主线程),都维护了一个管理NSAutoreleasePool的栈。

当创先新的Pool时,他们会被添加到栈顶。

当Pool被销毁时,他们会被从栈中移除。

autorelease的对象会被添加到当前线程的栈顶的Pool中。

当Pool被销毁,其中的对象也会被释放。

当线程结束时,所有的Pool被销毁释放。

对NSAutoreleasePool类方法和autorelease方法打断点,查看其运行过程,可以看到调用了以下函数:

1.NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

2. 

3.// 等同于 objc_autoreleasePoolPush 

4. 

5.  

6. 

7.id obj = [[NSObject alloc] init]; 

8. 

9.[obj autorelease]; 

10. 

11.// 等同于 objc_autorelease(obj) 

12. 

13.  

14. 

15.[NSAutoreleasePool showPools]; 

16. 

17.// 查看 NSAutoreleasePool 状况 

18. 

19.  

20. 

21.[pool drain]; 

22. 

23.// 等同于 objc_autoreleasePoolPop(pool)  

[NSAutoreleasePoolshowPools]可以看到当前线程所有pool的情况:

1.objc[21536]:

 ############## 

2. 

3.objc[21536]:

 AUTORELEASE POOLS for thread 0x10011e3c0 

4. 

5.objc[21536]:

 2 releases pending. 

6. 

7.objc[21536]:

 [0x101802000] ................ PAGE (hot) (cold) 

8. 

9.objc[21536]:

 [0x101802038] ################ POOL 0x101802038 

10. 

11.objc[21536]:

 [0x101802040] 0x1003062e0 NSObject 

12. 

13.objc[21536]:

 ############## 

14. 

15.Program ended with exit code:

 0  

在objc4中可以查看到AutoreleasePoolPage:

1.objc4/NSObject.mm AutoreleasePoolPage 

2. 

3.  

4. 

5.class AutoreleasePoolPage 

6. 

7.{ 

8. 

9.static inline void *push() 

10. 

11.{ 

12. 

13.生成或者持有 NSAutoreleasePool 类对象 

14. 

15.} 

16. 

17.static inline void pop(void *token) 

18. 

19.{ 

20. 

21.废弃 NSAutoreleasePool 类对象 

22. 

23.releaseAll(); 

24. 

25.} 

26. 

27.static inline id autorelease(id obj) 

28. 

29.{ 

30. 

31.相当于 NSAutoreleasePool 类的 addObject 类方法 

32. 

33.AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 实例; 

34. 

35.} 

36. 

37.id *add(id obj) 

38. 

39.{ 

40. 

41.将对象追加到内部数组 

42. 

43.} 

44. 

45.void releaseAll() 

46. 

47.{ 

48. 

49.调用内部数组中对象的 release 方法 

50. 

51.} 

52. 

53.}; 

54. 

55.  

56. 

57.void * 

58. 

59.objc_autoreleasePoolPush(void) 

60. 

61.{ 

62. 

63.if (UseGC) return nil; 

64. 

65.return AutoreleasePoolPage:

:

push(); 

66. 

67.} 

68. 

69.  

70. 

71.void 

72. 

73.objc_autoreleasePoolPop(void *ctxt) 

74. 

75.{ 

76. 

77.if (UseGC) return; 

78. 

79.AutoreleasePoolPage:

:

pop(ctxt); 

80. 

81.}  

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修饰符的变量不属于编译器的内存管理对象。

赋值时即不获得强引用也不获得弱引用。

来运行一段代码:

1.id __unsafe_unretained obj1 = nil; 

2. 

3.{ 

4. 

5.id __strong obj0 = [[NSObject alloc] init];  

6.  

7. 

8.obj1 = obj0;  

9.  

10. 

11.NSLog(@"A:

 %@", obj1); 

12. 

13.}  

14.  

15. 

16.NSLog(@"B:

 %@", obj1);  

运行结果:

1.2017-01-12 19:

24:

47.245220 __unsafe_unretained[55726:

4408416] A:

 

2. 

3.2017-01-12 19:

24:

47.246670 __unsafe_unretained[55726:

4408416] B:

 

4. 

5.Program ended with exit code:

 0  

对代码进行详细分析:

1.id __unsafe_unretained obj1 = nil; 

2. 

3.{ 

4. 

5.// 自己生成并持有对象 

6. 

7.id __strong obj0 = [[NSObject alloc] init]; 

8. 

9.  

10. 

11.// 因为 obj0 变量为强引用, 

12. 

13.// 所以自己持有对象 

14. 

15.obj1 = obj0; 

16. 

17.  

18. 

19.// 虽然 obj0 变量赋值给 obj1 

20. 

21.// 但是 obj1 变量既不持有对象的强引用,也不持有对象的弱引用 

22. 

23.NSLog(@"A:

 %@", obj1); 

24. 

25.// 输出 obj1 变量所表示的对象 

26. 

27.} 

28. 

29.  

30. 

31.NSLog(@"B:

 %@", obj1); 

32. 

33.// 输出 obj1 变量所表示的对象 

34. 

35.// obj1 变量表示的对象已经被废弃 

36. 

37.// 所以此时获得的是悬垂指针 

38. 

39.// 错误访问  

所以,最后的NSLog只是碰巧正常运行,如果错误访问,会造成crash

在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符变量时,要确保对象确实存在

【编辑推荐】

1.iOS抓取HTML,CSSXPath解析数据

2.iOS与Android开发——我们该如何选择?

3.用Jenkins搭建iOS/Android持续集成打包平台

4.iOS10个实用小技巧(总有你不知道的和你会用到的)

5.iOS进阶——Block

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1