iOS內存管理

1、內存布局

image
  • stack:方法調用

  • heap:通過alloc等分配對象

  • bss:未初始化的全局變量等。

  • data:已初始化的全局變量等。

  • text:程序代碼。

內存條中主要分為幾大類:棧區(stack)、堆區(heap)、常量區、代碼區(.text)、保留區。常量區分為未初始化區域(.bss)和已初始化區域(.data),棧區stack存儲順序是由高地址存向低地址,而堆區是由低地址向高地址存儲。內存條中地址由低到高的區域分別為:保留區,代碼區,已初始化區(.data),未初始化區(.bss),堆區(heap),棧區(stack),內核區。而程序員操作的主要是棧區與堆區還有常量區。

申請后的系統是如何響應的?

棧:

  • 只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。

堆:

  • 首先應該知道操作系統有一個記錄空閑內存地址的鏈表。當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序。由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。

申請大小的限制是怎樣的?

  • 棧:棧是向低地址擴展的數據結構,是一塊連續的內存的區域。是棧頂的地址和棧的最大容量是系統預先規定好的,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數 ) ,如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。
  • 堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

申請效率的比較?

  • 棧:由系統自動分配,速度較快。但程序員是無法控制的。
  • 堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.

2、內存管理方案

  • 1、TaggedPointer:小對象,比如NSNumber

    其主要的原理就是在對象的指針中加入特定需要記錄的信息,以及對象所對應的值,在64位的系統中,一個指針所占用的內存空間為8個字節,已足以存下一些小型的數據量了,當對象指針的空間中存滿后,再對指針所指向的內存區域進行存儲,這就是taggedPointer。距離NSNumber,最低4位用于標記是什么類型的數據(long為3,float則為4,Int為2,double為5),而最高4位的“b”表示是NSNumber類型;其余56位則用來存儲數值本身內容。

  • 2、NONPOINTER_ISA:objc_objcet對象中isa指針分為指針型isa與非指針型isa(NONPOINTER_ISA),運用的便是類似這種技術。

    在一個64位的指針內存中,第0位存儲的是indexed標識符,它代表一個指針是否為NONPOINTER型,0代表不是,1代表是。第1位has_assoc,顧名思義,1代表其指向的實例變量含有關聯對象,0則為否。第2位為has_cxx_dtor,表明該對象是否包含C++相關的內容或者該對象是否使用ARC來管理內存,如果含有C++相關內容或者使用了ARC來管理對象,這一塊都表示為YES,第3-35位shiftcls存儲的就是這個指針的地址。第42位為weakly_referenced,表明該指針對象是否有弱引用的指針指向。第43位為deallocing,表明該對象是否正在被回收。第44位為has_sidetable_rc,顧名思義,該指針是否引用了sidetable散列表。第45-63位extra_rc裝的就是這個實例變量的引用計數,當對象被引用時,其引用計數+1,但少量的引用計數是不會直接存放在sideTables表中的,對象的引用計數會先存在NONPOINTER_ISA的指針中的45-63位,當其被存滿后,才會相應存入sideTables散列表中。

    image
    image
  • 3、散列表

    散列表在系統中的提現是一個sideTables的哈希映射表,其中所有對象的引用計數(除上述存在NONPOINTER_ISA中的外)都存在這個sideTables散列表中,而一個散列表中又包含眾多sideTable。每個SideTable中又包含了三個元素,spinlock_t自旋鎖,RefcountMap引用計數表,weak_table_t弱引用表。所以既然SideTables是一個哈希映射的表,為什么不用SideTables直接包含自旋鎖,引用技術表和弱引用表呢?因為在眾多線程同時訪問這個SideTables表的時候,為了保證數據安全,需要給其加上自旋鎖,如果只有一張SideTable的表,那么所有數據訪問都會出一個進一個,單線程進行,非常影響效率,而且會帶來不好的用戶體驗,針對這種情況,將一張SideTables分為多張表的SideTable,再各自加鎖保證數據的安全,這樣就增加了并發量,提高了數據訪問的效率,所以這就是一張SideTables表下涵蓋眾多SideTable表的原因。

    基于此,我們進行SideTable的表分析,那么當一個對象的引用計數增加或減少時,需要去查找對應的SideTable并進行引用計數或者弱引用計數的操作時,系統又是怎樣實現的呢。

    當一個對象訪問SideTables時,首先會取到對象的地址,將地址進行哈希運算,與SideTables的個數取余,最后得到的結果就是該對象所要訪問的SideTable所在SideTables中的位置,隨后在取到的SideTable中的RefcountMap表中再次進行一次哈希查找,找到該對象在引用計數表中所對應的位置,如果該位置存在對應的引用計數,則對其進行操作,如果沒有對應的引用計數,則創建一個對應的size_t對象,其實就是一個uint類型的無符號整型。

    image
    image

