【iOS】Blocks学习

Blocks学习

Blocks概要

Blocks是C语言的扩充功能,简单来说:带有自动变量(局部变量)的匿名函数(不带有名称的函数)。

Blocks模式

Blocks语法

我们先来了解一下Blocks的BN范式:

这里先给出一个标准的Blocks语法的格式:

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

Blocks语法中,可以省略好几个项目,首先可以省略返回值类型:

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

当我们省略返回值类型的时候,如果表达式中有return语句即使用return语句的返回值类型,如果没有就使用void类型。当有多个return的时候,所有return的返回值必须相同

当我们不使用参数的时候,还可以省略参数列表,如下所示:

c 复制代码
^ {
	return count + 1;
}

Blocks类型变量

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

c 复制代码
int func(int count) {
	return count + 1;
}

int (*funcptr)(int) = &func;

同样的,在Block语法下,可将Blocks语法赋值给声明为Blocks类型的变量中。即源代码中一旦使用Blocks语法就相当于生成了可赋值给Blocks类型的变量的"值"。

下面举一个声明Blocks类型变量的示例:

c 复制代码
int (^blk((int);

这个Blocks类型的变量和一般C语言变量完全相同,可以作为以下用途使用:

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

这里给出一个使用Blocks语法将Blocks赋值为Blocks类型变量:

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

由"^"开始的Block被赋值给变量blk中。因为与通常的变量相同,所以也可以由Block类型变量向Block类型变量赋值:

c 复制代码
int (^blk)(int) = blk;
int (^blk2)(int);
blk2 = blk1;

在函数参数中使用Block类型变量可以向函数传递Block

c 复制代码
void (^func()(int)) {
	return count + 1;
}

在函数返回值中指定Block类型,可以将Block作为函数的返回值返回。

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

在函数参数和返回值中使用Block类型变量的时候,记述的方式过于复杂,这个时候我们可以使用typedef来解决该问题:

c 复制代码
typedef int (^blk_t)(int);

这样让我们的代码更加直观,更方便我们去理解这种类似的代码

截获自动变量值

在上述讲解中,我们已经明白了什么叫做匿名函数,而"带有自动变量值"是什么意思呢?在Blocks中表现为"截取自动变量值",下面举例说明:

c 复制代码
	int dmy = 256;
    int val = 250;
    const char* fmt = "Hyt is %d\n";
    void (^blk)(void) = ^{
        printf(fmt, val);
    };
    val = 2;
    fmt = "val = %d\n";
    blk();
    return 0;

下面我们看一下这段代码的执行结果:

那么为什么会是这样的结果呢?这是由于该Block语法执行的时候,字符串指针"Hyt is %d\n"被赋值到自动变量fmt中,int值250被赋值到自动变量val中,因此这些值被保存,从而在执行时使用。

这就是自动变量值的截获。

__block说明符

实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写该值。如果想在Block语法的表达式中将值赋给Block语法外声明的自动变量,需要在该自动变量上附加上__block说明符:

c 复制代码
	__block int val = 0;
    void (^blk)(void) = ^{
        val = 1;
    };
    blk();
    printf("val = %d\n", val);

这是我们将该变量成为__block变量

截获的自动变量

如果将值赋值给Block中截获的自动变量,就会产生变异错误。那么在截获OC 对象,调用变更该对象的方法也会产生编译错误吗?

这样是没有问题的,而向截获的变量array赋值则会产生编译错误。这种情况下,我们应该给截获的自动变量附加上__block说明符:

注意的是,在使用C语言数组的时候必须小心使用指针,在block中不能使用字符数组,这是由于截获自动变量的方法并没有对C语言数组的截获。

Blocks的实现

Blocks的实质

这不过是概念上的问题,在实际编译时无法转换成我们能够理解的源代码,但 clang (LLVM编译器)具有转换为我们可读源代码的功能。通过-rewrite-objc选项就能将含有 Block 语法的源代码变换为C++的源代码。说是C++,其实也仅是使用了struct结构,其本质是C语言源代码。

这里我们将Block语法:

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

该源代码通过clang可变换为以下形式:

c 复制代码
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;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

下面我们将这段代码分步来理解:

首先来看最初源代码中的Block语法:

c 复制代码
^{printf("Block\n");};

在转化后的源代码中也有相同的表达式:

c 复制代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block\n");
    }

这里我们可以看出,通过Block使用的匿名函数实际上被作为简单的C语言函数来处理。同时,我们可以通过Block语法所属的函数名可以看出:struct __main_block_impl_0 *__cself中的main表示Block语法所属的函数名为main,顺序值为0(在此处)来给劲clang变换的函数命名。

这里的参数_cself相当于OC中的self , 即指向Block值的变量。

下面来看一下struct __main_block_impl结构体,该结构体声明如下:

c 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
}

再来看一下_block-impl结构题的声明:

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

