频道栏目
首页 > 资讯 > IOS > 正文

block

16-05-24        来源:[db:作者]  
收藏   我要投稿

一、Blocks概要

Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数。
顾名思义,所谓的匿名函数就是不带有名称的函数。c语言的标准不允许存在这样的函数。例如:

int func (int count);
int resule = func(10);

如果想使用函数指针来代替直接调用函数,那么似乎不用知道函数名也能够使用该函数

int func (int count);
int (*funcptr)(int) = &func;
int result = (*funcptr)(10);

C语言函数中可能使用的变量:

自动变量(局部变量); 函数的参数; 静态变量(静态局部变量); 静态全局变量; 全局变量;

二、Blocks模式

1、Block语法

(1)、^ 返回值类型 参数列表 表达式;

^void (int i){ printf("%d", i);}

(2)、^ 参数列表 表达式;

^(int i){printf("%d", i);}

(3)、^ 表达式;

^{printf("123");}

当你省略返回值类型的时候,你的表达式里return返回什么类型,那么你的返回值类型就是什么。
当你不适用参数的时候,(void) 参数列表可以省略。

int (^myBlock) (int) = ^(int num){
        return num * num;
};

说明:

(1)、int:返回值类型,如果没有返回值则为void (2)、(^myBlock):块定义需要有一个^标记,myBlock是块名称 (3)、(int):参数类型列表,如果没有参数则为void (4)、^(int num):以^开头的参数列表,如果没有则为void,也可以省略 (5)、{}:block体(相当于函数体)

2、Block类型变量

(1)、使用block的时候,我们可以声明block变量,他同c中的函数指针:

int f(int a) {  
     return a;  
}  
int (*fa)(int) = &f;  

(2)、使用Block语法将Block赋值为Block类型变量;

int (^blk)(int) = ^(int a){return a;};

(3)、由^开始的Block语法生成的Block被赋值给变量blk中。因为与通常的变量相同,所有当然也可以由Block类型变量向Block类型变量赋值;

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

(4)、在函数参数中使用Block类型的变量可以向函数传递Block

void func (int (^blk) (int)){
    NSLog(@"%d",blk(1));
}

(5)、在函数返回值中指定Block类型,可以将Block作为函数的返回值返回

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

由此可见,在函数参数和返回值中使用Block类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用typedef来解决该问题:

typedef int (^blk_t)(int);

3、获取自动变量值

- (void)viewDidLoad {
    [super viewDidLoad];
    int myCount = 1;
    int (^blk)(void) = ^{
        NSLog(@"%d",myCount);
        return myCount;
    };
    myCount ++;
    blk();
    NSLog(@"%d",myCount);
}

打印结果如下:

2016-05-20 15:26:43.499 class_01[42917:1552743] 1
2016-05-20 15:26:43.500 class_01[42917:1552743] 2

Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行自动变量的值。

4、__block说明符

实际上,自动变量值截获只能保存执行Block语法的瞬间的值。保存后就不能改写该值。若尝试改写截获的自动变量值,会出现编译错误。如下:

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

若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符。

- (void)viewDidLoad {
    [super viewDidLoad];
    __block int myCount = 1;
    int (^blk)(void) = ^{
        NSLog(@"%d",myCount);
        myCount = 4;
        return myCount;
    };
    myCount ++;
    blk();
    NSLog(@"%d",myCount);
}
2016-05-20 15:33:30.154 class_01[42969:1556701] 2
2016-05-20 15:33:30.155 class_01[42969:1556701] 4

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

(1)、__block是只针对局部变量生效的一种描述变量存储类型的关键字,因此__block类型的变量都是栈变量; (2)、__block类型的变量在其定义的语法范围里,和该范围内的所有block共享存储空间,当block在被复制到heap区域时,同区域内的__block变量占用的内存不会随着退栈而销毁; (3)、出于优化的考虑,栈中的block对象最开始和一般的栈局部变量是相同的,当使用Block_copy对block进行复制时,才被拷贝到heap区域; (4)、__block变量不能是一个可变长数组;

5、截获的自动变量

由上述可得如果将值赋值给Block中截获的自动变量,就会产生编译;
如果截获Objective-C对象,调用变更该对象的方法如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableArray* arr = [NSMutableArray array];
    void (^blk)(void) = ^{
        [arr addObject:@"1"];
    };
    blk();
    NSLog(@"%@",arr);
}

打印:

2016-05-20 16:10:02.051 class_01[43053:1570116] (
    1
)

arr是一个指针,指向一个可变长度的数组。在block里面,并没有修改这个指针,而是修改了这个指针指向的数组。换句话说,arr是一个整数,保存的是一块内存区域的地址,在block里,并没有改变这个地址,而是读取出这个地址,然后去操作这块地址控件的内容。这是允许的,因为声明的block的时候实际上是把当时的临时变量又复制了一份,在block里即使修改这些复制的变量,也不影响外面的原始变量,这就是所谓的闭包。但是当变量是一个指针的时候,block里只是复制了一份这个指针,两个指针指向同一个地址。所以在block里面对指针指向内容做的修改,再block外面也一样生效。

