简单理解 Block

浏览: 116 发布日期: 2016-10-12 分类: ios

一、万年不变老问题:什么是 Block

Block 是一段代码块,可以简单的理解为带有自动变量的 匿名函数 , 自动变量 可以理解为 局部变量 , 匿名函数 就是没有名字的函数。 Block 可以像 函数 一样,传入 参数 ,得到 返回值 。

二、Block 的声明与定义

  1. Block 变量的声明

    int (^myBlock)(int);

    上述代码使用操作符 ^ 声明了一个名为 myBlock 的变量。入参为 int 类型,返回值为 int 类型。

  2. Block 的定义

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

    ^ 操作符表示 Block 语句的开始;

    () 中为参数列表;

    {} 中是实现的实现的实体;

    于是我们就知道上面的代码在说什么了:传入参数 num ,返回 int 类型的对象,赋值给 myBlock 。

    完整的分析可以看下图

    1.jpg

  3. 与函数的不同

    1. 没有函数名;(匿名函数嘛~)
    2. 带有操作符 ^ ;

三、Block 的使用

1. Block 的调用

好了,我们已经知道怎么声明及定义 Block ,那么怎么使用呢?超简单,上面说了 Block 与 函数很像,想想我们是怎么使用函数的?

NSLog(@"================%d", myBlock(3));
//结果为6

2. Block 作为函数参数

添加 typedef 关键字,声明一个 Block 类型变量。添加关键字的目的是,可以直接使用名称 nameBlock 。

typedef void(^nameBlock)(NSString *name);

将 nameBlock 作为入参,实现一个函数。

- (void)nameFunction:(nameBlock)nameBlock {
    nameBlock(@"小井");
}

对于函数的使用,直接调用时:

[self nameFunction:^(NSString *name) {
    if (![name isEqualToString:@""]) {
        NSLog(@"My name is %@", name);
    }
}];
// 结果为 My name is 小井

四、Block 中变量的修改

声明一个变量 temp , 声明一个 testBlock ,在 testBlock 的实现体中打印出变量 temp 的值。

int temp = 0;
void (^testBlock)() = ^{
    NSLog(@"temp = %d", temp);

};
temp = 1;
testBlock();
//结果 temp = 0

从最后的打印结果可以看出,尽管在调用 testBlock 之前,对变量 temp 重新赋值为 1, 打印结果仍为 0。于是我们知道了:

Block 在访问外部变量时,会拷贝一份到自己的数据存储中。

为了证明我们的观点,分别在 testBlock 实现体内部和实现体外部打印下地址。

int temp = 0;  
void (^testBlock)() = ^{
    NSLog(@"temp = %d", temp);
    NSLog(@"内部 temp is %ld", &temp);
};
temp = 1;
NSLog(@"外部 temp is %ld", &temp);
testBlock();
 //结果 
 // 外部 temp is 140734745021036
 // temp = 0
 // 内部 temp is 106102872376448

果然,地址不一样了。

下面我们尝试在 testBlock 中修改变量 temp 的值,在 testBlock 的实现实体中,添加如下一句代码。

temp = 2;

会发现,编译器报错了。这是因为 Block 拷贝的变量值是 const 的,即,在 Block 内部不能随意修改。但是当我确实有这样的需求,希望在 Block 内部修改外部变量时,怎么办呢?当当当~~~,只需要在外部变量的声明之前加上 __block 关键字,就可以愉快的在 Block 内部修改变量的值了,我们试试。

__block int temp = 0;

void (^testBlock)() = ^{
    temp = 2;
    NSLog(@"temp = %d", temp);
    NSLog(@"内部 temp is %ld", &temp);
};
temp = 1;
NSLog(@"外部 temp is %ld", &temp);
testBlock();
// 结果
// 外部 temp is 106102872333208
// temp = 2
// 内部 temp is 106102872333208

我们可以发现,在 testBlock 内部成功的修改了变量 temp 的值,并且,跟之前不一样的是,这次的变量地址也相同的。因为加入了 _block 修饰符后, Block 不再拷贝原变量,而是拷贝原变量的引用地址,即这次是把指针拷贝了过来,指针指向原变量地址

2.jpg

五、Block 深坑之循环引用

block 的使用不当会造成循环引用,内存泄露。

3.jpg

typedef void (^block_t)(void);

@interface TestObject : NSObject {
    block_t block_;
}
@end

@implementation TestObject

- (id)init {
    self = [super init];
    block_ = ^(void) {
          NSLog(@"self = %@", self);
    };
    return self;
}

上面的代码编译器会显示 warning

Capturing ‘self’ strongly in this block is likely to lead to a retain cycle;

block_ 是 self 的成员变量, self 持有 Block 的强引用。在 init 初始化方法中, Block 的实现体中,使用了 id 类型的 self , 赋值给了成员变量 block_ ,Block 语法自动由栈拷贝到堆,Block 持有了 self ,于是造成了循环引用。当 main 函数结束时,由于循环引用的存在,堆上的对象不能释放,造成了内存泄露。如下图:

4.jpg

解决 Block 的内存泄露有两种办法:

  1. 使用 __block;
  2. 使用 __weak;

先说使用 __weak :声明 __weak 属性的临时变量 temp , 并将 self 赋值给临时变量。

- (id)init {
    self = [super init];
    id __weak temp = self;
    block_ = ^(void) {
        NSLog(@"self = %@", temp);
    };
    return self;
}

5.jpg

再说使用 __block 方法:

- (id)init {
      self = [super init];
        __block id temp = self;

    block_ = ^(void) {
        NSLog(@"self = %@", temp);
        temp = nil;
    };
    return self;
}

可以分析出:

self 持有 Block ;

Block 持有 __block 变量;

__block 变量持有 self ;

6.jpg

从图上可以看出,还是会存在循环引用的。此时只需要显示的调用下 block_() 就能解决问题,因为在 Block 的执行体中, temp 变量被赋值为 nil , 对 self 的强引用失效,故解除了循环引用。

7.jpg

 

 

来自:http://www.jianshu.com/p/5371a9c27e8d

 

返回顶部