【iOS】Blocks

文章目录


前言

先前看了小蓝书上的block,发现不甚了解,现重新学习block

一、什么是Blocks

Blocks 是 C 语 言 的 扩 充 功 能 。 可 以 用 一 句 话 来 表 示 Blocks 的 扩 充 功 能 : 带 有 自 动 变 量 (局 部变量)的匿名函数。

我们把这句话拆分开来理解

自动变量 :

也就是局部变量
匿名函数

即不带名称的函数

二、Blocks模式

1.Block语法

因为我们知道Block是带有自动变量匿名函数

其与C函数只有两点不同

  1. 没有函数名
  2. 带有^

^记号 叫做插入记号,这样可以让我们在项目中方便查找

接下来我们将一下Block的范式

例子:

bash 复制代码
^int (int count) {
	return count + 1;
}

同时Block还可以进行省略

如果参数列表没有参数或是返回值类型为void,那么这两个地方都可以被省略

例子:

bash 复制代码
^void (void) {
	printf("1");
}

可以被省略为

bash 复制代码
^{
	printf("1");
}

2.Block类型变量

上一部分我们讲到了Block语法,我们这一部分将一下Block类型变量

在定义C 语言函数时,就可以将所定义函数的地址赋值给函数指针 类型变量中。

同样的,我们可以将Block语法赋值给声明为Block类型的变量中

Block类型变量实例如下:

bash 复制代码
int (^blk) (int)

声明Block 类型变量仅仅是将声明函数指针类型 变量的 "*" 变为"^"。

下面我们将Block赋值给Block类型变量

bash 复制代码
int (^blk) (int) = ^int (int count) {
	return count + 1;
}

同样Block类型变量也可以作为正常变量进行使用,如进行参数传递

可以用 typedef简化他

这样^blk_t就变成了一个block数据类型

我们可以像使用函数一样使用block

3.截获自动变量值

我们先来看一段代码

bash 复制代码
    int val = 111;
    char *fmt = "val = %d\n";
    void (^blk)(void) = ^ {
        printf(fmt, val);
    };
    blk();
    val = 222;
    fmt = "val2 = %d";
    blk();

可以看到我们两次调用blk,使用的自动变量都是第一次保存自动变量的瞬间值。这里的原因其实是因为在第一次执行blk时,自动变量被加入到了blk的结构体中

这里的具体实现在后面的源代码会进行讲解

4.__block说明符

我们在上面截获自动变量时block保存了执行时的自动变量的瞬间值,保存后就无法改写该值

但是当我们使用__block修饰时就可以进行改写

5.截获的自动变量

我们截获自动变量后不能对变量进行赋值,但是可以对变量进行使用

如上会产生错误

但是使用Array就不会发生错误

如果我们要修改截获的自动变量就要加上__block修饰符

另外不能再block中使用c语言数组,因为截获自动变量的方法并没有实现对c语言的捕获

三、Blocks的实现

1.Block的实质

要深入了解Block的实质,我们就必须看他的源码

我们在终端输入命令来查看其源码

bash 复制代码
    int main()
    {
        void (^blk)(void) = ^{printf("Block\n");};
        blk();
        return 0;
    }

上面代码转换为源码就变为了

bash 复制代码
//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;
}

我们一步步来分析我们的代码:

首先:

被转换为了

bash 复制代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            printf("block\n");
    }

我们逐步分析:

__main_block_func_0 是函数的名称。这个名称是由编译器自动生成的,与 Block 的创建有关。
struct __main_block_impl_0 *__cself 是函数的参数。这个参数是一个指向 __main_block_impl_0 结构体的指针。__main_block_impl_0Block 对象的实现结构体 ,包含了 Block 的一些元数据和状态信息。
__cself 是一个惯用的名称,表示 "current self",即当前 Block 实例。

__main_block_impl_0

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

bash 复制代码
//去除构造函数 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
};

我们这里理解一下Block对象的实现结构体:他是用于表示Block对象的一种数据结构,封装了Block的元数据、捕获变量以及执行代码等信息

__block_impl

首先看他的第一个成员变量__block_impl,他也是一个结构体,他封装了一些Block的核心元素以及信息

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

我们来解释一下这些参数:
isa:我们知道Block其实质上也是一个对象,我们会在运行时使用isa指针指向对象所属的类别,但是对于Block,其会根据Block对象内存的分配位置分别指向不同位置:
栈(_NSConcreteStackBlock)与堆(_NSConcreteMallocBlock)