Blocks默认不能修改相同作用域范围内的变量,但是如果这个相同作用域的变量如果使用了__block关键字进行修饰,则可以通过blocks进行修改。

三、Blocks的实现

1、Block的实质

Block是”带有自动变量值的匿名函数”。
在实际编译时无法转换成我们能够理解的源代码,但是clang(LLVM编译器)具有转换为我们可读源代码的功能。通过”-rewrite-objc”选项就能将含有Block语法的源代码转换为C++的源代码。

首先进入该源文件的绝对路径;

clang -rewrite-objc 源代码文件名

我们先来转换一个简单的block代码:

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

生成一个block.cpp的文件

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;
}

可以通过转换后的源代码看到,通过blocks使用的匿名函数,实际上被作为简单的c语言函数来处理。
在转换后的代码的命名上,是根据block语法所属的函数名和该block语法在该函数出现的顺序值。

来看block的语法:

^{printf();};

对应的是

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     printf();
}

这里的__cself相当于c++中的this。

函数参数声明:struct __main_block_impl_0 *__cself
与c++的this和oc的self相同,参数__cself 是 __main_block_impl_0结构体的指针。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
};

这里是取出构造函数的代码,
其中impl是__block_impl结构体。

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

Desc是指针,是__main_block_desc_0结构体的。

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

下面初始化含有这些结构体的__main_block_impl_0结构体的构造函数;

__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;
  }

_NSConcreteStackBlock用于初始化__block_impl结构体的isa成员。

main中构造函数是如何调用的:

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

拆分:

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

这样就容易理解了。 该源代码在栈上生成__main_block_impl_0结构体实例的指针。我们将栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型的变量blk;

注意到生成tmp的函数的参数, 第一个参数是由block语法转换的c语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。
看一下结构体初始化:
__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
这里使用__main_block_impl_0结构体实例的大小进行初始化。

下面看看栈上的_main_block_impl_0结构体实例(即Block)是如何根据这些参数进行初始化的。如果展开__main_block_impl_0结构体的__block_impl结构体,如下:

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

该结构体根据构造函数:

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

2、__block说明符

Block中所使用的被截获自动变量就如:带有自动变量值的匿名函数。 所说的,仅截获自动变量的值。
Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原先截获的自动变量。
如果在Block中视图改变自动变量,将引起编译错误。
但是这样,我们就不能再block中保存值了。
解决方案有两种方法:
(1)、c语言中有一个变量,允许Block改写值。具体如下:

静态变量; 静态全局变量; 全局变量;

在Block中,访问静态全局变量/全局变量没有什么改变,可以直接使用。
静态变量中,转换后的函数原本就设置在含有block语法的函数外,所以无法从变量作用域访问。

(2)、 使用__block说明符
__block存储域类说明符 __block storage-class-specifier

c中的存储域类说明符:

typedef extern static auto register

__block说明符类似于static、auto和register,它们用于作为静态变量值设置到那个存储区域中。例如,auto表示作为自动变量存储在栈中,static表示作为静态变量存储再数据区中。

3、Block存储域

Block和__block变量的实质就是在栈上的结构体实例。Block转换为Block的结构体型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓的结构体类型的自动变量,即栈上生成的该结构体的实例;

名称 实质
Block 栈上Block的结构体实例
__block变量 栈上__block变量的结构体实例

其中Block也是oc的对象,该OC的类为:_NSConreteStackBlock。

类似的类还有:

(1)、_NSConcreteGlobalBlock:他与全局变量一样,设置在程序的数据区域(.data区)中; (2)、_NSConcreteStackBlock:它的对象Block设置在栈上; (3)、_NSConcreteMallocBlock:它的对象设置在由malloc函数分配的内存块中(堆);
设置对象的存储域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的数据区域(.data区)
_NSConcreteMallocBlock

这里写图片描述

(1)、_NSConcreteGlobalBlock

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

该Block的类为_NSConcreteGlobalBlock类。此Block即该Block用结构体实例设置在程序的数据区域中。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获。由此Block用结构体实例的内容不依赖执行时的状态,所以整个程序只需要一个实例。因此将Block用结构体实例设置在于全局变量相同的数据区域中即可。
只在截获自动变量时,Block用结构体实例截获的值才会根据执行时的状态变化。
只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
在以下情况Block为_NSConcreteGlobalBlock类对象:

1、记述全局变量的地方有Block语法时; 2、Block语法的表达式中不使用截获的自动变量时;

(2)、_NSConcreteStackBlock

