Block详解

Block本质

Block本质上也是一个OC对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。

闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」;Block是Object-C对于闭包的实现

  • 可以嵌套定义,定义block方法和定义函数方法相似
  • Block可以定义在方法内部或外部
  • 只有调用Block时候,才会执行其{}体内的代码
  • 本质是对象,使代码高聚合
一、Block的实现

1、无参数无返回值


// 1、无参数,无返回值,声明和定义

void(^ZSBlockOne)(void) = ^(void){
	NSLog(@"无参数,无返回值")
};

ZSBlockOne();

2、有参数无返回值


// 2、有参数,无返回值,声明和定义

void(^ZSBlockTwo)(int value) = ^(int value){
	NSLog(@"有参数,无返回值的Block:%d",value);
};

ZSBlockTwo()

3、有参数有返回值


// 3、有参数,有返回值
int(^ZSBlockThree)(int value) = ^(int value){
	NSLog(@"有参数,有返回值的block:%d",value);
	return value + 10;
};
int a = ZSBlockThree(5);

4、无参数有返回值


// 4、无参数,有返回值
int(^ZSBlockFour)(void) = ^{
	NSLog(@"无参数,有返回值的block");
	return 100;
};
int a = ZSBlockFour();

5、开发中常用typedef定义block


typedef void (^ZSBlock)(int value);

// ZSBlock是一个种block类型,之后可以直接创建属性
@property(nonatomic, copy) ZSBlock myBlock;

self.myBlock = ^(int value){
	NSLog(@"调用了block,传了一个值为:%d",value);
};

self.myBlock(10);

二、Block的类型

Block共有3种类型:

1、全局Block(NSGlobalBlock)
2、栈上的Block(NSStackBlock)
3、堆上的Block(NSMallocBlock)

通过代码看下他们的作用域以及使用场景


// 1、Block内部没有调用外部变量
    void(^block)(void) = ^{
         NSLog(@"hello");
     };
        
// 2、内部调用外部变量的block
    int a = 10;
    void(^block1)(void) = ^{
         NSLog(@"a的值为:%d",a);
    };
        
// 3、直接调用block的class
    NSLog(@"%@ %@ %@",[block class],[block1 class],[^{
         NSLog(@"%d",a);
    } class]);

运行结果


2019-09-04 13:48:51.981040+0800 ZSLoadDemo[93842:995366] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

结论

1、NSGlobalBlock没有访问auto变量,直到程序结束才会被回收。
2、NSStackBlock访问了auto(外部)变量,存放在栈中,我们知道栈中的内存由系统自由分配和释放,作用域执行完毕之后就会被立即释放。
3、NSMallocBlock 本质NSStackBlock调用了copy,存放于堆内存中,是一个带引用计数的对象,也是我们平常开发中最常用的。存放在堆区需要我们进行内存管理。
总结:存储在栈中的Block就是栈块,存储在堆中就是堆块,既不在栈中,也不再堆中就是全局块

三、Block与外部变量

参考文献:https://imlifengfeng.github.io/article/457/
为了保证block内部能够正常访问外部的变量,block有一个变量捕获机制

1、block使用局部变量值
(1)默认情况,block内部直接使用外部局部变量
对于block外的变量引用,block默认是将其复制到其数据结构中来访问的。也就是说block的自动变量截获值针对block内部使用的自动变量,不实用则不截获,因为解惑的自动变量会存储在block的结构体内部,会导致block体积变大。所以要注意默认情况下block只能访问不能修改局部变量的值。

代码实例


int value = 10;
void(^myBlock)(void) = ^{
	NSLog(@"value==%d",value);
};
value = 20;

myBlock();

//输出结果
age = 10;

(2)使用__block 修饰的外部变量
对于用__block修饰的外部变量引用,block是复制其引用地址来实现访问的。block内部可以修改__block修饰的外部变量的值。

代码实例


__block int value = 10;
void(^myBlock)(void) = ^{
	NSLog(@"value==%d",value);
};
value = 20;

myBlock();

//输出结果
age = 20;

四、block为什么用copy修饰

block用copy作用域
上面说过block是一个oc对象,所以block理论上是可以用retain或strong去描述的。但是block在创建的时候它的内存是默认是分配栈(stack)上,而不是堆(heap)上的。所以它的作用域仅限创建时候的当前上下文(函数,方法…),当你在该作用域外使用block 时,程序会崩溃。

  1. 一般情况下你不需要自行调用copy或则retain一个block。只有当你需要在block定义域以外的地方使用时才需要copy,Copy将block从内存栈区移到堆区。
  2. 其实block使用copy是MRC留下来的也算是一个传统吧,在MRC下,如上述在方法中的block创建在栈区,使用copy就能把他放到堆区,这样在作用域外调用该block程序就不会崩溃。
  3. 但在ARC下,使用copy与strong其实都一样,因为block的retain就是用copy来实现的。
五、关于Block面试题

1、Copy将block从内存栈区移到堆区,这里移的是原来的block,还是新生成的block对象?

拷贝栈区的block所在的内存,然后在堆区重新开辟一块放新生成的block

2、__block什么时候用?

1、当需要在block中修改外部变量值时使用
2、外部变量避免被block强引用一次,避免循环引用

3、在block里面堆数组执行添加操作,这个数组需要声明为__block吗?

不需要,因为数组可以理解为指针,在block里面堆数组进行添加,删除操作,只是改变了指针指向的值,而没有修改外部数组的地址。

4、在block里面对NSInteger进行修改,需要对NSInteger声明为__block?

需要,NSInteger是基本数据数据类型,基本数据类型没有static等的保护下,需要__block。

5、为什么使用__block修饰外部变量的值就可以被block修改?

内部实现代码:


__block int val = 10;

转换成

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

会发现一个局部变量加上**__block修饰符后竟然跟block一样变成了一个__Block_byref_val_0结构体类型的自动变量实例。
我们在block内部访问val变量则需要通过一个叫
forwarding的成员变量来间接访问val变量**
forwarding
通过forwarding无论是在block中还是 block外访问block变量, 也不管该变量在栈上或堆上, 都能顺利地访问同一个__block变量。

未完待续,发现有问题可以留言一起讨论下
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值