iOS中的Block彻底详解

1、Block基础介绍

一、概念介绍

Block又称为块或块对象,它是苹果在OSX10.6和iOS4.0中新加入的功能,是C语言层面的特性及功能实现,相似其它语言的闭包(closure)功能.当时苹果正使用LLVM的clang做为C语言的编译器来改进C/OC/C++/OC++等的编译处理,Block也是当时的重要成果.html

二、块的定义及语法

Block是带有自动变量(局部变量)的匿名函数.由于底层结构体实现有isa指针,也被看做是块对象.Block的出现实际上是为了代替指针函数的一种语法结构,以前传递状态须要使用不透明的void指针来传递状态,而Block把C语言特性所编写的代码封装成简明且易用的接口.git

下面是Block的实现语法结构: block的语法是脱字符^加花括号,花括号里是块的实现.块能够当成是变量的值使用.以下:github

^{
    //Block implementation
}
复制代码

Block类型的声明语法结构:面试

return_type (^block_name)(parameters)
//中间的block_name能够看做是变量名
复制代码

Block的调用:xcode

block_name(parameters)
复制代码

2、Block的关于捕获变量的底层实现

在块声明的范围里,全部变量均可觉得其所捕获.捕获的自动变量不可修改,修改要加__block前缀. 块总能修改实例变、静态变量、全局变量、全局静态变量,无需加__block.安全

咱们常常会看到block相关的文章或面试题中有这些内容.那么Block底层实现是怎么样的呢?bash

一、咱们首先来看一下Block的内存布局,以下:

/* Revised new layout. */
struct Block_descriptor {
    unsigned long int reserved; //预留内存大小
    unsigned long int size; //块大小
    void (*copy)(void *dst, void *src); //指向拷贝函数的函数指针
    void (*dispose)(void *); //指向释放函数的函数指针
};


struct Block_layout {
    void *isa; //指向Class对象
    int flags; //状态标志位
    int reserved; //预留内存大小
    void (*invoke)(void *, ...); //指向块实现的函数指针
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
复制代码

其中最重要的就是invoke函数指针和descriptor块的描述.invoke函数指针它指向了块的实现,它的void*参数传入的是块的结构体. descriptor的结构体中包含了块大小以及两个重要的辅助函数指针等.咱们注意到块的布局中,最下面一部分是捕获到的变量,前面提到的void*参数传入块的结构体也是为了拿到捕获到的变量.那么下面让咱们来看一下块的实际源码是什么样的:闭包

cd到目录下,在终端经过 clang -rewrite-objc 文件名 的方法将文件转换为C/C++代码.若是发现不能转换须要下载插件,指令为xcode-select --install.app

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int ivar = 1;
        void (^tmpBlock)(void) = ^{
            NSLog(@"tmpBlock:%d",ivar);
        };
        tmpBlock();
    }
    return 0;
}
复制代码

转换后点开目录下的.cpp文件,会看到上万行的代码,屏蔽掉无用的代码,直接找到主要代码以下:函数

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_459ed6_mi_2,ivar);
        }

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 ivar = 1;
        void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ivar));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
    }
    return 0;
}
复制代码

咱们来挨个看一下:

__block_impl 结构体是包含咱们刚刚在Block布局中提到的前四个成员变量.

__main_block_impl_0结构体是包含了__block_impl结构体变量和__main_block_desc_0结构体指针以及捕获到的变量.最后是初始化构造函数中对impl成员和desc进行赋值.

__main_block_func_0这个函数他传递了__main_block_impl_0*型的参数,并从中读取块结构体中捕获的参数进行实际操做.看到这里你有个明白了这个就是对应上面那个invoke函数指针所指向的函数.

__main_block_desc_0这个结构体就是上面所说的块的描述结构体.可能你会发现他少了两个辅助函数的函数指针,缘由后面会说.

最后是main函数中的具体初始化和函数调用.恰好对应块变量的声明实现部分和块的调用.

二、关于捕获自动变量的分析

捕获自动变量这部分首先咱们要明确有哪几种变量,以下:

  • auto自动变量:默认方法的做用域中不加前缀就是自动变量,并且ARC下默认还会加__strong.
  • static静态变量:存放在内存的可读写区,初始化只在第一次执行时起做用,运行过程当中语句块变量将保持上一次执行的值.
  • static全局静态变量:也放在可读写区,做用域为当前文件.
  • 全局变量:也在可读写区,整个程序可用.
  • 另外在OC中它们又分为对象类型和非对象类型.

下面让咱们看一下他们在Block中的表现. 首先是前面那个是用自动变量ivar的例子,若是咱们修改它的值编译器会报警告:

Variable is not assignable (missing __block type specifier) 复制代码

提示咱们不能修改变量,这是为何呢?咱们来打印一下block内外的指针变量的地址:

int ivar = 1;
        NSLog(@"块外%p",&ivar);
        void (^tmpBlock)(void) = ^{
            NSLog(@"块内%p",&ivar);
        };
        tmpBlock();

2019-12-05 18:20:40.063273+0800 LearningDemo[69358:5789849] 块外0x7ffeefbff4dc
2019-12-05 18:20:40.063840+0800 LearningDemo[69358:5789849] 块内0x103400ad0
复制代码

你会发现它们不是同一个地址,是的,block的__main_block_impl_0结构体拷贝了一份自动变量进去做为结构体的成员变量,你修改的是结构体内部的ivar的值,而不是外部ivar的值,他们并非同一块内存上的东西.苹果让编译器在这种状况下报警告,提示开发者是改不了的. 接下来咱们给它加一个static前缀,以下:

static int ivar = 1;
        NSLog(@"块外%p",&ivar);
        void (^tmpBlock)(void) = ^{
            ivar = 2;
            NSLog(@"块内%p",&ivar);
        };
        tmpBlock();

2019-12-05 18:41:36.083804+0800 LearningDemo[69801:5806136] 块外0x100003320
2019-12-05 18:41:36.084770+0800 LearningDemo[69801:5806136] 块内0x100003320
复制代码

有意思的事情发生了,能够修改变量了,并且地址竟然同样了~那咱们看一下转换后的源码:

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

            (*ivar) = 2;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_3,&(*ivar));
        }

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; 

        static int ivar = 1;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_2,&ivar);
        void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &ivar));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_4,tmpBlock);
    }
    return 0;
}
复制代码

从上面的代码能够看到,它捕获到的变量变成了指针变量int *ivar,而后再看打印的代码中的&(*ivar),它拿到的是指针变量中所存的地址,也就是说结构体捕获的指针变量存储的地址和Block外部的静态变量是同一个地址!而Block实现的赋值是经过*运算符引用变量来赋值的,像这样(*ivar) = 2;.到这里你应该已经明白为何能够改变外部变量了吧,由于它们访问的是同一个内存地址.总结一下就是地址访问是能够修改外部变量的. 那你可能会说,对象类型本质上不就是一个指针变量吗,为何不能修改,咱们以NSString*为例看一下它转换后的实现源码:

NSString * strvar = @"strvar";
            NSLog(@"块外%p",&strvar);
            void (^tmpBlock)(void) = ^{
                NSLog(@"块内%p",&strvar);
            };
            tmpBlock();

2019-12-05 19:01:07.019544+0800 LearningDemo[69970:5816051] 块外0x7ffeefbff4d8
2019-12-05 19:01:07.020200+0800 LearningDemo[69970:5816051] 块内0x100701780
复制代码

从上面的结果可知,地址是不同的,为何不同呢 看一下源码就知道了

NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_ca150a_mi_4,&strvar);
复制代码

从源码咱们得知他取得的是指针变量的地址,而非指针变量存储的地址,因此咱们给对象类型赋值实际上是赋值一个新的对象地址上去,而不是经过同一个地址去修改替换分配在堆上的对象. 当咱们加上static前缀以后发现能够修改了,实际上是由于Block捕获的是NSString **strvar;指针的指针,也就是存的是外部对象类型变量的地址而不是变量内存储的地址,因此咱们能够修改strvar了.

NSMutableString * strvar = [NSMutableString stringWithFormat:@"NSMutableString"];
            NSLog(@"块外%p",&strvar);
            void (^tmpBlock)(void) = ^{
                [strvar appendString:@"111"];
                NSLog(@"块内%p",&strvar);
            };
            NSLog(@"tmpBlock %@",strvar);
            tmpBlock();
            NSLog(@"tmpBlock %@",strvar);

2019-12-06 12:01:25.339194+0800 LearningDemo[81031:6194158] 块外0x7ffeefbff4d8
2019-12-06 12:01:25.339677+0800 LearningDemo[81031:6194158] tmpBlock NSMutableString
2019-12-06 12:01:25.339739+0800 LearningDemo[81031:6194158] 块内0x102841ef0
2019-12-06 12:01:25.339780+0800 LearningDemo[81031:6194158] tmpBlock NSMutableString111
复制代码

你会发现若是是可变对象的话是能够经过方法来修改堆上的对象的,也就是说只要能够经过同一个地址访问到变量就能够对其赋值修改等.

