Blocks实现原理讲解

全乎的Blocks讲解

一句话概括Blocks:<strong>带有自动变量(局部变量)的匿名函数</strong>

Blocks模式

Block语法

Block的书写形式:<strong>^ 返回类型 参数列表 表达式</strong>

^int (int count) {  
        return count + 1;
    } 

我们可以看到Block的语法和C语言函数相比有两点不同

  1. 没有函数名
  2. 带有“^”
    省略形式:
    1、返回类型。省略返回类型时,如果表达式中有return语句就使用该返回类型,如果表达式没有return语句就是用void类型。表达式中含有多个return语句时,所有return的返回类型必须相同。
    2、参数列表。如果使用参数,参数列表也可以忽略。
    综上,最简单的Block形式如下:
    ^{
        printf("Blocks\n");
    }

Blocks变量

(一)Block类型变量与一般的C语言变量完全相同,可作为一下用途使用:
1、自动变量
2、函数参数
3、静态变量
4、静态全局变量
5、全局变量
当在函数和返回值中使用Block类型变量是,记述方式极为复杂。此时,我们可以像使用函数指针类型那样,使用typedef来解决问题。

    typedef int (^blk_t)(int);
    void func(int (^blk)(int))//原来的记述方式
    void func(blk_t blk)  //现在的记述方式
      ·
    int (^func())//原来的记述方式
    blk_t func() //现在的记述方式

(二)Block中变量值得获取
Block表达式截获所使用的自动变量的值,即保存自动变量的瞬间值。
(三)__block说明符
默认情况下,执行Block语法时,截获的是自动变量的瞬间值,截获保存之后就不能改写这个值,如果尝试修改这个值,编译器会报编译错误。
如果想在Block语法的表达式中将值赋给Block语法外声明的自动变量,修改在该自动变量上附加__block说明符。

Blocks底层的实现

Block是“带有自动变量的匿名函数”,实际上Block是作为极普通的c语言源代码来处理的。我们先来看一下个最简单的block语法:

    int main()
    {
        void (^blk)(void) = ^{printf("Block\n");};
        blk();
        return 0;
    }

我们使用clang将objc代码转换为c++的源代码。说是c++,其实也仅是使用了struct结构,本质还是c代码。

//block变量结构体
 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;
    __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) {
            printf("block\n");
    }
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()
{
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,          &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
}

objc中短短的几行代码,转换为c++代码后,竟然增加了这么多。我们从转换后的代码中选取出最重要的这几行代码。下面我们就来分析一下转换的代码,看看block到底是怎么通过c++代码来实现的。
很简单的,第一眼我们就看到转换后的代码中有一行printf("block\n");,没错,这就是objc代码中的block块内容。发现原来的Block函数,转换成了一个c++函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            printf("block\n");
    }

函数的名字是根据Block语法所属的函数名和该Block语法在该函数出现的顺序值来给Block函数命名。
此外,我们发现转换的后函数有一个入参__cself,这个参数就相当于c++实例方法中指向实例自身的变量this或者objc实例方法中指向对象自身的变量self,即参数__cself为指向Block值得变量。我们先来看看这个参数的声明:

struct __main_block_impl_0 * __cself

__cself__main_block_impl_0结构体的指针,该结构声明如下:

//去除构造函数 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
};

__main_block_impl_0结构体有两个成员变量,一个__block_impl 结构体impl,一个 __main_block_desc_0结构体指针Desc。一个一个的来看,
先看一下 __block_impl 这个结构体

 struct __block_impl {
        void *isa;  //如同对象类型的Class isa,将Block作为Objc对象是,关于该对象类的信息放置于isa指向的对象中
        int Flags;  //某些标志
        int Reserved; //保留区域
        void *FuncPtr; //函数指针
}

我们来分一下这个结构体中的四个参数:
1、isa,我们首先要认识到Blocks在objc也是对象,在Objective-C中使用id这一类型来存储对象。

typedef struct objc_object {
    Class isa;
} *id;

objc_object是表示一个类的实例的结构体,id为objc_object结构体的指针类型。
该结构体只有一个字段,即指向 其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表及父类的方法列表中去寻找与消息对象的selector指向的方法。找到后即运行这个方法。当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。
我们再来看看Class:

typedef struct objc_class *Class;

Class为objc_class结构体的指针类型。objc_class结构体的定义如下:

struct objc_class {
    Class isa; //在Objective-c中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,指向metaClass(元类)
    ·
    ·
    Class super_class; //指向该类的父类,如果该类已经是最顶层的根类,则super_class为NULL
}

在这里我们顺便提一下元类的概念。上面介绍objc_class结构是,提到,所有的类自身也是一个对象,我们可以向这个对象发送消息。如:

NSArray *array = [NSArray array];

在这个例子中,+array消息发送给了NSArray类,而这个NAArray类也是一个对象。既然是对象,那么他也是一个Objc_object指针,他包含一个指向其类的一个isa指针。那么这些就有一个问题了,这个isa指针指向什么呢?为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念:<strong>meta-class是一个类对象的类</strong>.
当我们向一个对象发消息时,runtime会在这个对象所属的这个类的方法列表中查找相应方法,而向一个类发送消息是,会在这个类的meta-calss元类的方法列表中查找。
ps:meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同
实例-类-元类的关系如下图:


</br>
回到正文,__main_block_impl_0结构就相当于objc_object结构体,其内部的isa指针目前一般初始化为isa = &_NSConcreterStackBlock;,即该Block类的信息放在了_NSConcreterStackBlock中。
2、Flags 标志位,默认设为0
3、Reserved 保留区
4、FuncPtr 函数指针,将Block转换后的函数指针赋值给FuncPtr,供以后调用。

接下来我们来看一下这个__main_block_desc_0结构体,其声明如下:

static struct __main_block_desc_0 {
  size_t reserved;  //保留区域
  size_t Block_size; //Block的大小
};

说了这么多,我们来看一下objc是怎么把Block转换成c++函数调用的。
首先我们构造过程:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,          &__main_block_desc_0_DATA));

乍一看,这个函数比较复杂,转换较多,我们分开来看:

struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,           &__main_block_desc_0_DATA);
struct __mian_block_impl_0 *blk = &tmp;

整个过程就是将__mian_block_impl_0结构体类型的自我变量,即栈上生成的__mian_block_impl_0结构体实例的指针,赋值给__mian_block_impl_0结构体指针类型的变量blk。对应最初源代码:

void (^blk)(void) = ^{printf("Block\n");};

下面就来看看__mian_block_impl_0结构体实例的构造函数:

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)

第一个参数是由Block语法转换后的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。__main_block_desc_0的初始化如下:

static struct __main_block_desc_0 __main_block_desc_0_DATA = { 
        0, 
        sizeof(struct __main_block_impl_0) //__main_block_impl_0结构体实例的大小
    };

__mian_block_impl_0结构体实例具体是如何初始化的呢,我们将__mian_block_impl_0结构体中的成员变量展开,如下:

struct __mian_block_impl_0 {
    void *isa; 
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0 * Desc;
}

初始化如下:

isa = &_NSConcreterStackBlock; 
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

再来看一下调用过程:

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

去掉转换部分:

(*blk->FuncPtr)(blk)

就是简单地使用函数指针调用函数。Block语法转换的__main_block_func_0函数指针被赋值给成员变量FuncPtr。此外,我们也发现了__main_block_func_0函数的参数__cself指向Block值。

Block截获自动变量值

上一节我们讲解了Block语法的底层实现,这一节主要讲解如何截取自动变量的值,前面曾提到过,Block获取的是自动变量的瞬间值。和前面一样,我们还使用clang对源代码进行转换。
源代码如下:

int main()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt,val);};
    blk();
    return 0;
}

转换后的代码:

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;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
printf(fmt,val);}

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 dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

与上一节的代码作比较,我们注意到,在__main_block_impl_0结构体中增加两个变量const char *fmtint val,类型与自动变量的类型完全相同。而且,还发现Block语法表达式中没有使用的自动变量不会被追加。即Blocks的自动变量截获只针对Block中使用的自动变量。
接下来我们再看一下构造函数:

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val);

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

可以发现,执行Block语法时,使用自动变量fmt和val来初始化__main_block_impl_0结构体实例。
再来看一下执行函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
    printf(fmt,val);
}

再转换后的函数中,使用的是自动变量保存在__main_block_impl_0结构体实例中的值。由此也说明了,Block获取的是自动变量的瞬间值。