3、數據結構

對于Spinlock_t自旋鎖,其本質是一種“忙等”的鎖,所謂“忙等”就是當一條線程被加上Spinlock自旋鎖后,當線程執行時,會不斷的去獲取這個鎖的信息,一旦獲取到這個鎖,便進行線程的執行。這對于一般的高性能鎖比如信號量不同,信號量是當線程獲取到信號量小于等0時,便自動進行休眠,當信號量發出時,對線程進行喚醒操作,這樣就致使了兩種鎖的性質不同。Spinlock自旋鎖只適用于一些小型數據操作,耗時很少的線程操作。

對于每張SideTable表中的弱引用表weak_table_t,其也是一張哈希表的結構,其內部包含了每個對象對應的弱引用表weak_entry_t,而weak_entry_t是一個結構體數組,其中包含的則是每一個對象弱引用的對象所對應的弱引用指針。

image
image
image
image

4、ARC & MRC

image
  • MRC:手動管理對象引用計數的方式,但這也是內存管理的立足之本.在MRC中可以調用alloc,retain,release,retainCount,dealloc等方法,這些方法在ARC中只能調用alloc方法,調用其他的會引起編譯報錯,不過在ARC模式中可以重寫dealloc方法。
image
  • ARC:就是現代程序員常用的對象引用計數管理方式,ARC是由編譯器和runtime協作,共同完成對對象引用計數的控制,而不需要程序員自己手動控制。ARC中禁止手動調用retain/realse/retainCount/dealloc。相比起MRC,在ARC中新增了weak和strong等屬性關鍵字。

5、引用計數

  • alloc:這個方法實質上是經過了一系列調用,最終調用了C函數的calloc,需要注意的是調用該方法時,對象的引用計數并沒有+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中的引用計數表中再次通過哈希查找找到對象所對應的size_t,再加上一個系統的(引用計數+1宏)。為什么這里沒有+1而是加上一個系統的宏呢,因為在size_t結構中,前兩位不是儲存引用計數的,第一位存儲的是是否有弱引用指針指向,第二位存儲的是對象是否在被回收中。所以,在增加其引用計數時需要右移兩位再進行增加,所以用到了這個系統的宏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方法原理一樣,只不過是減一個系統的宏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:這個方法的實現同樣是先查找系統的SideTables表,并找到對象對應的SideTable表,但在之前要先申明一個size_t為1的對象,隨后在對應的引用計數表中找到了對象對應的引用計數后,通過右移找到的count對象,與之前創建好的1相加,最后返回其結果便是引用計數。所以這就是為什么系統在調用alloc方法后并沒有給對象的引用計數+1,但retainCount方法調用后對象的引用計數就是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);
      }
      //從引用計數表中擦除該對象引用計數
      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:對象在被回收時,就會調用dealloc方法,其內部實現流程首先要調用一個_objc_rootDealloc()方法,再方法內部再調用一個rootDealloc()方法,此時在rootDealloc中會判斷該對象的isa指針,依次判斷指針內的內容:nonpointer_isa,weakly_referenced,has_assoc,has_cxx_dtor,has_sidetable_rc,如果判斷結果為:該isa指針不是非指針型的isa指針,沒有弱引用的指針指向,沒有相應的關聯對象,沒有c++相關的內容,沒有使用ARC模式,沒有關聯到散列表中,即判斷的內容都為否,則可以直接調用c語言中的free()函數進行相應的內存釋放,否則就會調用objc_dispose()這個函數。