Flags:这是一组标志位,用于存储Block对象的一些特性和状态信息。例如,有一个标志位表示Block对象是否被复制到堆上,另一个标志位表示Block是否使用了C++捕获变量。
Reserved:这是一个保留区域,目前并没有被使用。可能是为了对齐或者将来的扩展而预留的空间。
void *FuncPtr:是一个函数指针,指向Block的内部实现代码,当Block执行时,会调用这个函数执行block内部的代码

__main_block_desc_0

我们再来看一下__main_block_desc_0这个结构体

其声明如下:

bash 复制代码
static struct __main_block_desc_0 {
  size_t reserved;  //保留区域
  size_t Block_size; //Block的大小
};

下面我们分析一下这段代码的源代码:

bash 复制代码
void (^blk)(void) = ^{printf("Block\n");};
bash 复制代码
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,          &__main_block_desc_0_DATA));

总结下来就是通过生成一个Block对象的结构体实例,将其地址赋值给__main_block_impl_0结构体的指针类型变量blk

分开来看就清晰多了

bash 复制代码
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;

分析一下block实例的构造函数:__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)

__main_block_impl_0((void*)__main_block_func_0,&__main_block_desc_0_DATA);:

a. __main_block_impl_0 是一个结构体,这里调用它的构造函数(或类似的初始化函数),并传入两个参数:

b. 第一个参数 ((void *)__main_block_func_0) 是一个类型转换,将 __main_block_func_0 的地址转换为 void *。__main_block_func_0 是实现 Block 功能的函数的地址。

c. 第二个参数 &__main_block_desc_0_DATA 是指向另一个结构体(描述符),__main_block_desc_0_DATA,的地址。这个描述符通常包含有关 Block 的一些元数据,如其大小等。

Block对象的实现结构体初始化

我们来看一下__main_block_impl_0结构体实例是如何初始化得到的

bash 复制代码
isa = &_NSConcreterStackBlock; 
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

我们再来看一下函数中的这段代码:

bash 复制代码
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

我们在void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));中已经生成了一个Block的实现结构体的实例,并将其地址赋给指针变量blk

下一步我们就需要实现Block的内部代码,前面我们说到了FuncPtr是一个函数指针,指向Block的内部实现代码 ,接下来我们就可以通过调用FuncPtr来实现内部代码

去除转换部分就是

bash 复制代码
(*blk->FuncPtr)(blk)

这段代码就是简单的函数指针调用函数

这里有一个问题就是为什么Block的实质为OC对象,后面学到会进行补充

2.截获自动变量值

本部分主要讲解源代码如何截获自动变量值

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

源代码如下:

bash 复制代码
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;
}

可以看到在Block语法表达式中使用的自动变量被追加到了Block的实现结构体__main_block_impl_0中, 同时声明的成员变量类型与自动变量类型完全相同

这里需要注意:
Block语法表达式中没有使用的自动变量不会被追加,Block自动变量的截获只针对Block中使用的自动变量

下面再来看一下Block结构体实例的构造函数与先前有什么不同

bash 复制代码
__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实例的内部代码实现: ^{printf(fmt,val);};

bash 复制代码
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);
}

可以看到在Block的内部函数实现代码中使用的变量是Block对象后的实现结构体的成员变量,也就是__cself->fmt与__cself->val

这也说明这些变量在执行Block内部函数表达式前就被定义,因此源代码表达式不用改动就可以使用自动变量

同时这也说明了Block获取的是自动变量的瞬间值

3.__block说明符

我们在先前说过Block截获自动变量后无法再修改自动变量,那么如何才能再Block内部实现修改保存的自动变量的值呢?

在__block还没出现之前可以通过c语言的一些特性来对自动变量进行修改:
1、静态变量
2、静态全局变量
3、全局变量

下面来看这段代码:

bash 复制代码
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;
}

源代码:

bash 复制代码
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的指针被加到了Block对象的实现结构体中。在使用static_val时需要通过其指针对其进行访问。

这样一来我们就可以实现在Block实现代码内部修改变量

但是这样就引出了一个新的问题:

为什么自动变量不和静态变量一样通过指针作为成员变量保存在__main_block_impl_0中,这样不也可以对自动变量进行修改了吗?

在Block中我们可以存有超出其变量作用域的自动变量,但是我们常常在超出变量作用域的时候使用Block,因为超出变量作用域时变量已经被废弃,如果我们再次通过指针访问变量那么将会发生错误