__block说明符

前面我们说到,Block获取变量时是保存自动变量的瞬间值,在Block内部如果想要改变自动变量的值,编译器会报编译错误。那么如何才能在Block内部实现修改保存自动变量的值呢,在没用使用__block说明符之前,我们可以根据C语言的特性来实现:
1、静态变量
2、静态全局变量
3、全局变量
我们来看看这段源代码:

int global_val = 1;
static int static_global_val = 2;
int main()
{
    static int static_val = 3;
    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;
    };
    return 0;
}

改源代码里面使用Block改写了静态变量static_val、静态全局变量static_global_val和全局变量global_val。转换后的代码如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
int global_val = 1;
static int static_global_val = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val; //静态变量static_val的指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

     global_val *= 1;
     static_global_val *= 2;
     (*static_val) *= 3;
    }

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()
{
 static int static_val = 3;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));  //静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数
    return 0;
}

我们看到,对于静态全局变量static_global_val和全局变量global_val,在转换后的函数中直接使用。但是对于静态变量static_val却进行了转换,使用静态变量static_val的指针对其进行访问。将静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数并保存。这里我们就要问了,为什么自动变量不保存它的指针呢。
在实际使用中,自动变量分配在栈上,在由Block语法生成的Block上,经常情况下Block会在超过其变量作用域的时刻执行,当变量作用域结束时,自动变量就废弃了,通过自动变量的指针去访问已废弃的自动变量,会发生错误。
</br>
Objective-C提供了 __block说明符来解决这个问题。 __block说明符用来指定Block中想变更的自动变量。且看下面的源代码:

int main()
{
    __block int val = 3;  //__block修改val自动变量
    void (^blk)(void) = ^{
        val *= 1;   //修改自动变量的值
    };
    return 0;
}

转换后的代码如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
·
·
//新增的结构体
struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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()
{
 __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    return 0;
}

可以看到,仅仅是增加了一个 __block 说明符,源代码就急剧增加。
__block 变量val变为了一个结构体实例

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

该结构体的初始化过程如下:

__Block_byref_val_0 val = {
    0, //__isa
    &val, //__forwarding指向自己
    0,  //__flags = 0
    sizeof(__Block_byref_val_0), //__size 为自身__Block_byref_val_0的大小
    10,  //自动变量的值
}

那么如何给__block变量赋值呢?

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

     (val->__forwarding->val) *= 1;
}

通过__main_block_impl_0结构体中__Block_byref_val_0结构体实例的指针找到自动变量的结构体实例,然后通过自动变量结构体的成员变量__forwarding__forwarding持有指向该实例自身的指针)访问成员变量val(原变量).


我们为什么是通过成员变量__forwarding而不是直接去访问结构体中我们需要修改的变量呢? __forwarding被设计出来的原因又是什么呢?

Block存储域

我们知道,Block语法块转换为Block结构体类型(函数)的自动变量,__block变量转换为__block变量的结构体类型的自动变量。而这两种结构体类型的自动变量,存在于栈上。
通过前面的介绍,我们知道Block也是个Objective-C对象,将Block当作Objective-C对象来看待时,该Block的类为_NSConcreteStackBlock,之前我们提过Block类中有一个isa指针,在初始化时指向_NSConcreteStackBlock,其实除了这个之外还有两个类似的类:

  • __NSConcreteStackBlock
  • __NSConcreteGlobalBlock
  • __NSConcreteMallocBlock

通过名字我们就可以知道__NSConcreteStackBlock表示类的对象Block设置在栈上。
__NSConcreteGlobalBlock 表示类的对象Block设置在程序的数据区(.data)中
__NSConcreteMallocBlock 表示类的对象Block设置由malloc函数分配的内存块(堆)中
但是到目前为止,Block使用的都是__NSConcreteStackBlock 类,都是设置在栈上。但是也不全是这样,在记述全局变量的地方使用Block语法时,生成的Block为__NSConcreteGlobalBlock。此外,如果Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区(使用__NSConcreteGlobalBlock类)。
那么什么时候使用__NSConcreteMallocBlock这个类呢。这里我们就来顺便解决一下上面遗留的问题:

  • Block超出变量作用域可存在的原因
  • __block变量用结构体成员变量__forwarding存在的原因。

