iOS内存管理
2016-12-20 14:40
381 查看
最近有时间把《Objective-C高级编程》这本书看了一下,总觉得不做点总结都对不起作者。这篇文章就先讲一下iOS中的内存管理。
ARC是(Automatic Reference Counting)自动引用计数,交由编译器来进行内存管理。
MRR (manual retain-release)手动内存管理,这是基于reference counting实现的,由NSObject与runtime environment共同工作实现。
1.2 在讲iOS的内存管理之前,先来了解几个概念:
在Objective-C中有两种对象类型,一类是基本数据类型,如int、double、struct等基本数据类型,另一类是引用类型,如继承自NSObject类的对象。
值类型会被放到内存区的栈(stack)中,而栈的特性是:由编译器自动分配释放,存放函数的参数值,局部变量的值等。所以不需要我们手动释放其内存。
引用类型会被放到内存区的堆(heap)中,堆的特性是:一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收。所以需要我们手动去管理其内存。
1.3 Objective-C的内存管理,也就是引用计数(Reference Count)。
当我们新创建一个对象时,这个对象的引用计数为1,当有一个新的指针指向这个对象时,其引用计数器加1,当某个指针不在指向这个对象时,其引用计数器减1,当对象的引用计数为0时,这个时候我们就可以将对象销毁,回收内存。MRC模式下需要我们手动管理(retain、release)对象的引用计数。但是在iOS5之后推出了ARC,使得我们在ARC模式下不需要手动管理对象的引用计数,把内存管理交由编译器来管理。
内存管理的思考方式:
自己生成的对象,自己所持有。
非自己生成的对象,自己也能持有。
不在需要自己持有的对象时释放。
非自己持有的对象无法释放。
这些有关Objective-C内存管理的方法,实际上不包含在该语言中,而是包含在Cocoa框架中用于OSX、iOS应用开发。Cocoa框架中Foundation框架类库的NSObject类担负内存管理的职责。
1.4 iOS中是如何管理引用计数的呢?
先说结论:在iOS中是使用散列表(引用计数表)来管理引用计数,key为内存块地址的散列值。为了验证这一点,接下来要看一大波代码。
在讲
1.4.1 alloc
我们先来看一下
可以看到这个函数也是简单的调用了
对以上代码进行分析后发现关键代码就在
该函数又调用了
这段代码主要就是计算出这个
1.4.2 init
上面完成了一个对象的初始化操作,但是怎么没有发现
下面看看
1.4.3 retainCount、reatin、release
retainCount
通过查看源码发现苹果使用了
SideTable散列表
retain
调用
release
调用
1.4.4 dealloc
上面是一个对象销毁的过程,
ARC下面还是使用引用计数的方式来管理内存,ARC(自动引用计数)只是自动地帮我们处理“引用计数”的部分。
在Xcode上编译可以设置ARC有效(-fobjc-arc)或者无效(-fno-objc-arc),在一个应用程序中可以使用ARC和MRC混编。
2.1 ARC所有权修饰符
ARC下,对象类型必须附加所有权修饰符。所有权修饰符一共有4中:
__strong修饰符
__weak修饰符
__unsafe_unretained修饰符
__autoreleasing修饰符
__strong修饰符
__strong修饰符是对象类型的默认得所有权修饰符。表示对对象的“强引用”,当一个对象没有任何强引用时,其内存就会被释放。
__weak修饰符
使用__strong会引起有些问题,如“循环引用”,这个时候就得使用__weak修饰符了。__weak修饰符与__strong修饰符相反,提供弱引用,弱引用不能持有对象实例。__weak修饰符还有另外一个特点,在持有某对象的弱引用时,若该对象被废弃,则弱引用自动失效并且处于nil状态。
__unsafe_unretained修饰符
__unsafe_unretained修饰符不是安全的所有权修饰符,其绝大部分功能和__weak修饰符是一样的,都是提供一种对对象弱引用的状态。不同点是在持有某对象的弱引用时,若该对象被废弃,不会将对象置为nil,造成垂悬指针。
__autoreleasing修饰符
__autoreleasing自动释放修饰符
1.使用__autoreleasing修饰符的指针所指向的内存空间会在自动释放池结束的时候释放掉。
2.__autoreleasing结合自动释放池会延迟对象的释放。
以上只是简单的讲了一下几个修饰符的作用,想要了解更多详细内容可以在苹果官方文档查看。
1. 内存管理/引用计数
1.1 Objective-C 提供了两种内存管理方式ARC是(Automatic Reference Counting)自动引用计数,交由编译器来进行内存管理。
MRR (manual retain-release)手动内存管理,这是基于reference counting实现的,由NSObject与runtime environment共同工作实现。
1.2 在讲iOS的内存管理之前,先来了解几个概念:
在Objective-C中有两种对象类型,一类是基本数据类型,如int、double、struct等基本数据类型,另一类是引用类型,如继承自NSObject类的对象。
值类型会被放到内存区的栈(stack)中,而栈的特性是:由编译器自动分配释放,存放函数的参数值,局部变量的值等。所以不需要我们手动释放其内存。
引用类型会被放到内存区的堆(heap)中,堆的特性是:一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收。所以需要我们手动去管理其内存。
1.3 Objective-C的内存管理,也就是引用计数(Reference Count)。
当我们新创建一个对象时,这个对象的引用计数为1,当有一个新的指针指向这个对象时,其引用计数器加1,当某个指针不在指向这个对象时,其引用计数器减1,当对象的引用计数为0时,这个时候我们就可以将对象销毁,回收内存。MRC模式下需要我们手动管理(retain、release)对象的引用计数。但是在iOS5之后推出了ARC,使得我们在ARC模式下不需要手动管理对象的引用计数,把内存管理交由编译器来管理。
内存管理的思考方式:
自己生成的对象,自己所持有。
非自己生成的对象,自己也能持有。
不在需要自己持有的对象时释放。
非自己持有的对象无法释放。
这些有关Objective-C内存管理的方法,实际上不包含在该语言中,而是包含在Cocoa框架中用于OSX、iOS应用开发。Cocoa框架中Foundation框架类库的NSObject类担负内存管理的职责。
1.4 iOS中是如何管理引用计数的呢?
先说结论:在iOS中是使用散列表(引用计数表)来管理引用计数,key为内存块地址的散列值。为了验证这一点,接下来要看一大波代码。
在讲
iOS中是如何管理引用计数之前先看一下iOS中是如何初始化一个对象的。我们可以通过查看NSObject.mm找到相关的代码。
1.4.1 alloc
我们先来看一下
NSObject类的
alloc类方法,查看源码可以发现
alloc类方法很简单只是调用了一个简单的函数,代码如下
+ (id)alloc { return _objc_rootAlloc(self); }
alloc类方法只是简单的调用了
_objc_rootAlloc()函数,这个函数需要一个类型为
Class的参数。
Class又是个什么鬼呢? 苹果官方给出的解释:
An opaque type that represents an Objective-C class.(一个不透明的类型,代表着一个
Objective-C的对象。)
typedef struct objc_class *Class,就是一个指向
objc_class结构体的指针。在这里就不对
Class具体是什么做延伸了,想要了解更多可以查阅苹果的
Object.mm文件。接下来看一下
_objc_rootAlloc()函数是怎么实现的。
id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); }
可以看到这个函数也是简单的调用了
callAlloc函数,这个函数需要三个参数,接下来看一下这个函数的具体实现
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { //如果checkNil=true,并且cls为空 直接retutn nil; if (slowpath(checkNil && !cls)) return nil; //如果是Objc2.0 #if __OBJC2__ if (fastpath(!cls->ISA()->hasCustomAWZ())) { // No alloc/allocWithZone implementation. Go straight to the allocator. // fixme store hasCustomAWZ in the non-meta class and // add it to canAllocFast's summary if (fastpath(cls->canAllocFast())) { // No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(!obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (slowpath(!obj)) return callBadAllocHandler(cls); return obj; } } #endif // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; }
对以上代码进行分析后发现关键代码就在
id obj = class_createInstance(cls, 0)这一句。这个函数需要两个参数,第一个参数为
Class类型,第二个参数是这个对象在初始化时额外的字节。通过查看
objc-runtime-new.mm文件可以找到此函数的实现。
id class_createInstance(Class cls, size_t extraBytes) { return _class_createInstanceFromZone(cls, extraBytes, nil); }
该函数又调用了
_class_createInstanceFromZone函数,接着往下找
static __attribute__((always_inline)) id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { if (!cls) return nil; assert(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (!zone && fast) { obj = (id)calloc(1, size); if (!obj) return nil; obj->initInstanceIsa(cls, hasCxxDtor); } else { if (zone) { obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (!obj) return nil; // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } return obj; }
这段代码主要就是计算出这个
cls所需要的字节大小(
size_t size = cls->instanceSize(extraBytes)),然后调用
calloc函数分配内存空间。
calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。
1.4.2 init
NSObject的
init方法只是调用了
_objc_rootInit并返回了当前对象。
- (id)init { return _objc_rootInit(self); } id _objc_rootInit(id obj) { // In practice, it will be hard to rely on this function. // Many classes do not properly chain -init calls. return obj; }
上面完成了一个对象的初始化操作,但是怎么没有发现
retainCount在哪里有初始化?
下面看看
reatin,
release,
retainCount就明白了
1.4.3 retainCount、reatin、release
retainCount
- (NSUInteger)retainCount { return ((id)self)->rootRetainCount(); } inline uintptr_t objc_object::rootRetainCount() { if (isTaggedPointer()) return (uintptr_t)this; return sidetable_retainCount(); } uintptr_t objc_object::sidetable_retainCount() { SideTable& table = SideTables()[this]; size_t refcnt_result = 1; table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it != table.refcnts.end()) { // this is valid for SIDE_TABLE_RC_PINNED too refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT; } table.unlock(); return refcnt_result; }
通过查看源码发现苹果使用了
SideTable散列表来管理一个对象的引用计数。散列表的
key就是这个对象的指针。首先把
refcnt_result初始化为1,然后在1的基础上加
it->second >> SIDE_TABLE_RC_SHIFT的偏移值,得到一个对象最终的
retainCount。
SideTable散列表
struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; SideTable() { memset(&weak_table, 0, sizeof(weak_table)); } ~SideTable() { _objc_fatal("Do not delete SideTable."); } void lock() { slock.lock(); } void unlock() { slock.unlock(); } // Address-ordered lock discipline for a pair of side tables. template<bool HaveOld, bool HaveNew> static void lockTwo(SideTable *lock1, SideTable *lock2); template<bool HaveOld, bool HaveNew> static void unlockTwo(SideTable *lock1, SideTable *lock2); };
retain
- (id)retain { return ((id)self)->rootRetain(); } inline id objc_object::rootRetain() { if (isTaggedPointer()) return (id)this; return sidetable_retain(); } id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this; }
调用
retain就是先取得
SideTable,然后
refcntStorage += SIDE_TABLE_RC_ONE,最终在使用对象的指针作为key保存这个
refcntStorage。
release
- (oneway void)release { ((id)self)->rootRelease(); } inline bool objc_object::rootRelease() { if (isTaggedPointer()) return false; return sidetable_release(true); } uintptr_t objc_object::sidetable_release(bool performDealloc) { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; bool do_dealloc = false; table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) { do_dealloc = true; table.refcnts[this] = SIDE_TABLE_DEALLOCATING; } else if (it->second < SIDE_TABLE_DEALLOCATING) { // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it. do_dealloc = true; it->second |= SIDE_TABLE_DEALLOCATING; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second -= SIDE_TABLE_RC_ONE; } table.unlock(); if (do_dealloc && performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return do_dealloc; }
调用
retain就是先取得
SideTable,然后在判断是否需要调用
dealloc,如果不需要就把引用计数减去
SIDE_TABLE_RC_ONE。
1.4.4 dealloc
- (void)dealloc { _objc_rootDealloc(self); } _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); } inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; object_dispose((id)this); } id object_dispose(id obj) { if (!obj) return nil; objc_destructInstance(obj); free(obj); return nil; } void *objc_destructInstance(id obj) { if (obj) { Class isa = obj->getIsa(); if (isa->hasCxxDtor()) { object_cxxDestruct(obj); } if (isa->instancesHaveAssociatedObjects()) { _object_remove_assocations(obj); } objc_clear_deallocating(obj); } return obj; }
上面是一个对象销毁的过程,
dealloc最终调用的是
objc_destructInstance函数,这个函数只是销毁一个实例对象,但是并没有释放其所占的内存,需要另外调用
free()函数来释放内存。
objc_destructInstance函数的作用是调用
C++ 的destructors,然后移除关联对象的引用
_object_remove_assocations(常用于category中添加带变量的属性,这也是为什么没必要remove一遍的原因,然后调用
objc_clear_deallocating()函数清空引用计数表并清除弱引用表,将所有weak引用指nil。我这里只是简单的提了一下具体的实现细节可以参考sunnyxx大神的博客。
2.ARC规则
上面简单介绍了一下Objective-C的内存管理,下面开始讲述ARC下的内存管理。ARC下面还是使用引用计数的方式来管理内存,ARC(自动引用计数)只是自动地帮我们处理“引用计数”的部分。
在Xcode上编译可以设置ARC有效(-fobjc-arc)或者无效(-fno-objc-arc),在一个应用程序中可以使用ARC和MRC混编。
2.1 ARC所有权修饰符
ARC下,对象类型必须附加所有权修饰符。所有权修饰符一共有4中:
__strong修饰符
__weak修饰符
__unsafe_unretained修饰符
__autoreleasing修饰符
__strong修饰符
__strong修饰符是对象类型的默认得所有权修饰符。表示对对象的“强引用”,当一个对象没有任何强引用时,其内存就会被释放。
__weak修饰符
使用__strong会引起有些问题,如“循环引用”,这个时候就得使用__weak修饰符了。__weak修饰符与__strong修饰符相反,提供弱引用,弱引用不能持有对象实例。__weak修饰符还有另外一个特点,在持有某对象的弱引用时,若该对象被废弃,则弱引用自动失效并且处于nil状态。
id __weak weakObj = nil; { id __strong obj1 = [[NSObject alloc] init]; weakObj = obj1; NSLog(@"A:%@",weakObj); } NSLog(@"B:%@",weakObj); //执行结果 A:<NSObject:0x453a122> B:(null)
__unsafe_unretained修饰符
__unsafe_unretained修饰符不是安全的所有权修饰符,其绝大部分功能和__weak修饰符是一样的,都是提供一种对对象弱引用的状态。不同点是在持有某对象的弱引用时,若该对象被废弃,不会将对象置为nil,造成垂悬指针。
__autoreleasing修饰符
__autoreleasing自动释放修饰符
1.使用__autoreleasing修饰符的指针所指向的内存空间会在自动释放池结束的时候释放掉。
#import "TestObj.h" @implementation TestObj - (void)dealloc { NSLog(@"TestObj的dealloc方法调用了"); } @end - (void)test__autoreleasing { //定义自动释放对象指针 __autoreleasing TestObj *obj; @autoreleasepool { obj = [[TestObj alloc] init]; obj.name = @"test"; }//obj对象的dealloc //野指针 程序crash NSLog(@"obj_name = %@",obj.name); }
2.__autoreleasing结合自动释放池会延迟对象的释放。
- (void)testDelay__autoreleasing { @autoreleasepool { //定义自动释放对象指针 __autoreleasing TestObj *obj; { obj = [[TestObj alloc] init]; obj.name = @"test"; obj = nil; NSLog(@"把自动释放对象在自动释放池置空,其所指向的内存不会被释放。"); } NSLog(@"出上面的大括号,只要不出自动释放池是不释放所指内存空间的"); } } 代码执行结果: 2016-11-30 11:02:30.137 ARC[2515:2574305] 把自动释放对象在自动释放池置空,其所指向的内存不会被释放。 2016-11-30 11:02:30.137 ARC[2515:2574305] 出上面的大括号,只要不出自动释放池是不释放所指内存空间的 2016-11-30 11:02:30.137 ARC[2515:2574305] TestObj的dealloc方法调用了
以上只是简单的讲了一下几个修饰符的作用,想要了解更多详细内容可以在苹果官方文档查看。
相关文章推荐
- iOS 的多核编程和内存管理
- ios 开发内存管理原则
- iOS 5编程 内存管理 ARC技术概述
- IOS 5编程 内存管理 ARC技术概述
- iOS内存管理
- IOS 5编程 内存管理 ARC技术概述 .
- iOS-的多核编程和内存管理
- IOS 5编程 内存管理 ARC技术概述 常见问题解答
- IOS内存管理小结
- iOS下Objective-C内存管理
- iOS 5编程 内存管理 ARC技术概述
- iOS内存管理规则
- IOS 5编程 内存管理 ARC技术概述
- iOS的多核编程和内存管理
- [iphone开发多线程之]iOS的多核编程和内存管理
- IOS 5编程 内存管理 ARC技术概述
- iOS 5编程 内存管理 ARC技术概述
- iOS内存管理
- iOS 指南系列: 内存管理