解决Block中不能保存值的第二个方法是使用__block说明符

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

源代码:

bash 复制代码
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变成了一个结构体

bash 复制代码
struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

可以看到初始化如下

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

再来看一下源代码中是如何给变量进行修改的

这个过程更加复杂,__main_block_impl_0结构体持有了指向__block变量的__Block_byref_val_0的结构体实例指针

同时__Block_byref_val_0结构体中的成员变量__forwarding持有指向该实例自身的指针,然后我们可以通过__forwarding__forwarding持有指向该实例自身的指针)来访问原成员变量val

为什么需要__forwarding呢,后面的部分会讲到

另外不将__Block_byref_val_0结构体添加到__main_block_impl_0是为了在多个Block中都可以使用__block变量

4.Block存储域

通过我们前面的源码可以得知,Block转换为Block结构体类型变量__main_block_impl_0,__block转换为block变量的结构体类型变量__Block_byref_val_0

所谓结构体类型的自动变量,就是栈上生成的结构体的实例

另外我们知道Block也是OC对象,同时我们知道他也有isa指针,他的isa指针表明其当前所存储的区域

然后我们来梳理上节遗留的问题

  1. Block超出变量作用域可存在的原因
  2. block变量用结构体成员变量_ _ forwarding存在的原因

当Block超出其变量作用域,该Block会被废弃,配置在栈上的__block

也会被废弃

Blocks提供讲Block 和_ _block 变量从栈上复制到堆上的方法来解决这个问题,这 样 即 使Block语 法 记 述 的 变 量 作 用 域 结 束 , 堆 上的Block还可以继续存在

然后堆上的Block的isa指针就会指向_NSConcreteMallocBlock类

__block变量中使用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上都可以被正确访问。这也就是__forwarding成员变量存在的原因。

这里引用一下上面出现过的代码:

bash 复制代码
 (val->__forwarding->val) *= 1;

我们在上面讲过在使用__block变量时,我们可以通过__forwarding访问原成员变量val

当block从栈上复制到堆上的时候,__forwarding的指向就由val变为了堆上的block结构体实例。

ps: 栈上和堆上同时保持__block变量实例,但是访问和修改值则是在堆上

我们这里分析一下Blocks提供的复制方法究竟是什么

在ARC有效的时候,编译器会自行判断并自动生成将Block从栈上复制到堆上的代码

该源代码返回配置在栈上的Block,当函数返回时变量作用域结束,而后栈上的Block被废弃。但ARC会自动帮我们处理这个问题

在ARC有效时,blk_t tmp实际上带有__strong修饰符

通过objc_retainBlock可知函数实际上是Block_copy

在这之间发生的情况书上讲解的很清楚

将Block作为函数值返回时,编译器就会自动生成复制到堆上的到吗

在大多数情况下编译器会自行判断,当然也有一些情况需要我们手动复制:

  1. 向方法或者函数的参数 传递Block时。
    但是在方法或者函数里面适当的复制了传递的参数时,那么就不需要手动复制
  2. GCD的API不需要
  3. cocoa框架方法且方法名包含usingBlock

同样Block类型变量也可以使用copy方法进行复制

bash 复制代码
blkCopy = [blk copy];

5.__block变量存储域

这一部分我们讲一下当__block变量从栈上被复制到堆的时候其会受什么影响

当Block被从栈复制到堆上时,__block变量也会一起被复制到堆上并且被Block持有

如果多个Block使用同一个__block变量,那么复制到堆上的Block都会持有__block对象

如果堆上的Block被废弃,那么其所使用的__block变量也会被释放

由此我们可以知道__block的思考方式与OC引用计数管理完全相同

同时通过Block从栈上复制到堆上之后,原来的栈上的__block变量的__forwarding指针从指向自身变味指向堆上的__block结构体,由此不管__block变量配置在栈上还是堆上都可以顺利访问同一个__block变量

6.截获对象

首先来看一段源代码

bash 复制代码
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]);
}

这段输出意味着array超出其变量作用域而存在

同时通过解析后的源码可知Araay被夹到了Block的实现结构体中

同时OC为了准确把握Block从栈上复制到堆上以及堆上的Block的废弃的时机 ,在Block结构体中含有__strong与__weak修饰符的变量也会被恰当的初始化以及废弃。为此在__main_block_desc_0结构体中增加了成员变量copy与dispose作为指针赋值给函数__main_block_copy_0以及 __main_block_dispose_0