6、弱引用

當我們創建一個弱引用變量weakPointer的時候在編譯器中可以這么寫

id __weak weakPointer = object;

這行代碼實際上在系統內部實現的時候轉化為了兩行代碼:

id weakPointer;

objc_initWeak(&weakPointer,object);

首先定義了一個變量weakPointer,其次調用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];
}

內部原理與上面相似,當弱引用指針指向這個objcet變量時,首先去SideTables散列表中通過哈希查找,來找到object這個對象的SideTable表,再通過一次哈希查找,利用object對象的地址,在SideTable中的弱引用表中找到其對應的弱引用結構體數組,如果這個數組存在則在里面添加一個之前weakPointer的地址作為弱引用指針指向object,如果沒有這個結構體數組,則創建一個數組,將這個指針添加到第0個元素,同時給第2,3,4個元素設置為nil。這樣就完成了一個弱引用指針的定義實現過程了。

關于如何清除弱引用指針的,Dealloc方法調用過程已經說的很明白了,過程也與上面一行說的類似,就是在最后調用sidetable_clearDeallocating()方法中將對象對應的弱引用列表找到,將所有弱引用指針置為nil的時候就把相應的弱引用指針擦除了,這樣說就一目了然了。

7、自動釋放池

因為現在大家都在使用ARC模式下進行編程,一個很重要的問題也是最容易被大家所忽視的問題就是自動釋放池,大部分程序員尤其是剛入行的都只是知道有這么一個東西,但具體是什么,工作的原理是什么,在什么時候使用它都一概不知。所以寫一篇文章,記錄一下個人對自動釋放池的一些理解。

我們新建一個OC項目,在main函數中可以看到這么一串代碼:

int main(int argc, char * argv[]) {

  @autoreleasepool {

    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  }
}

編譯器會將@autoreleasepool{}改寫為:

//創建一個無類型指針的哨兵對象
void *pool = objc_autoreleasePoolPush();

//執行@autoreleasepool{}中對應{}里所書寫的代碼
{}中的代碼

//釋放哨兵對象所分隔區域內所有對象的引用計數
objc_autoreleasePoolPop(pool);

由以上可以看出,autoreleasepool工作的原理就是一個壓棧,一個出棧,push方法創建哨兵對象作為標記,pop操作作批量引用計數釋放的工作。

autoreleasepool是以棧為節點,通過雙向鏈表的形式組合而成的,autoreleasepool是與線程一一對應的。

那么什么是雙向鏈表呢,雙向鏈表的單位是節點,從頭結點開始一直到尾節點,每一個節點都會有一個父指針和子指針,父指針指向前一個節點,子指針指向后一個節點,而頭節點的父指針指向NULL,尾節點的子指針指向NULL。這樣就把棧空間通過雙向鏈表的形式連接起來了。

而autoreleasepoolPage就是雙鏈鏈表中每一個節點的長度了,可以說,autoreleasepoolPage把棧空間中一些大小的空間分割成一個個單位,稱為節點,然后通過雙向鏈表的形式連接起來,這就成了一個整體的autoreleasepool的結構了。

image
image
image

在AutoreleasePool中有四個變量分別是:

  • 1、id *next一個id類型的指針,指向的是下一個可存儲對象的位置,
  • 2、AutoreleasepoolPage const parent;這就是當前page父節點page的地址指針。
  • 3、AutoreleasepoolPage child,同理,是子節點表地址的指針。4.pthread_t const thread;這個變量中就記錄了線程的情況,所以說自動釋放池是與線程一一對應的關系。