我们知道,当一个变量设置在全局区时,从变量的作用于外也可以通过指针安全的使用。但是当设置在栈上时,当作用域结束时,这个Block就被废弃了。___block变量也是同样的情况。
Blocks提供了将Block和__block 变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。 当Block从栈上复制到堆上时,Block将结构体实例的isa设置成__NSConcreteMallocBlock
我们再来看看__forwarding成员变量,我们知道,有时候__block变量配置在堆上,也可以访问栈上的__block变量。在这种情况下,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__block变量还是从堆上的__block变量都能正确的访问。
那么什么时候栈上的Block会复制到堆上呢?

  • 调用Block的copy实例方法
  • Block作为函数返回值返回时
  • 将Block赋值为附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或者Grand Central Dispatch的API中传递Block时。

__block变量存储域

上一节介绍了Block的存储,那么__block变量又是如何处理的呢,当Block从栈复制到堆时,使用的所有__block变量也全部被从栈复制到堆。此时,Block持有__block变量。
在多个Block中使用__block变量时,因为最先会将所有的Block配置在栈上,所以__block变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数。
如果配置在堆上的Block被废弃,那么它所使用的__block变量也就被废弃。
之前我们提到过一句话:“不管__block变量配置在栈上还是在堆上,都能够访问该变量”。这也就是__forwarding成员变量存在的原因。
这里引用一下上面出现过的代码:

 (val->__forwarding->val) *= 1;

在使用__block变量时,通过__forwarding成员变量访问val成员(原变量)。当__block变量从栈上复制到堆上时,会将__forwarding成员变量的值替换为复制目标堆上的__block变量用结构体实例的地址。
ps: 栈上和堆上同时保持__block变量实例,但是访问和修改值则是在堆上。看张图吧:

截获Block对象

我们看一下源代码:

blk_t blk;
int main()
{
    id array = [[NSMutableArray alloc] init];
    blk = [^(id obj) {
        [array addObject:obj];
    } copy];
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
}

代码中使用copy函数将Block从栈上复制到堆上,而使用的array这个自动变量也一并复制到堆上。(当ARC有效是,id类型以及对象类型变量必定附加所有权标识符,缺省为附有__strong 修饰符的变量)。Objective-C的运行时库能够准确把握Block从栈上复制到堆上以及Block被废弃的时机,能够在恰当的时机进行初始化和废弃。为此需要使用在__main_block_desc_0结构体中增加的成员变量copy和dispose,即作为指针赋值给该成员变量的__main_block_copy_0__main_block_dispose_0 函数。我们来依次看看这两个函数的具体实现:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

_Block_object_assign 函数调用,相当于retain实例方法的函数。将对象赋值在对象类型的结构体成员变量中。有retain方法,肯定有release方法。__main_block_dispose_0函数调用__main_block_dispose_0函数释放赋值在Block中的结构体成员变量arrar中的对象。

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

__main_block_dispose_0 函数相当于release实例方法函数,释放赋值在对象类型的结构体成员变量中的对象。
有了这种构造,通过使用附有__strong 修饰符的自动变量,Block中截获的对象就能够超出其变量作用域而存在。
我们回头看看,其实这两个函数在使用 __block 变量时已经出现过了:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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};

和Block结构体的部分基本相同,不同支出在于__main_block_copy_0__main_block_dispose_0函数最后的参数不一样。BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF区分对象类型是对象还是__block变量。
由此可知。Block中使用的赋值为附有__strong修饰符的自动变量的对象和复制到堆上的__block变量由于被堆上的Block所持有,因而可超出其变量作用域而存在。

Block循环引用

如果在Block中使用附有__strong修饰符的自动变量,那么当Block从栈复制到堆上时,该对象为Block所持有,很容易引起循环引用。这种情况经常出现在一个类型对象中,有一个Block类型的成员变量,而这个Block中又引用了self变量,这就会造成一个循环引用。Block持有self,self持有Block。

- (id) init {
    self = [super init];
    blk = ^{NSLog(@"self = %@",self);
    return self;
}

若由于Block引发了循环引用,根据Block的用途选择使用__block变量、__weak修饰符或__unsafe_unretained修饰符来避免循环引用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容