iOS內(nèi)存管理

1、內(nèi)存布局

image
  • 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散列表中。

    image
    image
  • 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類型的無符號整型。

    image
    image

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)的弱引用指針。

image
image
image
image

4、ARC & MRC

image
  • MRC:手動管理對象引用計數(shù)的方式,但這也是內(nèi)存管理的立足之本.在MRC中可以調(diào)用alloc,retain,release,retainCount,dealloc等方法,這些方法在ARC中只能調(diào)用alloc方法,調(diào)用其他的會引起編譯報錯,不過在ARC模式中可以重寫dealloc方法。
image
  • 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);
}
image
  • 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)了。

image
image
image

在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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容