NSArray *testArr = @[@"1", @"2"];
void (^TestBlock)(void) = ^{
    NSLog(@"testArr :%@", testArr);
};

需要截获自动变量的为_NSConcreteStackBlock类对象,换句话说:
除_NSConcreteGlobalBlock的Block语法生成的Block都为_NSConcreteStackBlock类对象;

(3)、_NSConcreteMallocBlock

1、Block超出变量作用域可存在的原因

分配在栈上的Block和__block变量 其所属的变量作用域结束,该Block或者__block变量也会被废弃。
但是Blocks提供了 将Block和__block变量从栈上复制到堆上的方法来解决这个问题, 这样即使语法记述其作用域结束,堆上的Block也能继续存在。
如图:
这里写图片描述
这里写图片描述
此时,赋值到堆上的Block将_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isa;
impl.isa = _NSConcreteMallocBlock;

2、__block变量用结构体成员变量__forwarding存在的原因

上面的情况下只是Block,而__block变量需要用结构体成员变量__forwarding可以实现 无论__block变量配置在栈上还是堆上时都能够正确地访问__block变量。
在下面我们会说:在__block变量配置在堆上的状态下,也可以访问栈上的__block变量。此时,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,不管是从栈上的__block变量还是从堆上的__block变量都能正确访问。

Blocks提供的复制方法,如何从栈上复制到堆上的? ARC有效的时候,编译器可以自动判断。
来看Block函数:

typedef int (^blk_t)(int);
blk_t func(int rate) {
     return ^(int count){return rate*count;};
}

此时将会返回配置在栈上的Block的函数。 即 程序执行中,从该函数返回函数调用方时变量作用域结束,因此栈上的Block也被废弃。 虽然有问题,但是ARC的编译如下:

blk_t func(int rate) {
    //将通过Block语法生成的Block, 即配置在栈上的Block用结构体实例, 赋值给相当于Block类型的变量tmp中,此处的tmp相当于blk_t __strong tmp;
    blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
    //_Block_copy函数   将栈上的Block复制到堆上,复制后,将堆上的地址作为指针赋值给变量tmp,此处的objc_retainBlock相当于_Block_copy
    tmp = objc_retainBlock(tmp);
    //将堆上的Block作为OC对象,  注册到autoreleasepool中,然后返回该对象。
    return objc_autoreleaseReturnValue(tmp);
}

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

对于配置在堆上的Block以及配置在程序的数据区域上的Block,调用copy方法如下:

Block的类 副本源的配置存储域 复制效果
_NSConcreteGlobalBlock 程序的数据区域 什么也不做
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteMallocBlock 引用计数增加

注意:

①、Block_copy与copy等效,Block_release与release等效; ②、对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1; ③、NSGlobalBlock:retain、copy、release操作都无效; ④、NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[mutableAarry addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。 ⑤、NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain; ⑥、尽量不要对Block使用retain操作。

4、__block变量存储区

Block从栈复制到堆上时,对__block变量产生的影响

__block变量的配置存储域 Block从栈复制到堆时的影响
从栈复制到堆并被Block持有
被Block持有

(1)、如果一个Block中使用__block变量,当该Block从栈复制到堆时,使用的所有__block变量也必定配置在栈上。这些__block变量也全部被从栈复制到堆上。 此时Block持有__block变量。 即使在该Block已复制到堆的情况下,复制Block也对所使用的__block变量没有任何影响。
(2)、在多个Block中使用__block变量时,因为最先会将所有的Block配置在栈上,所以__block变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数;
(3)、如果配置在堆上的Block被弃用,那么它所使用的__block变量也就被释放。

5、截获对象

什么时候栈上的Block赋值到堆?

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

在调用Block的copy的时候,如果Block配置在栈上,那么该Block会从栈复制到堆。 当Block作为函数返回值的时候,将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量的时候,编译器会自动将对象的Block作为参数,并调用_Block_copy函数。这与调用Block的copy实例方法效果相同。在方法中含有usingBlock的Cocoa框架方法或Crand Central Dispatch的API中传递Block时,在该方法或函数内部对传递过来的Block调用Block的copy实例方法或者_Block_copy函数。
也就是说,虽然从源代码来看,在上面这些情况下栈上的Block被复制到了堆上, 但其实可归结为_Block_copy函数被调用时Block从栈复制到堆。
相反,释放复制到堆上的Block后,谁都不持有Block而使其被废弃时调用dispose函数, 相当于对象的dealloc实例方法。
这样,通过使用附有__strong修饰符的自动变量,Block中截获的对象就能超出其变量作用域存在了。

截获对象和使用__block变量时的不同:
对象:BLOCK_FIELD_IS_OBJECT
__block变量:BLOCK_FIELD_IS_BYREF
通过标志来区分是对象还是__block变量。

但是与copy函数持有截获的对象,dispose函数释放截获的对象相同,copy函数持有所使用的__block比那里,dispose函数释放所使用的__block变量。

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

在Block中使用对象类型自动变量时,除了以下3种情况,推荐调用Block的copy实例方法。

①、Block作为函数返回值返回时。 ②、将Block赋值给类的附有__strong修饰符的id类型或Block类型成员变量时。 ③、向方法名中含有usingBlock的Cocoa框架方法或Crand Central Dispatch的API中传递Block时。

6、__block变量和对象

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

clang 转化为

//__block变量用结构体部分
struct __Block_byref_obj_0 {
     void *__isa;
     __Block_byref_obj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     __strong id obj;
};

static void __Block_byref_id_object_copy_131(void* dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void**)((char*)src + 40), 131);
}