bash 复制代码
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*/);}
bash 复制代码
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

同时我们了解一下调用这两个函数的时机

那么什么时候栈上的block会复制到堆上呢

Block 被作为函数返回值时

如果函数返回一个栈上分配的 Block,那么在函数返回后,栈帧会被销毁,Block 就会失效。为了避免这种情况,编译器会自动将返回的 Block 复制到堆上。

bash 复制代码
int (^makeIncrementer)(int)) {
    int value = 0;
    int (^incrementer)(void) = ^{
        value++;
        return value;
    };
    return incrementer; // 编译器会自动将 incrementer 复制到堆上
}

int (^increment)(void) = makeIncrementer(0);
NSLog(@"%d", increment()); // 输出 1
NSLog(@"%d", increment()); // 输出 2

Block 被赋值给 __block 修饰的变量时

如果你将一个栈上的 Block 赋值给一个 __block 修饰的变量,编译器会自动将这个 Block 复制到堆上。__block 变量用于存储指向堆上 Block 的指针。

bash 复制代码
__block int (^blockVar)(void);

int value = 42;
blockVar = ^{
    return value; // 编译器会自动将该 Block 复制到堆上
};

NSLog(@"%d", blockVar()); // 输出 42

调用 Block_copy() 函数时

你可以手动调用 Block_copy() 函数将栈上的 Block 复制到堆上。这通常用于确保 Block 在超出其定义作用域后仍然有效。

bash 复制代码
int (^blockObj)(void) = ^{
    return 100;
};

blockObj = Block_copy(blockObj); // 手动将 blockObj 复制到堆上
NSLog(@"%d", blockObj()); // 输出 100

Block 被 GCD API 持有时

如果你将一个 Block 传递给 Grand Central Dispatch (GCD) API (如 dispatch_async),GCD 会自动将该 Block 复制到堆上,以确保在异步执行期间 Block 是有效的。

bash 复制代码
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
int value = 42;
dispatch_async(queue, ^{
    NSLog(@"%d", value); // GCD 会自动将该 Block 复制到堆上
});

Block 访问了 __block 修饰的变量时

如果 Block 内部访问了 __block 修饰的变量,编译器会自动将该 Block 复制到堆上,以确保变量在 Block 执行时是有效的。

bash 复制代码
__block int value = 42;

int (^blockObj)(void) = ^{
    value = 100; // 访问了 __block 变量,编译器会自动将该 Block 复制到堆上
    return value;
};

NSLog(@"%d", blockObj()); // 输出 100

另外需要知道只有调用_ Block_copy函数才能持有截获的附有_ _strong修饰符的对象类型的自动变量值

bash 复制代码
- (void)myMethod {
    __strong NSString *str = @"Hello";
    void (^blockObj)(void) = ^{
        NSLog(@"%@", str); // 默认捕获str的值复制
    };
    
    blockObj(); // 输出"Hello"
    
    // 如果不调用_Block_copy,str在这里被释放
    
    blockObj = _Block_copy(blockObj); // 将Block复制到堆上,并持有str的强引用
    
    // 现在无论str何时被释放,blockObj都能正确输出"Hello"
}

7.__block变量与对象

我们知道在ARC中会给id类型变量自动加上__strong修饰符,只有使用__strong修饰符的变量才会在block从栈复制到堆时使用_Block_object_assign来持有__block变量。如果使用__weak修饰符就当作用与结束时__block变量也会自动被释放

由此我们可以知道只有自动变量用__strong进行修饰时才会被block持有,且不随作用域结束而销毁

同时在blk被定义的时候blk就已经捕获了自动变量,而不是在调用blk时才进行捕获

bash 复制代码
    blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));

8.Block循环引用

同时Block也会有循环引用的问题,这里就需要用__weak,__block,__unsafe_unretained修饰符来避免循环引用。

这一部分后面会专门进行讲解

相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
SoraLuna4 小时前
「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现
macos·ui·harmonyos
追风林4 小时前
mac 本地docker-mysql主从复制部署
mysql·macos·docker
yqcoder4 小时前
mac 安装 nodemon
macos
一ge科研小菜鸡4 小时前
macOS开发环境配置与应用开发(详细讲解)
macos
hairenjing11234 小时前
使用 Mac 数据恢复从 iPhoto 图库中恢复照片
windows·stm32·嵌入式硬件·macos·word