内存管理方式:
MRC:手动管理内存,需要开发人员管理内存,手动调用Release,以控制对象内存的释放。
ARC:自动内存管理,系统控制内存的释放时机,主要由AutoReleasePool管理,释放时机有所延后,与RunLoop相关。
引用计数:
iOS对象的内存释放主要是引用计数决定,当一个对象初始化开始,每有一个指针指向该内存地址,引用计数就会增加,调用Retain增加引用计数,调用Release则减少引用计数,由哈希表管理每个对象的引用计数。
sideTable:
由sideTable管理,内部包含引用计数表和weak指针表。
struct SideTable {
spinlock_t slock;
# mark 引用计数表 key:对象地址 value:引用计数
RefcountMap refcnts;
# mark 存放weak指针的表 key:对象地址 value:对应weak指针的数组
weak_table_t weak_table;
};
PS:
在底层代码里会看到很多isTaggedPointer和nonpointer的判断,这两者都是用于64位上的优化机制,大概了解一下即可。
isTaggedPointer:对NSNumber,NSString之类常量的数据类型进行区别处理,将值直接保存在指针中,避免申请堆区的内存空间,所以也不需要维护引用计数。
nonpointer:isa在64位上不再是一个单纯的指针,还会存储一些其他信息,nonpointer用来区别,两种情况区别处理。
获取引用计数的方法:
objc_object::rootRetainCount()
{
#mark
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
#mark两种情况处理类似,只是nonpointer的话,部分数据可以直接在isa上取到
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
#mark 单纯的指针类型
return sidetable_retainCount();
}
objc_object::sidetable_retainCount()
{
#mark 通过对象的地址获取对应的表
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
release方法:
每次release调用都会减少引用计数,当引用计数小于临界值时的时候,会调用析构函数dealloc。
- (oneway void)release {
((id)self)->rootRelease();
}
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
#mark rootRelease内部相关逻辑较多,包含指针类型判断和数据异常处理,节省篇幅,这里只有部分……
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
#mark 加锁保护,真正处理的函数为sidetable_release。
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
……
}
sidetable_release方法的处理:
1、根据对象获取对应的sideTable
2、判断引用计数是否小于临界值
2、引用计数小于临界值的,标记为需要释放,否则进行-1。
4、如果标记为需要释放,则调用dealloc方法。
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
#mark 获取到SideTable
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
#mark 表里找不到
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
#mark 引用计数小于临界值了,需要释放
} 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)) {
#mark 引用计数-1
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
#mark 需要释放
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);//发送消息调用dealloc销毁对象。
}
return do_dealloc;
}
dealloc方法:
对象自身的释放和对象相关变量和关联数据的处理。
sideTable的处理,清除引用计数记录,释放weak指针。
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
#nark 没有其他关联的数据或者表需要处理,直接释放
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
#mark object_dispose方法:
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
#mark 释放对象的实例变量
if (cxx) object_cxxDestruct(obj);
#mark 移除动态关联的对象
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
接下来是clearDeallocating方法:
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
sidetable_clearDeallocating和clearDeallocating_slow内部处理相似,通过weak表找到指向该对象的weak指针并释放,有引用计数记录的,则清除该记录。
objc_object::sidetable_clearDeallocating()
{
SideTable *table = SideTable::tableForPointer(this);
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
spinlock_lock(&table->slock);
RefcountMap::iterator it = table->refcnts.find(this);
if (it != table->refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
#mark 如果有弱引用,还需要清理weak指针表
weak_clear_no_lock(&table->weak_table, (id)this);
}
#mark 清理引用计数记录
table->refcnts.erase(it);
}
spinlock_unlock(&table->slock);
}
释放weak指针
获取weak的hash表,根据对象地址获取所有指向该对象的weak指针数组,将数组内元素置为nil。
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
#mark 遍历数组,将指向对象的weak指针置为nil。
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak指针:
初始化weak指针的时候,会调用objc_initWeak函数。
当weak指针指向一个对象时,会调用objc_storeWeak函数。
当weak指针不再指向任何对象时,销毁会调用objc_destroyWeak函数。
//objc_initWeak
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak
(location, (objc_object*)newObj);
}
//objc_storeWeak:
objc_storeWeak(id *location, id newObj)
{
return storeWeak
(location, (objc_object *)newObj);
}
//objc_destroyWeak
objc_destroyWeak(id *location)
{
(void)storeWeak
(location, nil);
}
可以看出,三个方法的最终走向都是storeWeak方法,storeWeak的内部实现:
storeWeak内部逻辑主要是由 haveOld 和haveNew 判断,
haveOld 代表weak指针是否已经指向其他老对象了,haveNew 代表需要指向新的对象了 。所以正常情况下是:
objc_initWeak: haveOld = NO,haveNew = YES。
objc_storeWeak: haveOld = YES,haveNew = YES。
objc_destroyWeak:haveOld = YES,haveNew = NO。
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
#mark有老对象则有oldTable
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
#mark有新对象则有newTable
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo(oldTable, newTable);
#mark 异常处理
if (haveOld && *location != oldObj) {
SideTable::unlockTwo(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
#mark 异常处理
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
#mark 从老对象的weak表里移除记录
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
#mark 绑定新对象的weak表
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo(oldTable, newTable);
return (id)newObj;
}
自动释放池autoreleasepool:
经常看到这样的代码:
for (int i = 0 ; i< 555500; i++) {
@autoreleasepool {
TestARCObject *object = [[TestARCObject alloc]init];
[object doSomeThing ];
}
}
在创建大量的临时变量时,需要手动添加自动释放池,如果不添加自动释放池的时候,就会导致内存突然暴涨。因为ARC跟MRC不同,MRC是程序员可以手动释放每一个指针,而ARC下指针的释放是由autoreleasepool管理,然后批量进行release处理的。
如果我们自己不添加autoreleasepool,那么将会由系统自动创建的autoreleasepool管理,就是这个 :
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool { #mark 包裹在最外面的autoreleasepool
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
那么这个主线程的自动释放池什么时候对内部的指针进行release呢,虽然官方只是轻描淡写的一句“将会在合适的时候释放”,但是我们都能猜出其实是和RunLoop相关。
通过控制台打印出主线程RunLoop相关信息:
搜索autoRelease相关数据,可以找到_wrapRunLoopWithAutoreleasePoolHandler方法:
{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
可以看到activities有两个值,也就是说两种情况下会触发AutoreleasePool相关的回调方法,再查看RunLoop的状态枚举:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), 1
kCFRunLoopBeforeTimers = (1UL << 1), 2
kCFRunLoopBeforeSources = (1UL << 2), 4
kCFRunLoopBeforeWaiting = (1UL << 5), 32
kCFRunLoopAfterWaiting = (1UL << 6), 64
kCFRunLoopExit = (1UL << 7), 128 ,
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
activities = 0x1(1)对应kCFRunLoopEntry,也就是进入runLoop状态,执行autoreleasepool相关初始化操作,
activities = 0xa0(160 )对应kCFRunLoopExit | kCFRunLoopBeforeWaiting(32+128),也就是退出Runloop和即将进入休眠的状态,执行autoreleasepool相关释放操作。
使用clang命令行转换后可以看到:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
可以看到内部主要是构造函数push和析构函数pop方法,内部是由AutoreleasePoolPage管理。
objc_autoreleasePoolPush(void)
{
if (UseGC) return NULL;
return AutoreleasePoolPage::push();
}
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
if (!ctxt) return;
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePool自身没有具体的结构体数据,真正管理对象指针的是AutoreleasePoolPage,整体的数据结构是以AutoreleasePoolPage为单位的双向链表,AutoreleasePoolPage内部结构不细说了,除了保存一部分链表的信息数据外,其他剩余空间都是用来保存AutoreleasePool管理的对象指针。
static inline void *push()
{
if (!hotPage()) {
# hotPage代表当前的Page,如果没有会New一个,设为当前Page
setHotPage(new AutoreleasePoolPage(NULL));
}
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token) {
#mark 通过token获取对应的page
page = pageForPointer(token);
stop = (id *)token;
assert(*stop == POOL_SENTINEL);
} else {
// Token 0 is top-level pool
page = coldPage();
assert(page);
stop = page->begin();
}
#mark 释放Page内部指针直到碰到临界值
page->releaseUntil(stop);
…………(太多了,省略一下)
这里的push是AutoReleasePool初始化第一次调用的时候,所以传入的autoreleaseFast的参数是POOL_SENTINEL,POOL_SENTINEL是边界对象,用于分隔的作用。
Pop内部会获取相应的Page,然后调用releaseUntil方法,就开始批量释放内部指针了。