【iOS开发】—— 一浅谈blocks底层源码

【iOS开发】—— 一浅谈blocks底层源码,第1张

文章目录 概述什么是Blocks? Blocks模式Block语法Block类型变量截获自动变量值__block说明符截获的自动变量 Blocks的实现Block实质截获自动变量__block说明符Block存储域__block变量存储域截获对象__block变量和对象Block循环引用

概述 什么是Blocks?

Blocks是C语言的扩充功能。用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数
但是在C语言的标准中不允许存在匿名函数。通过Blocks,源代码中就可以使用匿名函数。

Blocks模式 Block语法

完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同:

没有函数名返回值类型前带有“^”(插入记号)。

语法:

^ 返回值类型 参数列表 表达式

“返回值类型”同C语言函数的返回值类型,“参数列表”同C语言的参数列表,“表达式”同C语言函数允许使用的表达式。表达式中含有return语句时,其类型必须与返回值类型相同。
例如:

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

Block语法会省略返回值类型以及参数列表,变成下面这样:

^ 表达式
Block类型变量

Block语法下,可将Block语法赋值给声明为Block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Block语法生成的值也被称为“Block”。
声明Block类型变量的示例如下:

int (^blk)(int)

该Block类型变量与一般的C语言变量完全相同,可作为一下用途:

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

下面我们试着使用Block赋值为Block类型变量:

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

由“^”开始的Block语法生成的Block被赋值给变量blk中,因为与通常的变量相同,所以也可以由Block类型变量向Block类型变量赋值。

int (^blk1)(int) = blk; 
int (^blk2)(int);
blk2 = blk1;

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

void func((^blk)(int))

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

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

从上面代码可以看出,在函数参数和返回值中使用Block类型变量时,记述方式复杂。这时可以利用typedef来解决该问题:

typedef int (^blk_t)(int);

原来的记述方式就变为:

void func(blk_t blk)


blk_t func()

通过Block类型变量调用Block与C语言通常的函数调用没有区别。在函数参数中使用Block类型变量并在执行Block的例子如下:

int func(blk_t blk, int rate) {
	return blk(rate);
}

当然,在Objective-C的方法中也可使用:

- (int) methodUsingBlock:(blk_t)blk rate:(int)rate {
	return blk(rate);
}

Block类型变量可完全像通常的C语言变量一样,因此也可以使用指向Block类型变量的指针,即Block的指针类型变量。

typedef int (^blk_t)(int);
blk_t blk = ^(int count)(return count + 1;);
blk_t *blkptr = &blk;
(*blkptr)(10);

由此可知,Block类型变量可像C语言中其他类型变量一样使用。

截获自动变量值

举例来说明:


在该源代码中,Block语法的表达式使用的是它之前的自动变量fmtval。Blocks中,Block表达式截获所使用的自动变量的值,即保存该变量值的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行时自动变量的值。该源代码就在Block语法后改写了Block中的自动变量valfmt
下面是执行结果:

val = 10

执行结果并不是改写后的值“These values were changed. val = 2”,而是执行Block语法时的自动变量的瞬间值。该Block语法在执行时,字符串指针“val = %d\n”被赋值到自动变量fmt中,int值10被赋值到自动变量val中,因此这些值被保存(即被截获),从而在执行块时使用。
这就是自动变量值的截获。

__block说明符

实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写此值。我们来尝试改写截获的自动变量值:

int val = 0;
void (^blk)(void) = ^{val = 1;};
blk();
printf("val = %d\n", val);

以上是在Block语法外声明的给自动变量赋值的源代码。该源代码会产生编译错误:

若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符。该源代码中,如果给自动变量声明int val附加__block说明符,就能实现Block内赋值。

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

该源代码的执行结果为:

val = 1;

使用附有__block说明符的自动变量可在Block中赋值,该变量称为__block变量。

截获的自动变量

如果将值赋值给Blcok中截获的自动变量,就会产生编译错误。

int val = 0;
void (^blk)(void) = ^(val = 1;);

该源代码会产生以下编译错误:

但是截获Objective-C对象,调用变更该对象的方法是不会产生编译错误:

id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
	id obj = [[NSObject alloc] init];
	[array addObject:obj];
};

