面試遇到block的第一天-本質

block的本質

  • block本質上也是一個OC對象,他的內部也有isa指針
  • block是封裝了函數調用以及函數調用環境(好比函數參數)的OC 對象

寫一個簡單的block demo驗證分析一下block的本質:bash

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        void(^myBlock)(void) = ^{
            
            NSLog(@"this is a block");
        };
        myBlock();
        NSLog(@"%@",myBlock);
        
    }
    return 0;
}
複製代碼

使用命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m編譯成C++代碼,找到C++代碼最後咱們能夠看到block被編譯成一個結構體iphone

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼

第一個屬性__block_impl又是一個結構體函數

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
複製代碼

這裏isa指針是指向了block的類型,這個後面再說,在後面貼的C++代碼中能夠注意一下,都是block的類型。
咱們這個簡單的myBlock內部只有一句NSLog的打印,被封裝在了__main_block_func_0這個函數中測試

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_26cca1_mi_0);
        }
複製代碼

__main_block_func_0的函數地址又做爲__main_block_impl_0構造函數的第一個參數fp,被賦值給implFuncPtr ui

最後經過 ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);調用FuncPtr,也就是調用myBlock中封裝的函數。

根據以上對源碼的分析 ,也驗證了咱們最開始說的兩點block的本質。this

變量捕獲

上面探究block本質的demo代碼特別簡單,下面咱們由深到淺的繼續探究block是如何獲取外部變量的。spa

局部變量(auto變量)

所謂auto變量也叫自動變量,就是離開做用域(他所在的{})就會自動銷燬,也就是局部變量,由於auto能夠省略,因此咱們平時均可以直接定義局部變量,不須要在前面加上auto修飾符。
先看一段示例代碼:指針

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        int age = 10;
        void(^myBlock)(void) = ^{
            
            NSLog(@"this is a block - %d ",age);
        };
        age = 20;
        myBlock();        
    }
    return 0;
}
複製代碼

那麼請問這段代碼的打印age的值是多少呢?有人以爲是20,有人以爲是10,這裏確定的告訴你們,或者你們手動寫一寫這段代碼測試一下打印結果,實際上是10.仍是用命令編譯成C++代碼一看究竟code

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_edbb92_mi_0,age);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 10;
        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);


    }
    return 0;
}
複製代碼

一樣的結構體,一樣的調用方式,上面說過的內容就不重複了,主要看局部變量age是如何被block獲取到的:cdn

  1. __main_block_impl_0結構體中添加了一個age屬性,構造函數中也添加了age: age(_age) 是C++語法,表示默認執行age = _age
  2. void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); 直接把age的值獲取到,而且保存在結構體內部,並且是值傳遞
  3. age = 20; 雖然修改了age的值,可是結構體內部的age的值並不會發生改變

static變量

auto相對的,還有static修飾的變量,那麼blockstatic變量捕獲的方式和auto變量有什麼不一樣呢?看下面的測試代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        auto int age = 10;
        static int height = 50;
        void(^myBlock)(void) = ^{
            
            NSLog(@"this is a block - %d ,height is %d",age, height);
        };
        age = 20;
        height = 100;
        myBlock();        
    }
    return 0;
}
複製代碼

輸出

block[87871:3465064] this is a block - 10 ,height is 100
複製代碼

很明顯咱們在block定義以後修改static變量的值,block內部是能夠知道的,再看C++代碼

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  int *height;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_716be0_mi_0,age, (*height));
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        auto int age = 10;
        static int height = 50;
        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
        age = 20;
        height = 100;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}
複製代碼

根據上面編譯完的代碼能夠很明顯看到變量age和height被block獲取時的區別,height被結構體__main_block_impl_0存儲的是地址

全局變量

繼續根據測試代碼和C++代碼,分析block對變量的捕獲

#import <Foundation/Foundation.h>
int age = 10;
static int height = 50;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
       
        void(^myBlock)(void) = ^{
            
            NSLog(@"this is a block - %d ,height is %d",age, height);
        };
        age = 20;
        height = 100;
        myBlock();        
    }
    return 0;
}

複製代碼

C++代碼:

#pragma clang assume_nonnull end
int age = 10;
static int height = 50;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_1de4db_mi_0,age, height);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        age = 20;
        height = 100;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}
複製代碼

能夠得出結論:

  • 全局變量不會被捕獲到block內部,無論在程序哪裏均可以直接訪問
  • 爲何局部變量須要捕獲呢?其實上面也說過了,是由於做用域的問題,最後block的調用,是在跨函數訪問變量,若是不捕獲到block內部,訪問auto變量的時候已經釋放了

self的捕獲

其實self的捕獲,纔是開發中常常遇到的狀況,也是引發block循環引用的罪魁禍首。固然self的捕獲能夠歸到上面兩類中,但仍是想拿出來單獨分析一下。也算是對上面兩類變量分析結果的實踐吧。

#import "Person.h"

@implementation Person
- (void)test_block {
    void(^block)(void) = ^{
        NSLog(@"this is a block - %p",self);
    };
}
@end

複製代碼

先思考一下,這裏self會被捕獲到block中嘛,self在這裏是一個auto變量仍是static變量仍是全局變量呢?先分析出是什麼變量,下一步咱們才能知道self是以什麼形式被捕獲到block中或者會不會被捕獲到block中。

你知道答案了嗎?

看看下面的C++代碼:

struct __Person__test_block_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_block_desc_0* Desc;
  Person *self;
  __Person__test_block_block_impl_0(void *fp, struct __Person__test_block_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼

能夠獲得結論,block是會捕獲self的,但是self是個局部變量嘛?這裏又要提到method的兩個默認參數了self_cmd,代碼中的test方法被編譯成_I_Person_test_block,他有兩個參數是self_cmd,函數的參數就是局部變量,而後被捕獲到block中賦值給struct中的self

static void _I_Person_test_block(Person * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__Person__test_block_block_impl_0((void *)__Person__test_block_block_func_0, &__Person__test_block_block_desc_0_DATA, self, 570425344));
}
複製代碼

同理,若是是block中訪問self的成員變量(self.name)或者經過self調用別的方法([self method]),也一樣會捕獲self到block中。

相關文章
相關標籤/搜索