block常見問題及底層初探

block常見的幾個問題:

  • 1、block是什么
    block是一個指向結構體的指針,編譯器會將block的內部代碼生成對應的函數。

  • 2、block為什么用copy修飾
    正常創建出來的block的內存是放在棧中(如果block內部沒有調用外部變量時存放在全局區),程序員無法去管理,什么時候釋放內存也不是程序員可以決定的,棧區的特點就是創建的對象隨時可能被銷毀,一旦被銷毀后續再次調用空對象就可能會造成程序崩潰。所以一般情況下,就需要使用copy去修飾,會把block copy到堆上,在ARC中,對block代碼塊內部的對象強引用,在非ARC中對于引用對象進行一次retain操作。

  • 3、block與循環引用
    在block代碼塊內部如果使用了當前對象進行調用方法,或者他的的操作,就會對當前對象進行強引用一次,并且如果這個block歸當前對象持有,就會導致block和當前對象互相持有引用,而都不能正常釋放。(注意:如果block不是當前對象所持有,例如在控制器中使用AFNetWorking請求,AFNetWorking的block并不是當前控制器所持有的,所以這里不用__weak,不會造成循環引用)

  • 4、_strong的使用
    使用_weak來修飾可以避免對控制器或者當前類的循環引用.
    但是有的時候,當前對象self已經銷毀了,之后再去執行這個block,里面的weakSelf就是個nil了,可能會出現異常,于是最好是如下寫法:

    __weak typeof(self) weakSelf = self;
    self.rightBtnClickHandler = ^() {
        __strong typeof(self) strongSelf = weakSelf;
        [strongSelf defaultRightBtnClick];
        [strongSelf defaultLeftBtnClick];
    };

? 在block內部使用__strong對weakSelf進行一次強引用,self的引用計數+1,此時strongSelf相當于是block內部的一個局部變量,只要block不結束,self這個對象就不會釋放,等到block執行完畢,局部變量strongSelf也會自動銷毀,完成釋放。

block底層初識

接下來看幾個常見的面試題

void test1()
{
    int value = 10;
    
    void (^block)(void) = ^{
        NSLog(@"value = %d", value);
    };
    
    value = 20;
    
    block(); // 打印結果:value = 10
}

void test2()
{
    __block int value = 10;
    
    void (^block)(void) = ^{
        NSLog(@"value = %d", value);
    };
    
    value = 20;
    
    block(); // 打印結果:value = 20
}

void test3()
{
    static int value = 10;
    
    void (^block)(void) = ^{
        NSLog(@"value = %d", value);
    };
    
    value = 20;
    
    block(); // 打印結果:value = 20
}

int value = 10;
void test4()
{
    void (^block)(void) = ^{
        NSLog(@"value = %d", value);
    };
    
    value = 20;
    
    block(); // 打印結果:value = 20
}

可以看出,先test1中,我們更改了局部變量value的值,但是block中的value值并沒有變化,但是在test2、3、4中,卻能成功更改。這是為什么呢?
我們嘗試著看下它們的底層代碼。
終端執行clang -rewrite-objc main.m

block_clang.png

把文件編譯成c++代碼,得到.cpp文件,打開后估計有接近10萬行代碼,直接command+??,我們來到文件的最后,再往上面滑一點,可以看到如下:


block_底層代碼.png

我們可以看到 void (*block)(void) 是一個沒有參數沒有返回值的函數指針,既然是一個函數指針,那它就是一個變量,變量里面只能保存函數地址,等式右邊接收是下面這個函數的返回值:

  __test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

這個函數有4個參數,參數 flags= 0,這個參數其實就相當于Swift中指定了一個默認值,不傳也有值,可以忽略。所以上面test1中傳了3個參數,test2中傳了4個參數,其中第三個參數是value,前者是傳值,后者是傳的value的地址,故后者value的值發生了變化

block_test1.png
block_test2.png

總結:

  • block可以修改全部變量和靜態變量,不可以修改局部變量,如果想要修改使用__block,
  • block之所以能夠修改全局變量、靜態變量、__block修飾的局部變量,是因為把指向變量的指針地址copy到block結構體內部,而局部變量是copy的變量值到block內部.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。