但是若向截获的变量array赋值则会产生编译错误。该源代码中截获的变量为NSMutableArray类的对象。如果用C语言来描述,就是截获NSMutableArray类对象用的结构体实例指针。虽然赋值给截获的自动变量array的 *** 作会产生编译错误,但使用截获的值却不会有任何问题。下面源代码向捕获的自动变量进行赋值,会产生编译错误:

id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
	array = [[NSMutableArray alloc] init];
};

这种情况下,需要给截获的自动变量附加__block说明符。

__block id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
	array = [[NSMutableArray alloc] init];
};

另外,在使用C语言数组时必须小心使用其指针。源代码示例如下:

const char text[] = "hello";
void (^blk)(void) = ^{
	printf("%c\n", text[2]);
};

只是使用C语言的字符串字面量数组,而并没有向截获的自动变量赋值,因此看似没有问题。但实际上会产生以下编译错误:

这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获。这时,使用指针可以解决该问题:

const char *text = "hello";
void (^blk)(void) = ^{
	printf("%c\n", text[2]);
};
Blocks的实现 Block实质

参考博客:【一篇文章剖析block底层源码以及Block.private】

通过代码去分析一下Block语法转换成的C++代码:

int main() {
	void (^blk)(void) = ^{
		printf("Block\n");
	};
	blk();
	return 0;
}
//经过clang转换后的C++代码
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(int argc, const char * argv[]) {
	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;
}

转化后的C++源码很复杂,所以我们分成几个部分来看。

首先是__block_impl结构体
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
结构体的名称:implimplementation的缩写,换句话说这一部分是block的实现部分结构体。void *isa:声明一个不确定类型的指针,用于保存Block结构体实例。int Flags:标识符。int Reserved:今后版本升级所需的区域大小。void *FuncPtr:函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。 在介绍struct __main_block_impl_0结构体之前,先介绍一下static struct __main_block_desc_0结构体
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)};
第一个成员变量指的是今后版本升级所需区域的大小(一般填0)。第二个成员变量是Block的大小。__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};: 这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是__main_block_desc_0_DATA。其中reserved为0,Block_sizesizeof(struct __main_block_impl_0)。 然后是struct __main_block_impl_0结构体
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;
  }
};
第一个成员变量是impl,也就是上面点1中的结构体的变量。第二个成员变量是Desc指针,就是上面点2中的结构体的指针。剩下的代码就是:初始化含有这些结构体的__main_block_impl_0结构体的构造函数。 接下来就是static void __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself {
	printf("Block\n");
}

这一部分就是Blcok执行的实际代码块。也是点3中fp指针指向的函数。括号中的参数__cself是相当于OC语言版的self,代表的是Block本身。

最后来看main函数
int main(int argc, const char * argv[]) {
	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;
}

先看第一行代码,这部分代码就是定义和初始化部分。
因为转换较多,所以看着很麻烦,去掉转换的部分,具体如下:

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

该源代码将__main_block_impl_0结构体类型的自动变量,也就是栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。
第二行代码就是相当于源代码中的blk(),即使用该Block部分。去掉转换部分就是:

(*blk->impl.FuncPtr)(blk);

这是使用函数指针调用函数。由Block语法转换的__main_block_impl_0函数的指针被赋值成员变量FunPtr中。

以上就是Block的实质,Block即为Objective-C对象。

截获自动变量

源代码:

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

和上面一样,将截获自动变量值的源代码通过clang进行转换:

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;
  int val = __cself->val; 
  
	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 argc, const char * argv[]) {
	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));
    return 0;
}

这次与上次不同的是在Block语法表达式中使用的自动变量被当作成员变量追加到了__main_block_impl_0结构体中:

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) 

初始化时自动变量fmt和val进行了赋值 *** 作:

	impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    fmt = "val = %d\n";
    val = 10;

由此可知,在__main_block_impl_0结构体实例中(即Block),自动变量被截获。
再看一下使用Block的匿名函数的实现,
源代码:

^{printf(fmt, val)};

转换为下面:

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

在转换后的代码中,截获到__main_block_impl_0结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义,所以之后即使改变自动变量的值也不会对Block语法中的内容有所变化。