全局变量静态全局变量由于做用域很广,在它们的做用域里均可以访问,因此并不须要捕获,均可以Block内修改,下面是测试代码:

int globalVar = 1;
static int staticGlobalVar = 1;
NSString *globalStr = @"globalStr";
static NSString *staticGlobalStr = @"staticGlobalStr";

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"块外%p",&globalVar);
        NSLog(@"块外%p",&staticGlobalVar);
        NSLog(@"块外%p",&globalStr);
        NSLog(@"块外%p",&staticGlobalStr);
         void (^tmpBlock)(void) = ^{
             globalVar += 1;
             staticGlobalVar += 1;
             globalStr = @"修改globalStr";
             staticGlobalStr = @"修改staticGlobalStr";
             NSLog(@"块内%p",&globalVar);
             NSLog(@"块内%p",&staticGlobalVar);
             NSLog(@"块内%p",&globalStr);
             NSLog(@"块内%p",&staticGlobalStr);
         };
        NSLog(@"globalVar: %d",globalVar);
        NSLog(@"staticGlobalVar: %d",staticGlobalVar);
        NSLog(@"globalStr: %@",globalStr);
        NSLog(@"staticGlobalStr: %@",staticGlobalStr);
        tmpBlock();
        NSLog(@"globalVar: %d",globalVar);
        NSLog(@"staticGlobalVar: %d",staticGlobalVar);
        NSLog(@"globalStr: %@",globalStr);
        NSLog(@"staticGlobalStr: %@",staticGlobalStr);
    }
    return 0;
}
2019-12-06 13:54:10.126960+0800 LearningDemo[82231:6240152] 块外0x100003308
2019-12-06 13:54:10.127585+0800 LearningDemo[82231:6240152] 块外0x100003320
2019-12-06 13:54:10.127687+0800 LearningDemo[82231:6240152] 块外0x100003310
2019-12-06 13:54:10.127738+0800 LearningDemo[82231:6240152] 块外0x100003318
2019-12-06 13:54:10.127774+0800 LearningDemo[82231:6240152] globalVar: 1
2019-12-06 13:54:10.127802+0800 LearningDemo[82231:6240152] staticGlobalVar: 1
2019-12-06 13:54:10.127847+0800 LearningDemo[82231:6240152] globalStr: globalStr
2019-12-06 13:54:10.127891+0800 LearningDemo[82231:6240152] staticGlobalStr: staticGlobalStr
2019-12-06 13:54:10.127970+0800 LearningDemo[82231:6240152] 块内0x100003308
2019-12-06 13:54:10.128051+0800 LearningDemo[82231:6240152] 块内0x100003320
2019-12-06 13:54:10.128112+0800 LearningDemo[82231:6240152] 块内0x100003310
2019-12-06 13:54:10.128170+0800 LearningDemo[82231:6240152] 块内0x100003318
2019-12-06 13:54:10.128221+0800 LearningDemo[82231:6240152] globalVar: 2
2019-12-06 13:54:10.128271+0800 LearningDemo[82231:6240152] staticGlobalVar: 2
2019-12-06 13:54:10.128365+0800 LearningDemo[82231:6240152] globalStr: 修改globalStr
2019-12-06 13:54:10.128870+0800 LearningDemo[82231:6240152] staticGlobalStr: 修改staticGlobalStr
复制代码

三、关于_ _block的底层实现原理

咱们理解上面那些内容以后接下来正式讲解__block的原理:

__block int ivar = 1;
                NSLog(@"块外%p",&ivar);
                void (^tmpBlock)(void) = ^{
                    ivar += 1;
                    NSLog(@"块内%p",&ivar);
                };
                ivar += 1;
                NSLog(@"ivar1: %d",ivar);
                tmpBlock();
                NSLog(@"ivar2: %d",ivar);
                NSLog(@"tmpBlock %@",tmpBlock);

2019-12-06 14:23:05.294076+0800 LearningDemo[82616:6257418] 块外0x7ffeefbff4d8
2019-12-06 14:23:05.294589+0800 LearningDemo[82616:6257418] ivar1: 2
2019-12-06 14:23:05.294673+0800 LearningDemo[82616:6257418] 块内0x102c00a78
2019-12-06 14:23:05.294726+0800 LearningDemo[82616:6257418] ivar2: 3
2019-12-06 14:23:05.295064+0800 LearningDemo[82616:6257418] tmpBlock <__NSMallocBlock__: 0x102c009f0>
复制代码

将上面代码转换成底层实现源码:

