iOS开发之内存管理
- 一、垃圾回收机制
- 二、内存管理的概念
- 三、OC内存管理注意事项
- 四、MRC相关语法
一、垃圾回收机制
与Java语言相同Objective-c 2.0之后,也提供了垃圾回收机制。OC是支持垃圾回收机制的(Garbage collection简称GC),macOS开发中是支持的。但是在iOS移动终端设备中,并不支持垃圾回收机制。因此,iPhone并不能对内存进行自动垃圾回收处理(autorelease),并且他与GC的机制是不一样的。
ARC是在IOS5之后推出的新技术,在Xcode4.2及之后的版本中由于引入了ARC(Automatic Reference Counting)机制,程序编译时Xcode可以自动给你的代码添加内存释放代码,如果编写手动释放代码Xcode会报错。因此需要注意垃圾回收机制并不是ARC,ARC也是需要管理内存的,只不过是隐式的管理内存,编译器会在适当的地方自动插入retain,release和autorelease.
知识拓展:在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。Java中标记垃圾的算法主要有两种, 引用计数法和可达性分析算法。
二、内存管理中的几个概念:
引用计算器:既retainCount,每个OC对象内部都有1个8字节空间用来存储retainCount,表示有多少”人”正在使用;
对象刚被创建时,默认计数值就为1,当计数值为0时,系统会自动调用dealloc方法将对象销毁
引用计数器的用法:给对象发送相应的技术操作来改变计数器的值
retain消息:使计数器+1
release消息:使计数器-1
retainCount消息:得到当前当前retainCount的值
三、OC内存管理开发中需要事项
其一野指针,其二内存泄漏。
1)野指针:即指针所指的对象已经被销毁,但后续还在使用该指针,此时指针指向了一个什么都不是的东西,我们称它为野指针,那么如何防止野指针的,一般处理的方式是对象进行release操作后,在赋值对象nil值。
2)内存泄漏:在操作对象是没有遵循内存配对原则,创建了对象了,却未对对象进行销毁,此时这个未被销毁的对象就是我们所谓的内存中泄漏的对象,这种行为也就是所谓的内存泄漏,内存泄漏不会影响对象的正常运行,但会影响程序的效率。
除此之外还有1)提前释放:如果没有使用空间直接free 。2)重复释放,如果你对一个空间进行了多次free 。
四、MRC(Manual Reference Counting)相关语法
1.关闭ARC
target -》 build setting - 》搜索 gar YES to NO
2.ARC和MRC混编
[工程]—>[Build Phases]—>[Compile Sources]—>
双击不想参与ARC的文件-fno-objc-arc
3.MRC管理黄金法则
1.凡是使用alloc,retain ,new ,copy(开头),mutableCopy(开头)的方法,都必须使用release 或者 autorelease 方法来【释放】
2.谁写alloc 谁负责release 那个类alloc 那个类release
4.一个简单的例子说明MRC
这里有一个Person类
这里是打印结果
我们在看看下面这一个例子
当执行release操作时,一个对象的引用计数为0时,它就会被释放掉。
AutoReleasePool
AutorealeasePool结构:
AutorealeasePool就是由AutoreleasePoolPage构成的双向链表,AutoreleasePoolPage是双向链表的节点
每一个AutorealeasePool都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节 。
什么是autorelease?
autorelease类似于C语言中Automatic variable自动变量,程序执行时,若某自动变量超出其作用域,该自动变量将被自动废弃。
autorelease何时释放?
1.当创建了局部释放池时,会在@autoreleasepool{}的右大括号结束时释放,及时释放对象大幅度降低程序的内存占用。
2.@autoreleasepool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的(每个线程对应一个runloop),而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池。
AutoreleasePool能否嵌套使用?
可以嵌套使用,其目的是可以控制应用程序的内存峰值,使其不要太高 可以嵌套的原因是因为自动释放池是以栈为节点,通过双向链表的形式连接的,且是和线程一一对应的 自动释放池的多层嵌套其实就是不停的push哨兵对象,在pop时,会先释放里面的,在释放外面的。
AutoreleasePool的释放时机是什么时候?
App 启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视一个事件:
监听 Entry(即将进入 Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池,优先级最高,保证创 建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件:
BeforeWaiting(准备进入休眠) 时调用 _objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
Exit(即 将退出 Loop) 时调用_objc_autoreleasePoolPop()来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
RunLoop 和 AutoreleasePool的关系
主程序的RunLoop在每次事件循环之前之前,会自动创建一个 autoreleasePool 并且会在事件循环结束时,执行drain操作,释放其中的对象
// MRC
NSAutoreleasePool *pool = [NSAutoreleasePool alloc] init];
id obj = [NSObject alloc] init];
[obj autorelease];
[pool drain];
// ARC
@autoreleasepool {
id obj = [NSObject alloc] init];
}
将main.m文件通过$ clang -rewrite-objc main.m重新编译生成发现aotuoreleasepool 被转换为为一个 __AtAutoreleasePool 结构体:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
这个结构体会在初始化时调用 objc_autoreleasePoolPush() 方法,会在析构时调用 objc_autoreleasePoolPop 方法。
这表明,main函数实际工作的时候,是这样的:
int main(int argc, const char * argv[]) {
{
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
// do things you want
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;
}
所以autoreleasepool的实现主要靠objc_autoreleasePoolPush()和objc_autoreleasePoolPop() 来实现
那么 objc_autoreleasePoolPush 和 objc_autoreleasePoolPop又是什么呢?
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
上面的方法看上去是对 AutoreleasePoolPage 对应静态方法 push 和 pop 的封装。
AutoreleasePoolPage的定义如下:
class AutoreleasePoolPage
{
//magic用来校验AutoreleasePoolPage的结构是否完整
magic_t const magic; // 16字节
//指向最新添加的autoreleased对象的下一个位置,初始化时指向begin();
id *next; // 8字节
//thread指向当前线程
pthread_t const thread; // 8字节
//parent指向父节点,第一个节点的parent指向nil;
AutoreleasePoolPage * const parent; // 8字节
//child 指向子节点,第一个节点的child指向nil;
AutoreleasePoolPage *child; // 8字节
//depth 代表深度,从0开始往后递增1;
uint32_t const depth; // 4字节
//hiwat 代表high water mark;
uint32_t hiwat; // 4字节
...
}
如果我们的一个 AutoreleasePoolPage 被初始化在内存的 0x100816000 ~ 0x100817000 中,它在内存中的结构如下:
其中有 56 bit 用于存储 AutoreleasePoolPage 的成员变量,剩下的 0x100816038 ~ 0x100817000 都是用来存储加入到自动释放池中的对象。
begin() 和 end() 这两个类的实例方法帮助我们快速获取 0x100816038 ~ 0x100817000 这一范围的边界地址。
next 指向了下一个为空的内存地址,如果 next 指向的地址加入一个 object,它就会如下图所示移动到下一个为空的内存地址中。
POOL_SENTINEL(哨兵对象)
在每个自动释放池初始化调用 objc_autoreleasePoolPush 的时候,都会把一个 POOL_SENTINEL push 到自动释放池的栈顶,并且返回这个 POOL_SENTINEL 哨兵对象。
注:下篇文章博主会详细讲解各种属性修饰符,请持续关注;