【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修饰符来避免循环引用。

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

相关推荐
用户097 小时前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan7 小时前
iOS26适配指南之UIColor
ios·swift
权咚1 天前
阿权的开发经验小集
git·ios·xcode
用户091 天前
TipKit与CloudKit同步完全指南
ios·swift
小溪彼岸1 天前
macOS自带截图命令ScreenCapture
macos
法的空间1 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
侃侃_天下1 天前
最终的信号类
开发语言·c++·算法
echoarts1 天前
Rayon Rust中的数据并行库入门教程
开发语言·其他·算法·rust
2501_915918411 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
Aomnitrix1 天前
知识管理新范式——cpolar+Wiki.js打造企业级分布式知识库
开发语言·javascript·分布式