struct __Block_byref_ivar_0 {
  void *__isa;  //对象特性
__Block_byref_ivar_0 *__forwarding;//栈上指向本身,若是结构体被拷贝到堆上指向堆上的拷贝的__Block_byref_ivar_0.
 int __flags;//状态标准位
 int __size;//大小
 int ivar;//原变量
};

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

                    (ivar->__forwarding->ivar) += 1;
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_3,&(ivar->__forwarding->ivar));
                }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->ivar, (void*)src->ivar, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->ivar, 8/*BLOCK_FIELD_IS_BYREF*/);}

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_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

                __attribute__((__blocks__(byref))) __Block_byref_ivar_0 ivar = {(void*)0,(__Block_byref_ivar_0 *)&ivar, 0, sizeof(__Block_byref_ivar_0), 1};
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_2,&(ivar.__forwarding->ivar));
                void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_ivar_0 *)&ivar, 570425344));
                (ivar.__forwarding->ivar) += 1;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_4,(ivar.__forwarding->ivar));
                ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_5,(ivar.__forwarding->ivar));
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_6,tmpBlock);
    }
    return 0;
}
复制代码

会发现多了一个 __Block_byref_ivar_0 结构体,这个结构体就是__block修饰的变量要转换的结构体, __main_block_impl_0捕获的就是这个结构体指针,Block的实现函数__main_block_func_0中经过__forwarding这个成员变量指向本身或堆上的拷贝的结构体,而后再访问成员变量中的原变量ivar来修改值. ivar打印的地址不一样是由于__Block_byref_ivar_0结构体被拷贝到了堆区,天然地址也不一样,至于为何会被拷贝到堆区,后面会讲.

接下来再看一下对象类型变量加__block前缀的代码及转换的源码:

__block NSArray *arr = @[@"1"];
        
           void (^tmpBlock)(void) = ^{
                arr = @[@"2"];
           };
        tmpBlock();
复制代码
struct __Block_byref_arr_0 {
  void *__isa;
__Block_byref_arr_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSArray *arr;
};

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

                (arr->__forwarding->arr) = ((NSArray *(*)(Class, SEL, ObjectType  _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(1U, (NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_aeef45_mi_1).arr, 1U);
           }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->arr, (void*)src->arr, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->arr, 8/*BLOCK_FIELD_IS_BYREF*/);}

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_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_arr_0 arr = {(void*)0,(__Block_byref_arr_0 *)&arr, 33554432, sizeof(__Block_byref_arr_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSArray *(*)(Class, SEL, ObjectType  _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(1U, (NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_aeef45_mi_0).arr, 1U)};

           void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_arr_0 *)&arr, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
    }
    return 0;
}
复制代码

会发现__Block_byref_arr_0结构体多了两个函数指针,它们分别指向__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131.再看上面的__block修饰的非对象类型和这个对象类型它们的__main_block_desc_0都有copy和dispose函数指针,它们都指向了__main_block_copy_0和__main_block_dispose_0函数.这些函数是干什么的?接下来让咱们一探究竟.

这里提一下,Block在使用的时候会分红栈块(_NSConcreteStackBlock)、堆块(_NSConcreteMallocBlock)、全局块(_NSConcreteGlobalBlock).下面的代码也会根据块的类型进行判断,这里提一下方便理解代码.具体介绍会放在第三节.

下面的代码是Block拷贝时底层执行的代码:

//ARC下给Block变量赋值的时候会调用
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//
id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}
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;
    }
}
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->copy)(result, aBlock); // do fixup
}
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock) {
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}
复制代码

经过上面的代码及注释咱们能够了解到了Block拷贝的基本流程,里面有一些枚举值,以下:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
复制代码

经过注释得知这些枚举值用于描述块对象的Block_layout-> flags的值.没错就是上面void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_ivar_0 *)&ivar, 570425344));中传的实参570425344.咱们来打印一下按位逻辑或的值NSLog(@"%d",1 << 25 | 1 << 29); LearningDemo[43493:8342154] 570425344 恰好是传的那个值,也就是BLOCK_HAS_COPY_DISPOSE| BLOCK_USE_STRET.也就是代码会继续走(*desc->copy)(result, aBlock);方法, 而后就调用到了咱们刚刚所提到的__main_block_copy_0.而__main_block_copy_0中的_Block_object_assign,它到底干了什么,下面是它源码:

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /******* id object = ...; [^{ object; } copy]; ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /******* void (^object)(void) = ...; [^{ object; } copy]; ********/

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /******* // copy the onstack __block container to the heap // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __block ... x; __weak __block ... x; [^{ x; } copy]; ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /******* // copy the actual field held in the __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly. __block id object; __block void (^object)(void); [^{ object; } copy]; ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /******* // copy the actual field held in the __block container // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __weak __block id object; __weak __block void (^object)(void); [^{ object; } copy]; ********/

        *dest = object;
        break;

      default:
        break;
    }
}
复制代码

