1、內(nèi)存布局
stack
:方法調(diào)用heap
:通過alloc等分配對象bss
:未初始化的全局變量等。data
:已初始化的全局變量等。text
:程序代碼。
內(nèi)存條中主要分為幾大類:棧區(qū)(stack)、堆區(qū)(heap)、常量區(qū)、代碼區(qū)(.text)、保留區(qū)。常量區(qū)分為未初始化區(qū)域(.bss)和已初始化區(qū)域(.data),棧區(qū)stack存儲順序是由高地址存向低地址,而堆區(qū)是由低地址向高地址存儲。內(nèi)存條中地址由低到高的區(qū)域分別為:保留區(qū),代碼區(qū),已初始化區(qū)(.data),未初始化區(qū)(.bss),堆區(qū)(heap),棧區(qū)(stack),內(nèi)核區(qū)。而程序員操作的主要是棧區(qū)與堆區(qū)還有常量區(qū)。
申請后的系統(tǒng)是如何響應(yīng)的?
棧:
- 只要棧的剩余空間大于所申請空間,系統(tǒng)將為程序提供內(nèi)存,否則將報異常提示棧溢出。
堆:
- 首先應(yīng)該知道操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表。當(dāng)系統(tǒng)收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結(jié)點,然后將該結(jié)點從空閑結(jié)點鏈表中刪除,并將該結(jié)點的空間分配給程序。由于找到的堆結(jié)點的大小不一定正好等于申請的大小,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中。
申請大小的限制是怎樣的?
- 棧:棧是向低地址擴展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數(shù) ) ,如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。
- 堆:堆是向高地址擴展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。
申請效率的比較?
- 棧:由系統(tǒng)自動分配,速度較快。但程序員是無法控制的。
- 堆:是由new分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過用起來最方便.
2、內(nèi)存管理方案
-
1、
TaggedPointer
:小對象,比如NSNumber其主要的原理就是在對象的指針中加入特定需要記錄的信息,以及對象所對應(yīng)的值,在64位的系統(tǒng)中,一個指針?biāo)加玫膬?nèi)存空間為8個字節(jié),已足以存下一些小型的數(shù)據(jù)量了,當(dāng)對象指針的空間中存滿后,再對指針?biāo)赶虻膬?nèi)存區(qū)域進(jìn)行存儲,這就是taggedPointer。距離NSNumber,最低4位用于標(biāo)記是什么類型的數(shù)據(jù)(long為3,float則為4,Int為2,double為5),而最高4位的“b”表示是NSNumber類型;其余56位則用來存儲數(shù)值本身內(nèi)容。
-
2、
NONPOINTER_ISA
:objc_objcet對象中isa指針分為指針型isa與非指針型isa(NONPOINTER_ISA),運用的便是類似這種技術(shù)。在一個64位的指針內(nèi)存中,第0位存儲的是indexed標(biāo)識符,它代表一個指針是否為NONPOINTER型,0代表不是,1代表是。第1位has_assoc,顧名思義,1代表其指向的實例變量含有關(guān)聯(lián)對象,0則為否。第2位為has_cxx_dtor,表明該對象是否包含C++相關(guān)的內(nèi)容或者該對象是否使用ARC來管理內(nèi)存,如果含有C++相關(guān)內(nèi)容或者使用了ARC來管理對象,這一塊都表示為YES,第3-35位shiftcls存儲的就是這個指針的地址。第42位為weakly_referenced,表明該指針對象是否有弱引用的指針指向。第43位為deallocing,表明該對象是否正在被回收。第44位為has_sidetable_rc,顧名思義,該指針是否引用了sidetable散列表。第45-63位extra_rc裝的就是這個實例變量的引用計數(shù),當(dāng)對象被引用時,其引用計數(shù)+1,但少量的引用計數(shù)是不會直接存放在sideTables表中的,對象的引用計數(shù)會先存在NONPOINTER_ISA的指針中的45-63位,當(dāng)其被存滿后,才會相應(yīng)存入sideTables散列表中。
imageimage -
3、散列表
散列表在系統(tǒng)中的提現(xiàn)是一個
sideTables
的哈希映射表,其中所有對象的引用計數(shù)(除上述存在NONPOINTER_ISA中的外)都存在這個sideTables
散列表中,而一個散列表中又包含眾多sideTable
。每個SideTable中又包含了三個元素,spinlock_t自旋鎖,RefcountMap引用計數(shù)表,weak_table_t弱引用表。所以既然SideTables是一個哈希映射的表,為什么不用SideTables直接包含自旋鎖,引用技術(shù)表和弱引用表呢?因為在眾多線程同時訪問這個SideTables表的時候,為了保證數(shù)據(jù)安全,需要給其加上自旋鎖,如果只有一張SideTable的表,那么所有數(shù)據(jù)訪問都會出一個進(jìn)一個,單線程進(jìn)行,非常影響效率,而且會帶來不好的用戶體驗,針對這種情況,將一張SideTables分為多張表的SideTable,再各自加鎖保證數(shù)據(jù)的安全,這樣就增加了并發(fā)量,提高了數(shù)據(jù)訪問的效率,所以這就是一張SideTables表下涵蓋眾多SideTable表的原因。基于此,我們進(jìn)行SideTable的表分析,那么當(dāng)一個對象的引用計數(shù)增加或減少時,需要去查找對應(yīng)的SideTable并進(jìn)行引用計數(shù)或者弱引用計數(shù)的操作時,系統(tǒng)又是怎樣實現(xiàn)的呢。
當(dāng)一個對象訪問SideTables時,首先會取到對象的地址,將地址進(jìn)行哈希運算,與SideTables的個數(shù)取余,最后得到的結(jié)果就是該對象所要訪問的SideTable所在SideTables中的位置,隨后在取到的SideTable中的RefcountMap表中再次進(jìn)行一次哈希查找,找到該對象在引用計數(shù)表中所對應(yīng)的位置,如果該位置存在對應(yīng)的引用計數(shù),則對其進(jìn)行操作,如果沒有對應(yīng)的引用計數(shù),則創(chuàng)建一個對應(yīng)的size_t對象,其實就是一個uint類型的無符號整型。
imageimage
3、數(shù)據(jù)結(jié)構(gòu)
對于Spinlock_t自旋鎖,其本質(zhì)是一種“忙等”的鎖,所謂“忙等”就是當(dāng)一條線程被加上Spinlock自旋鎖后,當(dāng)線程執(zhí)行時,會不斷的去獲取這個鎖的信息,一旦獲取到這個鎖,便進(jìn)行線程的執(zhí)行。這對于一般的高性能鎖比如信號量不同,信號量是當(dāng)線程獲取到信號量小于等0時,便自動進(jìn)行休眠,當(dāng)信號量發(fā)出時,對線程進(jìn)行喚醒操作,這樣就致使了兩種鎖的性質(zhì)不同。Spinlock自旋鎖只適用于一些小型數(shù)據(jù)操作,耗時很少的線程操作。
對于每張SideTable表中的弱引用表weak_table_t,其也是一張哈希表的結(jié)構(gòu),其內(nèi)部包含了每個對象對應(yīng)的弱引用表weak_entry_t,而weak_entry_t是一個結(jié)構(gòu)體數(shù)組,其中包含的則是每一個對象弱引用的對象所對應(yīng)的弱引用指針。
4、ARC & MRC
-
MRC:手動管理對象引用計數(shù)的方式,但這也是內(nèi)存管理的立足之本.在MRC中可以調(diào)用
alloc
,retain
,release
,retainCount
,dealloc
等方法,這些方法在ARC中只能調(diào)用alloc方法,調(diào)用其他的會引起編譯報錯,不過在ARC模式中可以重寫dealloc方法。
-
ARC:就是現(xiàn)代程序員常用的對象引用計數(shù)管理方式,ARC是由編譯器和runtime協(xié)作,共同完成對對象引用計數(shù)的控制,而不需要程序員自己手動控制。ARC中禁止手動調(diào)用
retain/realse/retainCount/dealloc
。相比起MRC,在ARC中新增了weak和strong等屬性關(guān)鍵字。
5、引用計數(shù)
-
alloc
:這個方法實質(zhì)上是經(jīng)過了一系列調(diào)用,最終調(diào)用了C函數(shù)的calloc,需要注意的是調(diào)用該方法時,對象的引用計數(shù)并沒有+1.id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA assert(!isa.indexed); #endif SideTable& table = SideTables()[this]; if (table.trylock()) { size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this; } return sidetable_retain_slow(table); }
image retain
:這個方法是先在SideTables中通過哈希查找找到對象所在的那張SideTable表,隨后在SideTable中的引用計數(shù)表中再次通過哈希查找找到對象所對應(yīng)的size_t,再加上一個系統(tǒng)的(引用計數(shù)+1宏)。為什么這里沒有+1而是加上一個系統(tǒng)的宏呢,因為在size_t結(jié)構(gòu)中,前兩位不是儲存引用計數(shù)的,第一位存儲的是是否有弱引用指針指向,第二位存儲的是對象是否在被回收中。所以,在增加其引用計數(shù)時需要右移兩位再進(jìn)行增加,所以用到了這個系統(tǒng)的宏SIDE_TABLE_RC_ONE。
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.indexed);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
if (table.trylock()) {
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;
}
return sidetable_release_slow(table, performDealloc);
}
-
release
:這個方法跟retain方法原理一樣,只不過是減一個系統(tǒng)的宏SIDE_TABLE_RC_ONE
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;
}
-
retainCount
:這個方法的實現(xiàn)同樣是先查找系統(tǒng)的SideTables表,并找到對象對應(yīng)的SideTable表,但在之前要先申明一個size_t為1的對象,隨后在對應(yīng)的引用計數(shù)表中找到了對象對應(yīng)的引用計數(shù)后,通過右移找到的count對象,與之前創(chuàng)建好的1相加,最后返回其結(jié)果便是引用計數(shù)。所以這就是為什么系統(tǒng)在調(diào)用alloc方法后并沒有給對象的引用計數(shù)+1,但retainCount方法調(diào)用后對象的引用計數(shù)就是1的原因。
inline void
objc_object::rootDealloc()
{
assert(!UseGC);
if (isTaggedPointer()) return;
if (isa.indexed &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc)
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
#if SUPPORT_GC
if (UseGC) {
auto_zone_retain(gc_zone, obj); // gc free expects rc==1
}
#endif
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 = !UseGC && obj->hasAssociatedObjects();
bool dealloc = !UseGC;
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (dealloc) obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (!isa.indexed) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (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());
}
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
//將指向該對象的弱引用指針置為nil
weak_clear_no_lock(&table.weak_table, (id)this);
}
//從引用計數(shù)表中擦除該對象引用計數(shù)
table.refcnts.erase(it);
}
table.unlock();
}
void
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;
}
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_entry_remove(weak_table, entry);
}
-
dealloc
:對象在被回收時,就會調(diào)用dealloc方法,其內(nèi)部實現(xiàn)流程首先要調(diào)用一個_objc_rootDealloc()方法,再方法內(nèi)部再調(diào)用一個rootDealloc()方法,此時在rootDealloc中會判斷該對象的isa指針,依次判斷指針內(nèi)的內(nèi)容:nonpointer_isa,weakly_referenced,has_assoc,has_cxx_dtor,has_sidetable_rc,如果判斷結(jié)果為:該isa指針不是非指針型的isa指針,沒有弱引用的指針指向,沒有相應(yīng)的關(guān)聯(lián)對象,沒有c++相關(guān)的內(nèi)容,沒有使用ARC模式,沒有關(guān)聯(lián)到散列表中,即判斷的內(nèi)容都為否,則可以直接調(diào)用c語言中的free()函數(shù)進(jìn)行相應(yīng)的內(nèi)存釋放,否則就會調(diào)用objc_dispose()這個函數(shù)。
6、弱引用
當(dāng)我們創(chuàng)建一個弱引用變量weakPointer的時候在編譯器中可以這么寫
id __weak weakPointer = object;
這行代碼實際上在系統(tǒng)內(nèi)部實現(xiàn)的時候轉(zhuǎn)化為了兩行代碼:
id weakPointer;
objc_initWeak(&weakPointer,object);
首先定義了一個變量weakPointer,其次調(diào)用objc_initWeak方法來給weakPointer的這個弱引用指針來賦值。
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
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:
if (HaveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);
if (HaveOld && *location != oldObj) {
SideTable::unlockTwo<HaveOld, HaveNew>(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.
if (HaveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(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.
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
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<HaveOld, HaveNew>(oldTable, newTable);
return (id)newObj;
}
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry;
new_entry.referent = referent;
new_entry.out_of_line = 0;
new_entry.inline_referrers[0] = referrer;
for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
new_entry.inline_referrers[i] = nil;
}
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t index = hash_pointer(referent) & weak_table->mask;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
內(nèi)部原理與上面相似,當(dāng)弱引用指針指向這個objcet變量時,首先去SideTables散列表中通過哈希查找,來找到object這個對象的SideTable表,再通過一次哈希查找,利用object對象的地址,在SideTable中的弱引用表中找到其對應(yīng)的弱引用結(jié)構(gòu)體數(shù)組,如果這個數(shù)組存在則在里面添加一個之前weakPointer的地址作為弱引用指針指向object,如果沒有這個結(jié)構(gòu)體數(shù)組,則創(chuàng)建一個數(shù)組,將這個指針添加到第0個元素,同時給第2,3,4個元素設(shè)置為nil。這樣就完成了一個弱引用指針的定義實現(xiàn)過程了。
關(guān)于如何清除弱引用指針的,Dealloc方法調(diào)用過程已經(jīng)說的很明白了,過程也與上面一行說的類似,就是在最后調(diào)用sidetable_clearDeallocating()方法中將對象對應(yīng)的弱引用列表找到,將所有弱引用指針置為nil的時候就把相應(yīng)的弱引用指針擦除了,這樣說就一目了然了。
7、自動釋放池
因為現(xiàn)在大家都在使用ARC模式下進(jìn)行編程,一個很重要的問題也是最容易被大家所忽視的問題就是自動釋放池,大部分程序員尤其是剛?cè)胄械亩贾皇侵烙羞@么一個東西,但具體是什么,工作的原理是什么,在什么時候使用它都一概不知。所以寫一篇文章,記錄一下個人對自動釋放池的一些理解。
我們新建一個OC項目,在main函數(shù)中可以看到這么一串代碼:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
編譯器會將@autoreleasepool{}
改寫為:
//創(chuàng)建一個無類型指針的哨兵對象
void *pool = objc_autoreleasePoolPush();
//執(zhí)行@autoreleasepool{}中對應(yīng){}里所書寫的代碼
{}中的代碼
//釋放哨兵對象所分隔區(qū)域內(nèi)所有對象的引用計數(shù)
objc_autoreleasePoolPop(pool);
由以上可以看出,autoreleasepool工作的原理就是一個壓棧,一個出棧,push方法創(chuàng)建哨兵對象作為標(biāo)記,pop操作作批量引用計數(shù)釋放的工作。
autoreleasepool是以棧為節(jié)點,通過雙向鏈表的形式組合而成的,autoreleasepool是與線程一一對應(yīng)的。
那么什么是雙向鏈表呢,雙向鏈表的單位是節(jié)點,從頭結(jié)點開始一直到尾節(jié)點,每一個節(jié)點都會有一個父指針和子指針,父指針指向前一個節(jié)點,子指針指向后一個節(jié)點,而頭節(jié)點的父指針指向NULL,尾節(jié)點的子指針指向NULL。這樣就把棧空間通過雙向鏈表的形式連接起來了。
而autoreleasepoolPage就是雙鏈鏈表中每一個節(jié)點的長度了,可以說,autoreleasepoolPage把棧空間中一些大小的空間分割成一個個單位,稱為節(jié)點,然后通過雙向鏈表的形式連接起來,這就成了一個整體的autoreleasepool的結(jié)構(gòu)了。
在AutoreleasePool中有四個變量分別是:
- 1、id *next一個id類型的指針,指向的是下一個可存儲對象的位置,
- 2、AutoreleasepoolPage const parent;這就是當(dāng)前page父節(jié)點page的地址指針。
- 3、AutoreleasepoolPage child,同理,是子節(jié)點表地址的指針。4.pthread_t const thread;這個變量中就記錄了線程的情況,所以說自動釋放池是與線程一一對應(yīng)的關(guān)系。
執(zhí)行邏輯:
1、調(diào)用objc_autoreleasepoolPush()方法,在當(dāng)前autoreleasepoolPage中的next指針位置創(chuàng)建一個為nil的哨兵對象,隨后將next指針的位置指向下一個內(nèi)存地址。
-
2、第二步就是執(zhí)行代碼了,在自動釋放池范圍內(nèi)的代碼被執(zhí)行,給逐個對象調(diào)用[object autorelease]方法。其實在autorelease方法內(nèi)部實現(xiàn)的步驟為:
- 1.判斷next指針是否已經(jīng)在棧頂了,如果是,則增加一個棧節(jié)點到鏈表上,隨后增加一個對象到新的棧節(jié)點鏈表中,如果不是的話則在next指針?biāo)傅奈恢锰砑右粋€調(diào)用autorelease方法的對象。
3、第三步就是執(zhí)行objc_autoreleasepoolPop()方法,該方法會根據(jù)傳入的哨兵對象找到對應(yīng)的內(nèi)存位置,然后根據(jù)哨兵對象的位置給上次push后添加的對象依次發(fā)送release消息,然后回退next指針到正確的位置。
以上三個步驟就是在autorelease自動釋放池中進(jìn)行的操作,也是這三個步驟構(gòu)成了自動釋放池。
總結(jié)一下
main函數(shù)中的autoreleasepool是在runloop結(jié)束的時候調(diào)用objc_autoreleasepoolPop的方法的
多層嵌套的autoreleasepool其實就是在棧中多次插入哨兵對象
而在我們開發(fā)的過程中,通過for循環(huán)加載一些占用內(nèi)存較大的對象時可以嵌套使用autoreleasepool,在這些對象使用完畢的時候及時被釋放掉,這樣就不會造成內(nèi)存過大或過多浪費的情況啦~
8、循環(huán)引用
-
循環(huán)引用分三種:
1.自循環(huán)引用
2.相互循環(huán)引用
3.多循環(huán)引用
循環(huán)引用出現(xiàn)的地方多數(shù)是在block,NSTimer中,代理中如果代理對象沒有設(shè)置為weak也會產(chǎn)生循環(huán)引用。
-
破環(huán)的方法無非是將一方引用的方式改為弱引用,但在OC中,引用一個對象而不增加其引用計數(shù)一共有三種關(guān)鍵字可以實現(xiàn):1.
weak
,2.block
,3._unsafe_unretained
第一種weak之前的文章已經(jīng)詳細(xì)敘述了其工作原理,如何對對象進(jìn)行添加弱引用指針以及弱引用指針如何在對象被銷毀時進(jìn)行回收的,這里就不多寫了。
第二種__block關(guān)鍵字,其在MRC模式下,使用__block關(guān)鍵字修飾一個對象,不會增加其引用計數(shù),從而可以避免循環(huán)引用。但是在ARC中,__block關(guān)鍵字修飾的對象會被強引用,就沒法避免循環(huán)引用了。這就是__block和__weak的區(qū)別
第三種_unsafe_unretained關(guān)鍵字,使用之后確實不會增加引用對象的引用計數(shù),但是當(dāng)引用對象被釋放的時候,會產(chǎn)生懸垂指針,從而發(fā)生一些不可預(yù)見的錯誤,所以是不安全的,不可取。
以上就是這三種關(guān)鍵字的區(qū)別了。
最常見的循環(huán)引用案例——NSTimer
在NSTimer被創(chuàng)建的時候,由于系統(tǒng)常駐的runloop對其進(jìn)行強引用,其又會對當(dāng)前對象進(jìn)行強引用,當(dāng)前對象又會對其進(jìn)行循環(huán)引用,而VC又會對當(dāng)前對象進(jìn)行循環(huán)引用,這時候就造成了一個環(huán),導(dǎo)致內(nèi)存泄漏。這時候如果將VC中創(chuàng)建的對象timer的關(guān)鍵字改為weak也無濟于事,因為當(dāng)VC釋放對象的時候,timer并不會對系統(tǒng)中持有的定時器對象進(jìn)行釋放,是因為runloop對系統(tǒng)Timer對象還有一個強引用,導(dǎo)致系統(tǒng)中的Timer不會被正常釋放,而系統(tǒng)Timer又對當(dāng)前timer對象有一個強引用,這樣就導(dǎo)致了當(dāng)前對象timer無法被正常的釋放了。遇到這種問題,大部分圖省事的做法就是手動破環(huán),當(dāng)VC被回收的時候手動將timer調(diào)用廢棄方法,回收系統(tǒng)對象。這種做法也可取,但是在VC非常多的時候,而且使用定時器的地方非常多的時候,稍不注意忘記回收timer就會導(dǎo)致內(nèi)存的泄漏,而且這種方式也是非常耗費大量無用功的。所以我自己封裝了一個weakTimer的自定義定時器。
將系統(tǒng)Timer實例對象,與VC中timer對象以及VC的關(guān)系變了一下,在中間加了一個過渡層對象。將過度對象弱引用VC為target,然后弱引用系統(tǒng)Timer,并且調(diào)用target中真正需要實現(xiàn)的方法,每次定時器的回調(diào)方法時,過度對象都會去判斷這個target是否為nil,如果為nil就將定時器置空,如果對象還在,就會先判斷目標(biāo)方法是否相應(yīng),隨后調(diào)用真正的方法。這種破環(huán)的模式即安全,又對調(diào)用者來說簡單,且不易出錯,比較提高效率。
#import <Foundation/Foundation.h>
@interface NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats;
@end
#import "NSTimer+WeakTimer.h"
@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;
- (void)fire:(NSTimer *)timer;
@end
@implementation TimerWeakObject
- (void)fire:(NSTimer *)timer
{
if (self.target) {
if ([self.target respondsToSelector:self.selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:self.selector withObject:timer.userInfo];
#pragma clang diagnostic pop
}
}
else{
[self.timer invalidate];
}
}
@end
@implementation NSTimer (WeakTimer)
+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
{
TimerWeakObject *object = [[TimerWeakObject alloc] init];
object.target = aTarget;
object.selector = aSelector;
object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
return object.timer;
}
@end