盘点:iOS中block的用法解析和底层原理

本文详细解析了iOS中Block的用法,包括Block的声明、定义、常见用法、少见用法以及应用场景。同时,探讨了Block如何捕获外部变量、内存管理以及Block的底层实现,帮助开发者深入理解Block的工作机制。此外,还提到了Block在内存中的三种类型:全局Block、栈Block和堆Block,以及Block的复制和生命周期。最后,文章提供了研究Block底层的方法,通过clang工具查看Block的C++实现。
摘要由CSDN通过智能技术生成

1. 前言


Block:带有自动变量(局部变量)的匿名函数。它是C语言的扩充功能。之所以是拓展,是因为C语言不允许存在这样匿名函数。

1.1 匿名函数

匿名函数是指不带函数名称函数。C语言中,函数是怎样的呢?类似这样:

int func(int count);

调用的时候:

int result = func(10);

func就是它的函数名。也可以通过指针调用函数,看起来没用到函数名:

int result = (*funcptr)(10);

实际,在赋值给函数指针时,必须通过函数的名称才能获得该函数的地址。完整的步骤应该是:

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

而通过Block,就能够使用匿名函数,即不带函数名称的函数。

1.2 带有自动变量

关于“带有自动变量(局部变量)”的含义,这是因为Block拥有捕获外部变量的功能。在Block中访问一个外部的局部变量,Block会持用它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态。

捕获外部变量,看一个经典block面试题:

int val = 10; void (^blk)(void) = ^{ printf("val=%d
",val);
}; 
val = 2; 
blk();

上面这段代码,输出值是:val = 10,而不是2。

block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝;换句话说block截获自动变量的瞬时值;或者block捕获的是自动变量的副本。

由于block捕获了自动变量的瞬时值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。

所以,上面的面试题的结果是2不是10。

解决block不能修改自动变量的值,这一问题的另外一个办法是使用__block修饰符。

__block int val = 10; void (^blk)(void) = ^{printf("val=%d
",val);};  
val = 2;  
blk();

上面的代码,跟第一个代码段相比只是多了一个__block修饰符。但是输出结果确是2。

2. Block语法大全


约定:用法中的符号含义列举如下:

  • return_type表示返回的对象/关键字等(可以是void,并省略)

  • blockName表示block的名称

  • var_type表示参数的类型(可以是void,并省略)

  • varName表示参数名称

2.1 Block声明及定义语法,及其变形
(1) 标准声明与定义
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
(2) 当返回类型为void
void (^blockName)(var_type) = ^void (var_type varName) { // ... };
blockName(var);

可省略写成

void (^blockName)(var_type) = ^(var_type varName) { // ... };
blockName(var);
(3) 当参数类型为void
return_type (^blockName)(void) = ^return_type (void) { // ... };
blockName();

可省略写成

return_type (^blockName)(void) = ^return_type { // ... };
blockName();
(4) 当返回类型和参数类型都为void
void (^blockName)(void) = ^void (void) { // ... };
blockName();

可省略写成

void (^blockName)(void) = ^{ // ... };
blockName();
(5) 匿名Block

Block实现时,等号右边就是一个匿名Block,它没有blockName,称之为匿名Block:

^return_type (var_type varName)
{ //... };
2.2 typedef简化Block的声明

利用typedef简化Block的声明:

  • 声明
typedef return_type (^BlockTypeName)(var_type);
  • 例子1:作属性
//声明 typedef void(^ClickBlock)(NSInteger index); //block属性 @property (nonatomic, copy) ClickBlock imageClickBlock;
  • 例子2:作方法参数
//声明 typedef void (^handleBlock)(); //block作参数 - (void)requestForRefuseOrAccept:(MessageBtnType)msgBtnType messageModel:(MessageModel *)msgModel handle:(handleBlock)handle{
  ...
2.3 Block的常见用法
2.3.1 局部位置声明一个Block型的变量
  • 位置
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
  • 例子
void (^globalBlockInMemory)(int number) = ^(int number){ printf("%d 
",number);
};
globalBlockInMemory(90);
2.3.2 @interface位置声明一个Block型的属性
  • 位置
@property(nonatomic, copy)return_type (^blockName) (var_type);
  • 例子
//按钮点击Block @property (nonatomic, copy) void (^btnClickedBlock)(UIButton *sender);
2.3.3 在定义方法时,声明Block型的形参
  • 用法
- (void)yourMethod:(return_type (^)(var_type))blockName;
  • 例子

UIView+AddClickedEvent.h

- (void)addClickedBlock:(void(^)(id obj))clickedAction;
2.3.4 在调用如上方法时,Block作实参
  • 例子

UIView+AddClickedEvent.m

- (void)addClickedBlock:(void(^)(id obj))clickedAction{ self.clickedAction = clickedAction; // :先判断当前是否有交互事件,如果没有的话。。。所有gesture的交互事件都会被添加进gestureRecognizers中 if (![self gestureRecognizers]) { self.userInteractionEnabled = YES; // :添加单击事件 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
        [self addGestureRecognizer:tap];
    }
}

- (void)tap{ if (self.clickedAction) { self.clickedAction(self);
    }
}
2.4 Block的少见用法
2.4.1 Block的内联用法

这种形式并不常用,匿名Block声明后立即被调用:

^return_type (var_type varName)
{ //... }(var);
2.4.2 Block的递归调用

Block内部调用自身,递归调用是很多算法基础,特别是在无法提前预知循环终止条件的情况下。注意:由于Block内部引用了自身,这里必须使用__block避免循环引用问题。

__block return_type (^blockName)(var_type) = [^return_type (var_type varName)
{ if (returnCondition)
    {
        blockName = nil; return;
    } // ... // 【递归调用】 blockName(varName);
} copy];

【初次调用】
blockName(varValue);
2.4.3 Block作为返回值

方法的返回值是一个Block,可用于一些“工厂模式”的方法中:

    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值