根据上面拷贝逻辑可知,该函数传的参数destArg是拷贝到堆上的目标Block的成员变量, object是源Block的成员变量,也就是说该函数主要是针对捕获变量的操做.最后的flags也是个枚举,以下:

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ... 对象类型变量
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable 块变量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable __Block_byref_arr_0结构体
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers 弱引用变量
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines. 
};
复制代码

接下来看看这些枚举值的case中都作了什么.

BLOCK_FIELD_IS_OBJECT中执行了_Block_retain_object,它的实现就是下面的代码,因此它其实什么都没作.而后将object的地址赋值给拷贝后的block成员变量.

static void _Block_retain_object_default(const void *ptr __unused) { }
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
复制代码

BLOCK_FIELD_IS_BLOCK当块中捕获到了Block变量时的操做,又会去调用_Block_copy函数.

BLOCK_FIELD_IS_BYREF也就是__block修饰变量拷贝的操做,也就是__main_block_copy_0函数传的那个8/*BLOCK_FIELD_IS_BYREF*/,而后内部又调用了下面函数

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
复制代码

而其中的131则是128+3.

下面看看执行的_Block_byref_copy函数,源码以下:

// Runtime entry points for maintaining the sharing knowledge of byref data blocks.

// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack 👇 拷贝Block_byref结构体
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
 //****👆上面这段代码就是栈上的Block成员变量forwarding指向堆上的Block的过程****//

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {//👈初始化__Block_byref_arr_0的时候,传的33554432恰好是 1<<25打的值.
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
复制代码

上面的代码执行顺序就是 块拷贝->__main_block_copy_0 ->__Block_byref_id_object_copy_131

剩下的case都是指向源变量地址.

到此为止你应该已经发现一开始的例子__main_block_desc_0中没有copy和dispose函数指针的缘由就是它捕获的是非对象类型,而有这两个函数指针的是对象类型和__block修饰的变量,用于拷贝捕获的变量.另外,__block修饰的非对象类型是没有__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131的,由于__Block_byref做为对象是须要这些拷贝操做的,而里面的非对象类型变量不须要. 另外关于_Block_object_dispose的底层实现我就不在赘述了,和copy过程相似,看一下源码就懂了.

3、关于栈块、堆块、全局块

根据Block所在内存区域不一样分为栈块(_NSConcreteStackBlock)、堆块(_NSConcreteMallocBlock)、全局块(_NSConcreteGlobalBlock).它们在ARC和MRC下的表现是有差别的.

  • MRC 下的表现 堆块:为Block执行copy方法才会变成堆块.赋值到有copy、strong修饰符的属性变量也是堆块. 栈块:写在方法里的默认就是栈块. 全局块:全局变量区域定义的块.不赋值且捕获了自动变量的也是全局块.
  • ARC下的表现 堆块:块实现赋值到块自动变量上就会变成堆块.由于默认有__strong修饰符的缘故,被持有. 栈块:不赋值的块且捕获了自动变量的默认是栈块. 全局块:全局变量区域写的块.块里面使用的是全局变量、全局静态变量、静态变量、或者不使用任何变量的时候也是全局块.

了解当前的Block是那种类型的块以及如何会变成这种类型的块以后,再结合上面的关于Block捕获变量的底层实现和拷贝过程咱们就清楚的明白当前的块能不能修改变量,变量有没有执行拷贝了. #4、常见实战应用

一、适配器模式

用Block做为适配器来传值,能够下降代码分散程度,在数据种类不是不少的时候能够代替委托协议来使用.被适配者在Block的实现中执行操做.相对协议更加轻松的实现了适配器模式.

二、循环引用问题

通常咱们会用__weak 来修饰self变量来打破循环引用,由于__weak修饰的变量Block不会持有它,执行拷贝操做引用计数也不会增长,可是在Block的实现内记得用__strong再修饰一边self变量,防止外面的变量提早释放或者被置空致使访问错误.原理也很简单,由于Block的结构体会捕获self,加上__weak修饰符就能够不持有self变量来,也就不会形成循环引用.而__strong是加在Block的实现里的,也不会形成循环引用,又能够保证代码的安全访问.

欢迎你们关注个人我的博客: darkknightkazuma.github.io

相关文章
相关标签/搜索