Block底層實現原理

圖片描述

古巷悠悠歲月深,青石老街印舊痕
今夜小樓聽風雨,不見當年傘下人面試

前言

Block做爲iOS中老生常談的問題,也是面試中面試官比較喜歡問的 一個問題 ,下面咱們經過源碼查看block的底層實現原理函數

什麼是Block

Block:將函數及其上下文組裝起來的對象spa

Block本質就是一個對象

建立一個PHJBlock類指針

@implementation PHJBlock
    
    - (void)test {
        int a = 10;
        void (^ block)(void) = ^{
            NSLog(@"%d", a);
        };
        block();
    }
    
    @end

查看編譯後的C++源碼code

編譯: Clang -rewrite-objc PHJBlock.m對象

static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
        int a = 10;
        void (* block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }

__PHJBlock__test_block_impl_0的內部實現圖片

struct __PHJBlock__test_block_impl_0 {
          struct __block_impl impl;
          struct __PHJBlock__test_block_desc_0* Desc;
          int a;
          __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
          }
        };

__block_impl的內部實現ip

看到isa,證實block的本質就是一個對象內存

struct __block_impl {
      void *isa; // 看到isa,證實block的本質就是一個對象
      int Flags;
      int Reserved;
      void *FuncPtr;
    };

Block的三種類型

棧Block
堆Block
全局Blockcmd

int a = 10;
        // 堆block
        void(^block)(void) = ^{
            NSLog(@"%d", a);
        };
        block();
        NSLog(@"%@", block);
        
        // 全局block
        void(^block1)(void) = ^{
        };
        block1();
        NSLog(@"%@", block1);
        
        // 棧block
        NSLog(@"%@", ^{
            NSLog(@"%d", a);
        });

打印查看block內存地址
圖片描述

Block捕獲外部變量

局部變量

基本數據類型:截獲其值
對象類型:對於對象類型的局部變量連同全部權修飾符一塊兒截獲

靜態局部變量

以指針形式

全局變量

不截獲

靜態全局變量

不截獲

Block捕獲基本數據類型局部變量

不加修飾詞修飾的變量

- (void)test {
        int a = 100;
        void(^block)(void) = ^{
            printf("%d", a);
        };
        block();
    }

編譯後的C++代碼:捕獲外部變量的值

static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) {
      // 捕獲 a 的值
      int a = __cself->a; // bound by copy
            printf("%d", a);
        }

加修飾詞修飾的變量

- (void)test {
        __block int a = 100;
        void(^block)(void) = ^{
            a ++;
            printf("%d", a);
        };
        block();
    }

編譯後的C++代碼:捕獲外部變量的的地址

總結:這也就解釋了爲何咱們在外部變量前加上__block就能在block內部能夠修改變量的值

static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) {
      // 捕獲 a 的地址
      __Block_byref_a_0 *a = __cself->a; // bound by ref
    
            (a->__forwarding->a) ++;
            printf("%d", (a->__forwarding->a));
    }

發現變量a加上__block後變成了一個對象
__forwarding指針

struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };

Block捕獲對象類型局部變量

- (void)test {
        
        __unsafe_unretained id obj = nil;
        __strong NSObject *obj1 = nil;
        
        void(^block)(void) = ^{
            NSLog(@"__unsafe_unretained類型變量:%@", obj);
            
            NSLog(@"__strong類型變量:%@", obj1);
        };
        block();
    }

編譯後的C++代碼:連同外部變量的修飾詞一塊兒捕獲

static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
    
        // 修飾詞一塊兒捕獲
        __attribute__((objc_ownership(none))) id obj = __null;
        __attribute__((objc_ownership(strong))) NSObject *obj1 = __null;
    
        void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, obj, obj1, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }

總結:這也就解釋了爲何咱們在外部對象前加上__weak就能在block內部使用的時候能夠避免循環引用問題

Block捕獲靜態局部變量

- (void)test {
        static int a = 100;
        void(^block)(void) = ^{
            NSLog(@"static類型變量a :%d", a);
        };
        block();
    }

編譯後的C++代碼:捕獲變量的地址

&a表明傳入的是a變量的地址

static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) {
        static int a = 100;
        // 下面的&a表明傳入的是a變量的地址
        void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, &a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }

int *a接收傳入的a變量的地址

struct __PHJBlock__test_block_impl_0 {
      struct __block_impl impl;
      struct __PHJBlock__test_block_desc_0* Desc;
      // 捕獲a變量的地址
      int *a;
      __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

Block不會捕獲全局變量

int a = 100;
    
    @implementation PHJBlock
    
    - (void)test {
        void(^block)(void) = ^{
            NSLog(@"全局變量a :%d", a);
        };
        block();
    }
    
    @end

編譯後的C++代碼:不會捕獲全局變量

struct __PHJBlock__test_block_impl_0 {
      struct __block_impl impl;
      struct __PHJBlock__test_block_desc_0* Desc;
      __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

Block不會捕獲靜態全局變量

static int a = 100;
    
    @implementation PHJBlock
    
    - (void)test {
        void(^block)(void) = ^{
            NSLog(@"全局變量a :%d", a);
        };
        block();
    }
    
    @end

編譯後的C++代碼:不會捕獲靜態全局變量

struct __PHJBlock__test_block_impl_0 {
      struct __block_impl impl;
      struct __PHJBlock__test_block_desc_0* Desc;
      __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

通常狀況下對截獲變量進行賦值操做須要加__block

須要加__block

__block NSMutableDictionary *dicM = nil;
    void(^block)(void) = ^{
       dicM = [NSMutableDictionary dictionary];
    };
    block();

不須要加__block

NSMutableDictionary *dicM = nil;
    void(^block)(void) = ^{
       [dicM setObject:@(1) forKey:@"1"];
    };
    block();

總結:賦值的時候須要加__block,操做使用的時候不用加

__forwarding指針

棧Block沒有進行copy操做

棧__forwarding指針都指向棧中本身的變量

棧Block若是進行了copy操做

棧和堆上的__forwarding指針都指向堆的變量

總結:不論在任何內存位置,均可以順利訪問同一個__block變量

Block的Copy操做

棧block進行copy,獲得堆block
堆block進行copy,增長引用計數
全局block進行copy,block不會產生影響

__block 修飾局部對象類型變量的循環引用問題

MRC下,不會產生循環引用
在ARC下,會產生循環引用,引發內存泄漏,解決:在block內部對__block修飾的對象變量進行置nil操做
相關文章
相關標籤/搜索