iOS愛上底層-Block實現與原理

前言

不少人在面試的時候都會被問到Block,那麼Block分爲哪幾種類型呢? 其實Block共有6種類型,其中三種經常使用級別,分別是:_NSConcreteGlobalBlock _NSConcreteStackBlock _NSConcreteMallocBlock,三種系統級別 ,分別是_NSConcreteAutoBlock _NSConcreteFinalizingBlock _NSConcreteWeakBlockVariable,本次只介紹3種經常使用類型。面試

Block常見的三種類型

  • 全局Block(__NSGlobalBlock__)

  • 堆Block(__NSMallocBlock__)

  • 棧Block(__NSStackBlock__)

解決Block循環引用的幾種方式

//循環引用
    self.name = @"1234";
    self.block = ^{
        NSLog(@"%@",self.name);
    };
    self.block();
複製代碼

不少人都知道Block會引發循環引用,如同上面這段代碼,當self持有block,block持有self,就產生了循環引用。接下來就介紹3種解決循環引用的方式
一、__block:咱們只須要在self.name的下面建立一箇中介者vc(__block ViewController *vc = self),而後將block裏面的self.name替換成vc.name,用完以後將vc置爲nil便可。(俗稱中介者模式)
bash

二、傳參:咱們只須要將當前的self當作參數傳入到block裏面,block內部會建立一個臨時變量vc,此時咱們就打破了互相持有,就會解決循環引用的問題了

三、weak and strong:這種方式也是最多人用的,既建立weak,可是有一個問題,若是添加一個延遲執行,weakSelf就會提早釋放,致使訪問不到外界變量,因此咱們又須要在blcok裏面strong一下

Block的本質是什麼?

把左邊OC(左邊)的代碼編譯成C++(右邊),咱們會發現本來的block在C++裏面被拆解成一個叫 __main_block_impl_0的構造函數,咱們在往上看能夠發現,它其實就是一個結構體對象。而這個構造函數傳了兩個值 __main_block_func_0__main_block_desc_0_DATA__main_block_func_0:就是Block所執行的內容,在C++底層則變成了一個函數。 __main_block_desc_0_DATA:有兩個成員: reservedBlock_size

Block是如何捕獲外界變量的?

咱們建立一個局部變量a=10,而後看C++代碼,能夠發如今 __main_block_impl_0結構體中建立了一個屬性int a,而且在函數實現中建立了一個臨時變量a,將結構體中的屬性賦值給他。因爲此次拷貝是值拷貝,因此在函數裏面不能對當前的屬性進行修改。爲了可以改變a的值,要加上 __block,以下圖:

咱們能夠發現 __block修飾的臨時變量在C++中變成了一個結構體 __Block_byref_a_0。而且在調用函數的時候,傳的是a的指針,而且在函數的實現中, __Block_byref_a_0 *a = __cself->a則是進行一次指針拷貝,因此用 __block修飾的變量能夠在Block內部進行修改。

Block是如何從棧到堆?

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
複製代碼

這裏就是當前Block從棧->堆的過程。 一、aBlock->flags & BLOCK_NEEDS_FREE會判斷當前Block的引用計數器,由於block的引用計數器是不受runtime下層處理的,因此它由本身來進行管理。並且這裏的引用計數器是+2,而不是+1,由於+1會對別的屬性有影響,因此這裏是+2。函數

二、 aBlock->flags & BLOCK_IS_GLOBAL判斷當前的Block是否爲全局變量,是的話就直接返回
三、else就是將block複製到棧中,首先會建立一個新的結構體result,而後將舊的Block所有拷貝到新的Block中,而後isa就被標記爲 _NSConcreteMallocBlock

總結

一、解決block循環引用的思路就是中介者模式。
二、Block的本質就是結構體
三、當Block捕獲到外界變量時,他就會從全局block變成堆blockui

相關文章
相關標籤/搜索