執行邏輯:

  • 1、調用objc_autoreleasepoolPush()方法,在當前autoreleasepoolPage中的next指針位置創建一個為nil的哨兵對象,隨后將next指針的位置指向下一個內存地址。

  • 2、第二步就是執行代碼了,在自動釋放池范圍內的代碼被執行,給逐個對象調用[object autorelease]方法。其實在autorelease方法內部實現的步驟為:

    • 1.判斷next指針是否已經在棧頂了,如果是,則增加一個棧節點到鏈表上,隨后增加一個對象到新的棧節點鏈表中,如果不是的話則在next指針所指的位置添加一個調用autorelease方法的對象。
  • 3、第三步就是執行objc_autoreleasepoolPop()方法,該方法會根據傳入的哨兵對象找到對應的內存位置,然后根據哨兵對象的位置給上次push后添加的對象依次發送release消息,然后回退next指針到正確的位置。

以上三個步驟就是在autorelease自動釋放池中進行的操作,也是這三個步驟構成了自動釋放池。

總結一下

  • main函數中的autoreleasepool是在runloop結束的時候調用objc_autoreleasepoolPop的方法的

  • 多層嵌套的autoreleasepool其實就是在棧中多次插入哨兵對象

  • 而在我們開發的過程中,通過for循環加載一些占用內存較大的對象時可以嵌套使用autoreleasepool,在這些對象使用完畢的時候及時被釋放掉,這樣就不會造成內存過大或過多浪費的情況啦~

8、循環引用

  • 循環引用分三種:

    • 1.自循環引用

    • 2.相互循環引用

    • 3.多循環引用

  • 循環引用出現的地方多數是在block,NSTimer中,代理中如果代理對象沒有設置為weak也會產生循環引用。

  • 破環的方法無非是將一方引用的方式改為弱引用,但在OC中,引用一個對象而不增加其引用計數一共有三種關鍵字可以實現:1.weak,2.block,3._unsafe_unretained

    • 第一種weak之前的文章已經詳細敘述了其工作原理,如何對對象進行添加弱引用指針以及弱引用指針如何在對象被銷毀時進行回收的,這里就不多寫了。

    • 第二種__block關鍵字,其在MRC模式下,使用__block關鍵字修飾一個對象,不會增加其引用計數,從而可以避免循環引用。但是在ARC中,__block關鍵字修飾的對象會被強引用,就沒法避免循環引用了。這就是__block和__weak的區別

    • 第三種_unsafe_unretained關鍵字,使用之后確實不會增加引用對象的引用計數,但是當引用對象被釋放的時候,會產生懸垂指針,從而發生一些不可預見的錯誤,所以是不安全的,不可取。

以上就是這三種關鍵字的區別了。

最常見的循環引用案例——NSTimer

  • 在NSTimer被創建的時候,由于系統常駐的runloop對其進行強引用,其又會對當前對象進行強引用,當前對象又會對其進行循環引用,而VC又會對當前對象進行循環引用,這時候就造成了一個環,導致內存泄漏。這時候如果將VC中創建的對象timer的關鍵字改為weak也無濟于事,因為當VC釋放對象的時候,timer并不會對系統中持有的定時器對象進行釋放,是因為runloop對系統Timer對象還有一個強引用,導致系統中的Timer不會被正常釋放,而系統Timer又對當前timer對象有一個強引用,這樣就導致了當前對象timer無法被正常的釋放了。遇到這種問題,大部分圖省事的做法就是手動破環,當VC被回收的時候手動將timer調用廢棄方法,回收系統對象。這種做法也可取,但是在VC非常多的時候,而且使用定時器的地方非常多的時候,稍不注意忘記回收timer就會導致內存的泄漏,而且這種方式也是非常耗費大量無用功的。所以我自己封裝了一個weakTimer的自定義定時器。

  • 將系統Timer實例對象,與VC中timer對象以及VC的關系變了一下,在中間加了一個過渡層對象。將過度對象弱引用VC為target,然后弱引用系統Timer,并且調用target中真正需要實現的方法,每次定時器的回調方法時,過度對象都會去判斷這個target是否為nil,如果為nil就將定時器置空,如果對象還在,就會先判斷目標方法是否相應,隨后調用真正的方法。這種破環的模式即安全,又對調用者來說簡單,且不易出錯,比較提高效率。

#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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容