第二个成员变量为Desc指针,下面为_main_block_desc_0结构体的声明:

c 复制代码
static struct __main_block_desc_0 {
  unsigned long reserved;
  unsigned long Block_size;
}

下面再来看看初始化含有这些结构体的__main_block_impl_0结构体的构造函数:

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

下面来看看该构造函数的调用:

c 复制代码
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//对应的最初源代码
void (^blk)(void) = ^{ printf("Block\n"); };

总结来说,这段代码将栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型的变量blk。

__main_block_impl_0结构体实例构造参数:_main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));中,第一个参数为由Block语法转化的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。下面为__main_block_desc_0结构体实例初始化部分代码:

c 复制代码
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

该源代码使用Block,进行初始化,下面展示__main_block_impl_0结构体的构造函数:

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

这时候我们来看一下blk()转换源代码为:

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

去掉转换部分:

c 复制代码
((__block_impl *)blk);

这就是简单的时候函数指针调用函数。

截获自动变量值

这里我们先将截获自动变量值的源代码通过clang进行转换:

c 复制代码
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 = 250;
    const char* fmt = "Hyt is %d\n";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    val = 2;
    fmt = "val = %d\n";
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

这里不难看出,block语法中使用的自动变量被作为成员变量追加到__main_block_impl_0结构体中:

c 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  }

注意:block语法表达时中没有使用的自动变量不会被追加,Blocks的自动变量截获只针对Block中使用的自动变量。

下面来看看那初始化该结构体实例的构造函数的差异:

