一.可执行程序在内存中的结构
一个二进制程序在内存中大体分为两个部分:可读写部分(Readwrite)和只读部分(Readonly)。只读部分用来存储程序代码(.text
)和程序常量(.rodata
),可读写部分由四个部分组成:.data
,.bss
,heap
,stack
。
.data
: 已经初始化的全局变量和静态变量。.bss
: 即 Block Started by Symbol, 未初始化的全局变量和静态变量。heap
: 堆,使用malloc
,realloc
, 和free
函数控制的变量,堆在所有的线程,共享库,和动态加载的模块中被共享使用,是一种不连续的数据结构(链式),由程序员创建和管理的内存区块。stack
: 栈,函数调用时使用栈来保存函数现场,自动变量(即生命周期限制在某个 scope 的变量)也存放在栈中,是一种连续的数据结构,由系统管理,并且有硬件底层寄存器支持。
二.常见的内存管理技术--引用计数和垃圾回收
引用计数是比较容易实现的一种方案:记录对象被引用的次数,而不具体记录是谁引用了它。当计数器的值为0的时候就回收,当计数器大于0的时候就保持,在Cocoa平台上实现的ARC(自动引用计数)就是编译器帮助开发者注入相关的管理代码并且自动跟踪引用计数。
优势
- 实时地精确地释放那些变成没用的对象。
- 没有后台处理,在低功耗的系统上更有效率,如手机设备。
劣势
- 不能处理循环引用。
垃圾回收技术是基于标记清除(或标记整理)或其变体的算法来实现的一种技术,由收集器框架记录下对象和对象之间的联系(这些联系信息存放的位置不重要,可以在对象的内存布局空间上,也可以在独立的地方,关键在于这些信息可以被收集器访问)。确定一个世界的根,定期的从这个根开始遍历这个世界,把有关联的对象标记起来,最后回收没有被标记的对象。
优势
- 垃圾收集可以清理所有不被使用的对象图谱,包括循环引用。
- 垃圾回收运行在后台,可以作为定期应用程序流程运行。
劣势
- 对象释放的时间范围是难以确定的。
- 当垃圾回收运行时,可能导致系统资源紧张,需暂停一些优先级较低的线程。
三.iOS中基于引用计数的内存管理方式--MRC和ARC
Autorelease Pool
Autorelase Pool 提供了一种可以允许你向一个对象延迟发送release
消息的机制,也就是一个对象存放在池子中直到这个池子结束后统一将对象进行释放。它将会把使用autorelease
标记的对象注册到Autorelease Pool中来管理,在没有手加Autorelease Pool的情况下,autorelease
的对象是在当前的runloop
一个 event迭代结束时释放的(也就是说系统会在每个runloop event中自动生成一个Autorelease Pool)。
有两种使用Autorelease Pool的方法:NSAutoreleasePool
和@autoreleasepool
。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init;
// xxxxx
NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease];
[pool drain];
@autoreleasepool {
//code
NSString* str = [[NSString alloc] initWithString:@"hello"];
}
@autoreleasepool的常见用法:
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
在循环代码里面加入@autoreleasepool
来使注册池中对象由循环中的释放池管理而不是由系统的释放池管理,达到提前释放对象内存空间的目的。
上述代码中,在没有加入@autoreleasepool
的循环里面中会暂时生成urls.length个fileContents对象,直到外层系统创建的 pool 会在整个 runloop circle 结束之后这段内存空间才会自动释放掉。而加入@autoreleasepool
的话则每生成一个fileContents对象都会在当此循环结束后释放,这就解决了一个内存峰值的问题。
@autoreleasepool的高级用法:
– (id)findMatchingObject:(id)anObject {
id match;
while (match == nil) {
@autoreleasepool {
/* Do a search that creates a lot of temporary objects. */
match = [self expensiveSearchForObject:anObject];
if (match != nil) {
[match retain]; /* Keep match around. */
}
}
}
return [match autorelease]; /* Let match go and return it. */
}
在block内对match对象发送retain消息和在block外对match发送autorelease消息能延长match对象的生命周期并且允许match对象在block外部接收消息或者作为方法的返回值返回。我们不需要再关心match什么时候释放, 因为它已经交给了更上一层(系统自动生成的AutoreleasePool)去管理。
Autorelease Pool 与函数返回值
如果一个函数的返回值是指向一个对象的指针,那么这个对象肯定不能在函数返回之前进行 release,这样调用者在调用这个函数时得到的就是野指针了,在函数返回之后也不能立刻就 release,因为我们不知道调用者是不是 retain 了这个对象,如果我们直接 release 了,可能导致后面在使用这个对象时它已经成为 nil 了。
为了解决这个纠结的问题, Objective-C 中对对象指针的返回值进行了区分,一种叫做retained return value,另一种叫做unretained return value。前者表示调用者拥有这个返回值,后者表示调用者不拥有这个返回值,按照“谁拥有谁释放”的原则,对于前者调用者是要负责释放的,对于后者就不需要了。
按照苹果的命名 规则,以alloc
,copy
,init
,mutableCopy
和new
这些方法打头的方法,返回的都是 retained return value,例如[[NSString alloc] initWithFormat:]
,而其他的则是 unretained return value,例如[NSString stringWithFormat:]
。
weak和Autorelease Pool
weak表示一种属性的持有而非拥有状态,当给一个 weak 赋以一个自己生成的对象,对象会立马被释放(unretained return value),但它又可以持有非自己生成的对象,这主要归功于在ARC环境下使用weak的对象会通过objc_loadWeak
向系统生成的Autorelease Pool注册来延长对象的声明周期。
NSNumber __weak *number = [NSNumber numberWithInt:100];
NSLog(@"number = %@", number);
// 会正常打印
MRC:手动引用计数
使用alloc
/new
/copy
/mutableCopy
,retain
,release
和 AutoreleasePool来手动管理对象内存。遵循四个基本原则:
- 自己生成的对象,自己持有。
- 非自己生成的对象,自己也能持有。
- 不在需要自己持有对象的时释放。
- 非自己持有的对象无需释放。
总结起来就是谁拥有谁释放,只持有不释放。
对象操作的四个类别所对应的计数变化:
对象操作 | OC中对应的方法 | 对应的 retainCount 变化 |
---|---|---|
生成并持有对象 | alloc/new/copy/mutableCopy等 | +1 |
持有对象 | retain | +1 |
释放对象 | release | -1 |
废弃对象 | dealloc | - |
ARC:自动引用计数
ARC 是苹果引入的一种自动内存管理机制,会根据引用计数自动监视对象的生存周期,实现方式是在编译时期自动在已有代码中插入合适的内存管理代码以及在 Runtime 做一些优化。也就是在编译阶段插入ARC的代码帮助开发者进行内存管理,不需要开发者再遵循MRC来手动进行对象的生命周期管理。
在ARC环境下,变量可以通过四种标识符来显式声明一个对象的ARC管理方式:__strong
,__weak
,__unsafe_unretained
,__autoreleasing
。
__strong
是默认使用的标识符。只有还有一个强指针指向某个对象,这个对象就会一直存活。__weak
声明这个引用不会保持被引用对象的存活,如果对象没有强引用了,弱引用会被置为 nil。__unsafe_unretained
声明这个引用不会保持被引用对象的存活,如果对象没有强引用了,它不会被置为 nil。如果它引用的对象被回收掉了,该指针就变成了野指针。__autoreleasing
用于标示使用引用传值的参数(id *),在函数返回时会被自动释放掉。
使用方法:
Number* __strong num = [[Number alloc] init];
同样的,属性@property
也可以通过四种标识符来显式声明一个对象的ARC管理方式:assign,copy,strong,weak。
@property (assign/retain/strong/weak/unsafe_unretained/copy) Number* num