static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void**)((vhar*)src + 40), 131);
}

//__block变量声明部分
__Block_byref_obj_0 obj = {
     0,
     &obj,
     0x2000000,
     sizeof(__Block_byref_obj_0),
     __Block_byref_id_object_copy_131,
     __Block_byref_id_object_dispose_131,
     [[NSObject alloc] init]
};

ARC有效时:
当Block从栈复制到堆时,使用_Block_object_assign函数,持有Block截获的对象。 当堆上的Block被废弃时,使用_Block_object_dispose函数,释放Block截获的对象;

在__block变量为附有__strong修饰符的id类型或对象类型自动变量的情形下会发生同样的过程。当__block变量从栈复制到堆上,使用_Block_object_assign函数,持有赋值给__block变量的对象。当堆上的__block被废弃时,使用_Block_object_dispose函数,释放赋值给__block变量的对象。

由此可见,即使对象复制到堆上的附有__strong修饰符的对象类型__block变量中,只要__block变量在堆上存在,那么该对象就会继续处于被持有状态。这与Block中使用赋值给附有__strong修饰符的对象类型自动变量的对象相同。

7、Block循环引用

ARC有效时:

(1)weak消除循环引用

①、self—-Block

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

typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
    blk_t blk_;
}
@end

@implementation MyObject
- (id)init {
    self = [super init];
    blk_ = ^{NSLog(@"self = %@", self);};
    return self;
}
- (void)dealloc {
    NSLog(@"dealloc");
}
@end
int main() {
    id o = [[MyObject alloc] init];
    NSLog(@"%@", o);
    return 0;
}

源代码中,MyObject类的dealloc实例方法一直没有被调用;
MyObject类对象的Block类型成员变量blk_持有赋值为Block的强引用。即MyObject类对象持有Block。init实例方法中执行Block语法使用附有_strong修饰符的id类型变量self。并且由于Block语法赋值在了成员变量blk中,因此通过Block语法生成在栈上的Block此时由栈赋值到堆,并持有所使用的self。self持有Block,Block持有self。这正是循环引用。

为了避免循环引用,可声明附有__weak修饰符的变量,并将self赋值使用。

- (id)init{
    self = [super init];
    id __weak tmp = self;
    blk_ = ^{NSLog(@"self = %@", tmp);};
    return self;
}
②、成员变量id——Block

下面代码也会引起循环:

@interface MyObject:NSObject {
     blk_t blk_;
     id obj_;
}
@end

@implementation MyObject
- (id) init {
     self = [super init];
     blk_ = ^{NSLog(@"obj_ = %@", obj_);};
     return self;
}
@end

Block中没有self,也同样截获了self,引起循环。
其实在block语法中使用了obj_,其实就已经截获了self: self->obj_。
与前面一样,我们可以声明__weak的临时变量来避免循环引用;

(2)、__block消除循环引用

我们还可以使用__block变量来避免循环引用:

typedef void (^blk_t)(void);
@interface MyObject:NSObject{
     blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
     self = [super init];
     __block id tmp = self;
     blk = ^{
          NSLog(@"self = %@", tmp);
          tmp = nil;
     };
     return self;
}
- (void)execBlock{
     blk_();
}
- (void)dealloc{
     NSLog(@"dealloc");
}
@end

int main() {
     id o = [[MyObject alloc] init];
     [o execBlock];
     return 0;
}

这里并没有引起循环引用,但是如果不调用execBlock实例方法,即不执行赋值给成员变量blk_的Block,便会循环引用并引起内存泄露。原因如下:

MyObject类对象持有Block; Block持有__block变量; __block变量持有MyObject类对象。;

(3)、__block,__weak,__unsafe_unretained

①、使用__block变量的优点:

通过__block变量可控制对象的持有期间; 在不能使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可(不必担心悬垂指针); 在执行Block时可动态地决定是否将nil或其他对象赋值在__block变量中。

②、使用__block变量的缺点

为避免循环引用必须执行Block;
相关TAG标签
上一篇:Hive入门笔记-----架构以及应用介绍
下一篇:Html5用户注册自动校验
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站