c 复制代码
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {

在初始化结构体实例的时候,根据传递给构造函数的参数对由自动变量追加的成员变量进行初始化。以下通过构造函数调用确认其参数:

c 复制代码
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

在使用Block匿名函数的实现,最初源代码的Block语法如下所示:

c 复制代码
^{printf(fmt, val);};

将其转换为以下函数:

c 复制代码
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语法表达时所使用的自动变量值被保存到Block得结构体实例中。说明了获取的是瞬间值。

__block说明符

这里我们也将上文中使用__block说明符的源代码进行转化:

c 复制代码
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(void) {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    printf("val = %d", (val.__forwarding->val));
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我们可以发现代码量急速的增加了,那么__block int val = 10这段代码是如何转化过去的呢?这里来看看:

c 复制代码
__Block_byref_val_0 val = {
	(void*)0,
	(__Block_byref_val_0 *)&val, 
	0, 
	sizeof(__Block_byref_val_0), 
	10
};

这里我们可以发现__block变量也变成了一个__Block_byref_val_0的结构体类型的自动变量,该变量的初始化为10,并且这个值也出现在结构体实例的初始化中,意味着该结构体持有相当于原自动变量的成员变量。

该结构体的声明如下:

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

不难看出,结构体最后的val相当于原自动变量的成员变量,下面来看看给__block变量赋值(^{val = 1;})的代码:

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

这段代码相比于之前在Block中向静态变量赋值更为复杂。Block的__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。

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

__block变量的__Block_byref_val_0结构体并不在Block用__main_block_impl_0结构体中,这样是为了在多个Block中使用__block变量。例如:

这样我们可以看出,两个Block都使用了__Block_byref_val_0结构体实例val指针,这样的话,既可以在多个Block中使用同一个__block变量,也可以在一个Block中使用多个__block变量。

Block存储域

通过我们前面的源码可以得知,Block转换为Block结构体类型变量__main_block_impl_0,__block转换为block变量的结构体类型变量__Block_byref_val_0所谓结构体类型的自动变量,就是栈上生成的结构体的实例。

通过之前的说明可知Block也是OC对象,将Block当作OC对象来看的时候,该Block的类为_NSConcreteStackBlock。虽然该类并没有出现在已变换的源代码中,但是有很多类似的类:

c 复制代码
_ NSConcreteStackBlock(分布在栈上)
_NSConcreteGlobalBlock(分布在程序的数据区的位置)
_NSConcreteMallocBlock(分布在堆的位置)

下面图片说明:

在前期出现的Block例子中均是_NSConcreteStackBlock类,并且都设置在栈上。但是实际上并不都是这样,在记述全局变量的地方使用Block语法时,生成的Block为_NSConcreteGlobalBlock类对象。

那么将Block配置在堆上的_NSConcreteMallocBlock类在何时使用呢?这是两个问题的答案:

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


Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题,还配置栈上的Block复制到堆上,这样的话,哪怕Block语法记述的变量作用域结束,堆上的Block还可以继续存在。


_block变量存储域

使用__block变量的Block从栈上复制到堆上,__block变量也会收到影响,如图:

在一个Block中使用__block变量的时候:

在多个Block中使用__block变量的时候,因为最先会将所有的Block配置在栈上,所以__block变量的配置也会在栈上。在任何一个Block从栈上复制到堆上的时候,__block变量也会一并从栈上复制到堆并被该Block所持有,当剩下的Block从栈上复制到堆上,被复制的Block持有__block变量,并增加__block变量的引用计数:

如果配置在堆上的Block被废弃,那么他使用的__block变量也就被释放了;

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

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

截获对象

我们先来看看这段OC代码:

objectivec 复制代码
int main(int argc, const char * argv[]) {
    NSMutableArray* array = [NSMutableArray array];
        void (^blk)(id object) = [^(id object) {
            [array addObject:object];
            NSLog(@"%ld", array.count);
        } copy];
    blk([[NSMutableArray alloc] init]);
    blk([[NSMutableArray alloc] init]);
    blk([[NSMutableArray alloc] init]);
    return 0;
}

运行结果

这个结果说明赋值给变量array的NSMutableArray类的对象在该源代码最后Block的执行部分超出其变量作用域而存在,将其转换后的源代码为:

objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id object) {
  NSMutableArray *array = __cself->array; // bound by copy

            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)object);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_07_67glw_2n3csgg41v0tztm7w00000gn_T_main_07e131_mi_0, ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
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*/);}

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

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[]) {
    NSMutableArray* array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
        void (*blk)(id object) = (void (*)(id))((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"));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

这里我们可以看到这里的Block的结构体:

objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *__strong array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 按照1.3.4节,在OC中,C语言结构体不能含有附_strong修饰符的变量。因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存。
  • 但是 Objective-C 的运行时库能够准确把握Block 从栈复制到堆以及堆上的Block 被废弃的时机,因此 Block 用结构体中即使含有附有__strong 修饰符或__weak 修饰符的变量,也可以恰当地进行初始化和废弃。为此需要使用在__main_block _dese_0结构体中增加的成员变量 copydispose,以及作为指针赋值给该成员变量的_main_block copy_0函数和__main_block_dispose 0函数。
  • 由于在该源代码的Block 用结构体中,含有附有__strong 修饰符的对象类型变量 array,所以需要恰当管理赋值给变量 array 的对象。因此__main_block_copy_0函数使用_Block_object_assign 函数将对象类型对象赋值给 Block 用结构体的成员变量 array 中并持有该对象。
objectivec 复制代码
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方法,将对象赋值在对象类型的结构体中,同时有一个释放内存的函数:

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

这个方法其实是Block在废弃后会调用的函数,用来释放内存。这些函数分别会在Block复制到堆上被调用,和Block被废弃的时候调用:

在下面这几种情况下Block会被复制到堆上:

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

如果Block配置在栈上,那么就会从栈上复制到堆上。Block 作为函数返回值返回时、将Block 赋值给附有__strong 修饰符id类型的类或Block 类型成员变量时,编译器自动地将对象的Block作为参数并调用_Block_copy 函数,这与调用Block的 copy 实例方法的效果相同。在方法名中含有 usingBlock 的 Cocoa 框架方法或 Grand CentralDispatch 的API 中传递 Block 时,在该方法或函数内部对传递过来的Block 调用 Block 的 copy 实列方法或者_Block _copy 函数。

在Block中使用对象类型自动变量的时候,除以下情形外,推荐调用Block的copy实例方法:

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

__block变量和对象

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

由此我们可以知道只有自动变量用__strong进行修饰时才会被block持有,且不随作用域结束而销毁。同时在blk被定义的时候blk就已经捕获了自动变量,而不是在调用blk时才进行捕获

objectivec 复制代码
    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"));

总结

本篇博客主要是笔者对于自己寒假学习中内容的一个总结,但是笔者还是感觉自己对于这个部分的学习并不是很好,后期笔者学习后如果遇到问题还会再次总结相关知识,本篇博客就到这里,期待笔者的新博客吧。

相关推荐
-曾牛7 分钟前
Git Flow
大数据·git·学习·elasticsearch·个人开发
帅次16 分钟前
Flutter Expanded 与 Flexible 详解
android·flutter·ios·小程序·webview
CIb0la25 分钟前
决策卫生问题:考公考编考研能补救高考选取职业的错误吗
学习·考研·生活·高考
pumpkin845141 小时前
学习笔记二十二—— 并发五大常见陷阱
笔记·学习
西瓜本瓜@1 小时前
在 Android 中实现通话录音
android·java·开发语言·学习·github·android-studio
豆沙沙包?2 小时前
4.黑马学习笔记-SpringMVC(P43-P47)
笔记·学习
moxiaoran57532 小时前
Kubernetes(k8s)学习笔记(二)--k8s 集群安装
笔记·学习·kubernetes
孤寂码农_defector2 小时前
鸿蒙系统的 “成长烦恼“:生态突围与技术迭代的双重挑战
macos·华为·objective-c·cocoa·harmonyos
姝孟2 小时前
学习笔记(C++篇)--- Day 3
c++·笔记·学习
xixixiLucky3 小时前
RESTful学习笔记(二)---简单网页前后端springboot项目搭建
笔记·学习