Block的本质

515 阅读4分钟

Block本质

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象

定义一个block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
    
        void(^block)(void) = ^(void){
            NSLog(@"hello-world");
        };
        block();
    }
    return 0;
}

通过命令行执行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 获得main.cpp文件查看block的底层实现

编译后main.cpp里main函数代码如下

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

       //定义block变量
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        //执行block内部的代码
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

从上面代码中可以看出 __main_block_impl_0 是我们所定义的block __main_block_func_0 block内部代码封装成函数 __main_block_desc_0_DATA 定义的block所占内存的结构体

__main_block_impl_0结构体如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  //构造函数 类似OC init函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock; //block类型
    impl.Flags = flags;
    impl.FuncPtr = fp;// 执行函数的地址
    Desc = desc; //存储 __main_block_desc_0(0,sizeof(__main_block_impl_0))的值
  }
};

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;//指针指向了block内部执行函数的地址
};

__main_block_func_0

// block 内部代码封装成函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2v_yzvmxk793t31txq41bn50r1r0000gn_T_main_f08b1f_mi_0);
        }
        

__main_block_desc_0_DATA

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)};

最终block转化成__main_block_impl_0结构体对象,赋值给变量block,传入参数是__main_block_func_0__main_block_desc_0_DATA来执行__main_block_impl_0的构造函数,__main_block_desc_0_DATA函数赋值给__main_block_impl_0->FuncPtr,执行函数是block->FuncPtr(block),删除冗余代码之前是((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);,那么为什么block可以直接强制转化成__block_impl呢?因为__main_block_impl_0结构体的第一行变量是__block_impl,相当于__main_block_impl_0的内存地址和__block_impl的内存地址一样,强制转化也不会有问题。

block的变量捕获

  • 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

auto

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

main.cpp 代码

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

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;
  }
};

可以看出是直接将age 传进block的构造函数中 传递方式为值传递。

static

        int age = 10;
        static int height = 20;
        void (^block)(void) = ^(void){
            NSLog(@"age is %d height is %d",age,height);
        };
        age = 20;
        height = 30;
        block();

main.cpp

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        static int height = 20;
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
        age = 20;
        height = 30;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

可以看出height传值是用&取得指针地址符 在__main_block_impl_0结构体中 也是以*开头指针地址 所以height的值会发生改变。

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;
  }
};

全局变量

全局变量不用捕获到__main_block_impl_0结构体当中,直接访问全局变量。

__block修饰符

  • __bllock可以用于解决block内部无法修改auto变量值得问题
  • __block 不能修饰全局变量、静态变量(static)
  • 编译器会将__block变量包装成一个对象

code

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

main.cpp

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

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

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
     }
    return 0;
}

在block内部多了一个指向__Block_byref_age_0类型结构体的age指针。我们发现int类型的age在这个结构体内部了。

那也就是说,__block修饰的变量,编译器会把它包装成一个对象,然后我们的这个成员变量放到了这个对象的内部。

我们观察一下这个__Block_byref_age_0内部,这些变量可能有疑惑的也就是这个__forwarding。他是一个指向这个结构体自身的指针。而且我们还可以看出来在打印age的时候,是也是通过__forwarding调用的age(age->__forwarding->age)

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);

__main_block_desc_0这个结构体中也多了两个指针,这是与内存管理有关的函数。