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

开发笔记:iOS进阶iOS(ObjectiveC)内存管理

篇首语:本文由编程笔记#小编为大家整理,主要介绍了iOS进阶 - iOS(Objective-C) 内存管理相关的知识,希望对你有一定的参考价值。 作者:&nb

篇首语:本文由编程笔记#小编为大家整理,主要介绍了iOS进阶 - iOS(Objective-C) 内存管理相关的知识,希望对你有一定的参考价值。








作者: 小鱼









"猿吧"17年最新学习教程包括


<ios 、Java、大数据、Python、WEB前端、php等 视频+课件+代码+最新电子书>


资源共享通知






第一篇 iOS 内存管理

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



  1. alloc retain release delloc 做了什么?


  2. autoreleasepool 是怎样实现的?


  3. __unsafe_unretained 是什么?


  4. Block 是怎样实现的


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



所以我将在本篇博文中详细的从 ARC 解释到 iOS 的内存管理,以及 Block 相关的原理、源码。(篇幅过长,Block部分请复制请到原文阅读!


2 从 ARC 说起


说 iOS 的内存管理,就不得不从 ARC(Automatic Reference Counting / 自动引用计数) 说起, ARC 是 WWDC2011 和 iOS5 引入的变化。ARC 是 LLVM 3.0 编译器的特性,用来自动管理内存。


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







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




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


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


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


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






3 你需要知道


1. 包含 NSObject 类的 Foundation 框架并没有公开
(此处错误,感谢 的指出)



  1. Foundation 框架是非开源的,但是 NSObject 被包含在 中,该库已开源。


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


  3. 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 这样的方法名可以看出,其实引用计数表就是散列表。


下图是 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




在 中可以查看到 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);

}





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 修饰符变量时,要确保对象确实存在





iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理


iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理 iOS进阶 - iOS(Objective-C) 内存管理






iOS进阶 - iOS(Objective-C) 内存管理

有意思啊







还要错过关注我?










长按二维码


关注的不仅仅是技术










推荐阅读
  • 我的iOS开发入门自学路径
    我有一个清单,列着希望在大学里完成的事。比如,学一门乐器,和朋友去旅游,搭建自己的博客,去滑翔,看各主题的书籍。其中一项是,写自己的App并且上架。去年开始,我准备完成这一项,所以 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 实现一个通讯录系统,可添加、删除、修改、查找、显示、清空、排序通讯录信息
    本文介绍了如何实现一个通讯录系统,该系统可以实现添加、删除、修改、查找、显示、清空、排序通讯录信息的功能。通过定义结构体LINK和PEOPLE来存储通讯录信息,使用相关函数来实现各项功能。详细介绍了每个功能的实现方法。 ... [详细]
  • jsappsugar,基于,js,语法,定义 ... [详细]
  • 2015年iOS测试现状
    本文由伯乐在线-nathanw翻译,dopcn校稿。未经许可,禁止转载!英文出处:www.mokacoding.com。欢迎加入翻译小组。几周前,我决定将将我在mokacoding ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
author-avatar
mobiledu2502874983
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有