早前大家都知道IOS的内存管理方式是MRC的, 尽管现在我们是在ARC下开发,但是我们依然要保持着一颗好奇的人。IOS 到底是怎么进行内存管理的?
一,IOS的程序内存布局(由低地址 -> 高地址)
OC 的内存管理不管是ARC 还是MRC 采用的是引用计数器管理。
在runtime的源码NSobject.mm 中找到函数
id objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
点击进去可以看到
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
//在次点击以下函数
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
从源码中可以看出,先判断是不是taggerPointer。
taggerPointer是64位开始引入的,用于优化NSNumber、NSDate、NSString等小对象的存储。
在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值;使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中;当指针不够存储数据时,才会使用动态分配内存的方式来存储数据;objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销;
在64位iOS平台中,通过最高有效位是否是1 来判断是不是taggerPointer;
在继续往下查看源码之前,大家都知道,64位之前isa就是个普通的指针,存储着类/元类对象的内存地址,64位之后isa 进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息;
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;//是一个存放着对象引用计数的散列表
weak_table_t weak_table;
}
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;
}
说到内存管理,不得不提“僵尸对象+ 空指针+野指针”
1.僵尸对象:一个OC对象引用计数器为0,对象内存已经被回收但是数值(对象)还存在的对象被成为僵尸对象,该对象不能被引用也不可以访问
2.空指针:空指针是有效指针,值为nil,NULL ,0,给空指针发消息,不会报错,只是不响应而已。他是一个没有指向任何东西的指针。
3.野指针:指针指向了一个已经被销毁的对象的内存地址,向野指针发消息会报EXC_BAD_ACCESS,而导致程序崩溃
1.在控制器的viewdidload 中打印下,runloop的mainloop,你会发现主线程中注册了两个关于autolease的观察者。所以autorelease调用release是由runloop控制的,在某次runlop循环中,runloop休眠之前调用的。
2.autoreleasepool底层实现
用clang编译器吧main.m 生成.cpp 文件发现 @autoreleasepool 变成了 __AtAutoreleasePool __autoreleasepool;全局搜索查看 __AtAutoreleasePool如下:
struct __AtAutoreleasePool {
// C++的构造函数
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
// C++的析构函数;与构造函数不同的地方在于前面有个~
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
由此可知
@autoreleasepool{ //大括号开头调用 objc_autoreleasePoolPush()函数。
} //大括号结束之前调用objc_autoreleasePoolPop()函数。
在runtime的源码中搜索objc_autoreleasePoolPush与objc_autoreleasePoolPop
objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
objc_autoreleasePoolPop
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
自动释放池的底层全部依赖类 AutoreleasePoolPage,该对象是通过双向链表的形式连接在一起的。
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
id *next指向了下一个能存放autorelease对象地址的区域
循环引用:可以使用instrument 里的leaks 观察是否有循环引用问题,然后在想办法解决。一般都是吧其中的一个对象改为弱引用。另外就是主动断开链接
Core Foundation 的内存管理不受ARC控制。对于这些引用技术的修改使用CFRetain 与CFRelease。在ARC 有时需要吧Core Foundation 对象转换成OC对象,这就需要Bridge。
__bridge
: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。__bridge_retained
:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。__bridge_transfer
:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。