总结:Block捕获的外部变量可以改变值的是静态变量、静态全局变量、全局变量,const修饰的变量,个人觉得没什么不同,const修饰的全局变量和平时全局变量一样,修饰的局部变量和普通的局部变量一样。Block是不捕获全局变量的,直接访问;捕获的是局部变量,局部自动变量是通过地址传递,而局部静态变量是通过值传递。

变量类型是否能捕获传递方式
全局变量直接访问
局部自动变量(auto)值传递
局部静态变量(static)指针传递
__block说明符

像前面我们说的一样,因为在实现上不能改写被截获自动变量的值,所以当编译器在编译过程中检出被截获自动变量赋值的 *** 作时,就会报错。那这样就无法在Block中保存值了,极为不便。这时我们就可以使用“__block说明符”,更准确的表述是“__block存储域类说明符”。

下面用代码来介绍__block的作用:

int main(int argc, const char * argv[]) {
    __block int val = 10;
    void (^blk)(void) = ^{
        val = 1;
    	printf("val = %d\n", val);
    };
    blk();
    return 0;
}

经clang变换后的代码:

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; 
  (val->__forwarding->val) = 1;
  printf("val = %d\n", (val->__forwarding->val));
}

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, BLOCK_FIELD_IS_BYREF);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src {
	_Block_object_dispose((void*)src->val, 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[]) {
	__Block_byref_val_0 val = {
    	0,
    	(__Block_byref_val_0 *)&val, 
    	0, 
    	sizeof(__Block_byref_val_0), 
    	10
    };
    blk = &__main_block_impl_0(
    	__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    return 0;
}

还是逐个去分析:

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

其他几个成员变量没什么说的,主要看一下__Block_byref_val_0 *__forwarding,这个相当于指向该结构体本身的一个指针。

__main_block_impl_0结构体
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;
  }
};

这部分值得注意的是,对于我们的__Block_byref_val_0结构体,我们同样是用一个指针去保存,这么做的原因是通过__block修饰的变量可能会被不止一个block使用,使用指针可以保证其可以被多个block调用。

static void __main_block_func_0函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; 
  (val->__forwarding->val) = 1;
  printf("val = %d\n", (val->__forwarding->val));
}

这里需要注意的是,对val赋值的时候需要通过forwarding指针。

最后再看一下主函数:
int main(int argc, const char * argv[]) {
	__Block_byref_val_0 val = {
    	0,
    	(__Block_byref_val_0 *)&val, 
    	0, 
    	sizeof(__Block_byref_val_0), 
    	10
    };
    
    blk = &__main_block_impl_0(
    	__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    return 0;
}

主要看一下__block修饰的变量的赋值:

__Block_byref_val_0 val = {
    	0,
    	(__Block_byref_val_0 *)&val, 
    	0, 
    	sizeof(__Block_byref_val_0), 
    	10
    };

这个__block变量val变为了__Block_byref_val_0结构体变量。通过调用 static void __main_block_func_0函数(通过__Block_byref_val_0结构体成员变量__forwarding访问成员变量val),将10赋给val。

Block存储域

在上文出现过_NSConcreteStackBlock一词,这是描述Block对象的类。分为以下三种:

_NSConcreteStackBlock:该类的对象Block设置在栈上。_NSConcreteGlobalBlock:该类的对象Block设置在数据区域(.data)中。_NSConcreteMallocBlock:该类的对象则设置在由malloc函数分配的内存块中(即)。

在以上例子中的Block都是_NSConcreteStackBlock类,且都设置在栈上,但实际上并不都是这样,在记述全局变量的地方使用Block语法时,生成的Block对象就是_NSConcreteGlobalBlock;以及Block语法的表达式中不使用应截获的自动变量时,生成的Block对象也是_NSConcreteGlobalBlock。不会有一创建的Block对象就是分配在堆上的,但是可以对栈上的Block对象copy就可以实现分配在堆上。
但是,当我们不知道Block对象在哪个存储域时,且使用了copy方法,这样会如何呢?

不管Block配置在何处,用copy方法复制都不会引起任何问题,即使在不确定情况下也可以调用copy方法。

__block变量存储域

上面讲了Block的存储域,下面再看一下__block变量的存储域。
使用__block变量的Block从栈上复制到堆上时,__block变量也会受到影响。

当1个Block中使用__block变量,则当该block从栈复制到堆上时,使用的所有的这些__block对象也全部从栈复制到堆,此时Block持有__block对象。

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

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

此思考方式和Objective-C的引用计数式内存管理完全相同。使用__block变量的Block持有__block变量。如果Block被废弃,则它所持有的__block变量也被释放。

现在我们理解了__block变量的存储域之后,可以回过头想想上面的使用__block变量用结构体成员变量__forwarding的原因。“不管__block变量配置在栈上还是堆上,都能够正确的访问该变量”,就像这句话所说,通过Block的复制,__block变量也从栈上复制到堆上,此时可同时访问栈上的__block变量和堆上的__block变量

通过该功能,无论是在Block语法中、Block语法外使用__block变量,还是在栈上或堆上,都可以顺利地访问同一个__block变量。

截获对象

总结一下栈上的Block会复制到堆上的情况:

调用Block的copy实例方法Block作为函数返回值返回时在Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时在方法名中含有usingBlockCocoa框架方法或Grand Central Dispatch的API中传递Block
介绍两种函数,也就是在上文中介绍__block说明符时未说明的C++源代码中的__main_block_dispose_0(以下简称dispose函数)和__main_block_copy_0函数(以下简称copy函数)。前者相当于release实例方法,后者相当于copy实例方法。

当栈上的Block被复制到堆上时,可以归结为_Block_copy函数被调用时Block从栈上复制到堆上,同样的,在释放复制到堆上的Block后,谁都不持有Block而使其被废弃时调用dispose函数,这相当于对象的dealloc实例方法。有了这种构造,我们就可以通过使用附有__strong修饰符的自动变量,使Block中截获的对象就能够超出其变量作用域而存在

Block中使用的赋值给附有__strong修饰符的自动变量的对象和复制到堆上的__block变量由于被堆上的Block所持有,因而可以超出其变量作用域而存在。

只有调用_Block_copy函数才能持有截获的附有__strong修饰符的对象类型的自动变量值,如果不调用_Block_copy函数,即使被截获了,也会随着变量作用域的结束被废弃。

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

Block作为函数返回值返回时将Block赋值给类的附有__strong修饰符的id类型或Block类型成员变量时在方法名中含有usingBlockCocoa框架方法或Grand Central Dispatch的API中传递Block
因为以上三种情形下,不使用copy实例方法,栈上的Block也会复制到堆上。 __block变量和对象

用__block修饰对象:

__block id obj = [[NSObject alloc] init];

通过clang转换为:

先介绍一个函数:_Block_object_assign:该函数相当于retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中。

通过调用_Block_object_assign函数和_Block_object_dispose函数,控制赋值给附有__strong修饰符的对象类型自动变量的对象的持有和释放状态。
我们前面用到的只有附有__strong修饰符的id类型或对象类型自动变量。如果使用__weak修饰符呢?

首先是在Block中使用附有__weak修饰符的id类型变量的情况:

ARC环境下执行结果为:

若同时指定__block修饰符和__weak修饰符会怎么样呢?

这是因为即使附加了__block说明符,附有__strong修饰符的变量array也会在该变量作用域结束的同时被释放被废弃,nil被赋值给附有__weak修饰符的变量的array2中。

不要同时使用__autoreleasing修饰符与__block说明符,会产生编译错误。

Block循环引用

如果在Block中使用附有__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block所持有。这样容易引起循环引用。

当该对象被Block持有,而Block又被该对象持有,就会产生循环引用。使用__weak或者__block修饰该对象即可解决。
使用__weak修饰符的实质是将对象的强引用改为弱引用,使用__block修饰符实质是在Block中将__block变量赋nil值。
对于非ARC下, 为了防止循环引用, 我们使用__block来修饰在Block中使用的对象:
对于ARC下, 为了防止循环引用, 我们使用__weak来修饰在Block中使用的对象。原理就是:ARC中,Block中如果引用了__strong修饰符的自动变量,则相当于Block对该变量的引用计数+1。
iOS Block在ARC/非ARC下的使用总结

参考博客:[iOS开发]Block

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/web/996909.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存