iOS开发之OC篇(14)—— Block

#版本
Xcode 9.1

#block简介
block是一个OC对象,于iOS4开始引入。其本身封装了一段代码,可被当作变量、当作参数或作为返回值。block常用于GCD、动画、排序及各类回调传值中。

block代码结构图 block代码结构图 注:图片来自[这里](http://www.jianshu.com/p/29d70274374b)

示例1

    // 创建一个block
    int(^myBlock)(int) = ^(int num) {
        return num * 3;
    };
    
    // 调用block
    NSLog(@"%d",myBlock(3));        // 结果输出9

示例2
如果需要重复声明多个相同参数和返回值的block,我们可以用typedef来定义block类型。

    // 声明一个block
    typedef int(^myBlock)(int);
    
    // 实例化第一个block
    myBlock block1 = ^(int num) {
        return num * 3;
    };
    
    // 实例化第二个block
    myBlock block2 = ^(int num) {
        return num * 4;
    };
    
    // 调用block
    NSLog(@"%d, %d",block1(3),block2(3));       // 结果为9, 12

#block分类
按照存储区域可分为三类:

  • NSGlobalBlock
  • NSStackBlock
  • NSMallocBlock

####1. NSGlobalBlock
全局block——没有用到外界变量,或者只用到全局变量、静态(static)变量。生命周期从创建到应用程序结束。

示例:

    /* 情况1:不使用外部变量 */
    // 声明一个没有用到外部变量的block
    void (^globalBlock1)(void) = ^{
    };
    // 调用block
    globalBlock1();
    // 查看block类型
    NSLog(@"globalBlock1:%@",globalBlock1);
    
    /* 情况2:使用全局变量和静态变量 */
    // 新建静态变量
    static NSString *staticStr = @"我是静态变量";
    // 声明一个使用全局变量和静态变量的block
    void (^globalBlock2)(void) = ^{
        NSLog(@"%@, %@",globalStr,staticStr);
    };
    // 调用block
    globalBlock2();
    // 查看block类型
    NSLog(@"globalBlock2:%@",globalBlock2);

结果如下:

![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzg5MDA3OTUtNTgzZmQ5MTIyMDE2ZGM0MS5wbmc?x-oss-process=image/format,png)

####2. NSStackBlock
栈block——用到局部变量、成员属性/变量,且没有强指针引用。生命周期由系统管理,超出作用域(函数返回时)马上被销毁。

    /* 情况1:用到局部变量 */
    // 新建一个局部变量
    NSString *localStr = @"我是局部变量";
    // 查看block类型, 直接在NSLog里面调用使用了局部变量的block,这样就没有强指针引用了
    NSLog(@"stackBlock1:%@",^{NSLog(@"%@",localStr);});
    
    /* 情况2:用到成员属性/变量 */
    // 给成员变量、成员属性赋值
    _variateStr = @"我是成员变量";
    self.propertyStr = @"我是成员属性";
    // 查看block类型, 直接在NSLog里面调用使用了成员属性/变量的block,这样就没有强指针引用了
    NSLog(@"stackBlock2:%@",^{NSLog(@"%@, %@",_variateStr,self.propertyStr);});

结果:

![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzg5MDA3OTUtNDY2ZmVhMWYwYzFlOGU2ZC5wbmc?x-oss-process=image/format,png)

####3. NSMallocBlock
堆block——有强指针引用或使用有copy修饰的成员属性/变量。没有强指针引用即销毁,生命周期需手动管理。

    /* 情况1:用到强指针引用 */
    // 新建一个局部变量
    NSString *localStr = @"我是局部变量";
    // 声明一个使用强指针引用的block
    void (^mallocBlock1)(void) = ^{
        NSLog(@"%@",localStr);
    };
    // 调用block
    mallocBlock1();
    // 查看block类型
    NSLog(@"mallocBlock1:%@",mallocBlock1);
    
    /* 情况2:用到有copy修饰的成员属性/变量 */
    // 给成员变量、成员属性赋值
    _variateStr = @"我是成员变量";
    self.propertyStr = @"我是成员属性";
    // 声明一个使用有copy修饰的成员属性的block
    void (^mallocBlock2)(void) = ^{
        NSLog(@"%@, %@",_variateStr, self.propertyStr);
    };
    // 调用block
    mallocBlock2();
    // 查看block类型
    NSLog(@"mallocBlock2:%@",mallocBlock2);

结果:

![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzg5MDA3OTUtYmFiM2M4OTFkMjNjNzY1OC5wbmc?x-oss-process=image/format,png)

#block访问外部变量
###1. 访问局部变量
在block中可以访问局部变量,但不能直接修改局部变量。
示例1:

    // 声明局部变量local
    int local = 100;
    // 声明block
    void(^myBlock)(void) = ^{
        // local++;    // 修改local值会报错
        NSLog(@"local1 = %d", local);
    };
    // 在调用block之前改变local的值
    local = 101;
    // 调用block
    myBlock();
    // 查看local改变后的值
    NSLog(@"local2 = %d", local);

打印结果:

![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzg5MDA3OTUtMTc0N2NlZjY0MjE2YWE4My5wbmc?x-oss-process=image/format,png)

原理分析:

在block定义时是将局部变量的值传给block变量所指向的结构体,因此在调用block之前对局部变量进行修改并不会影响block内部的值。同时传进来的内部值也是不可修改的。

但是,我们有时候又想在block内部修改block外的局部变量,应该怎么办呢?

使用__block修饰的局部变量,在block中可以直接修改
示例2:

    // 声明局部变量local (用__block修饰)
    __block int local = 100;
    // 声明block
    void(^myBlock)(void) = ^{
        NSLog(@"修改前local1 = %d", local);
        local++;    // 不会报错
        NSLog(@"修改后local1 = %d", local);
    };
    // 在调用block前修改local值
    local = 200;
    // 调用block
    myBlock();
    // 查看local改变后的值
    NSLog(@"local2 = %d", local);

打印结果:

![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzg5MDA3OTUtMTRkNGVhMTVjM2Y0NTdjNi5wbmc?x-oss-process=image/format,png)

原理分析:

在局部变量前使用__block修饰,block定义时是将局部变量的指针传给block变量所指向的结构体,因此在调用block之前对局部变量进行修改会影响block内部的值。同时内部的值也是可以修改的。

###2. 访问静态变量

在block中可以访问静态变量,也可以直接修改静态变量。

示例:

    // 声明静态变量staticInt
    static int staticInt = 100;
    // 声明block
    void(^myBlock)(void) = ^{
        NSLog(@"修改前staticInt1 = %d", staticInt);
        staticInt++;    // 不会报错
        NSLog(@"修改后staticInt1 = %d", staticInt);
    };
    // 在调用block前修改staticInt值
    staticInt = 200;
    // 调用block
    myBlock();
    // 查看staticInt改变后的值
    NSLog(@"staticInt2 = %d", staticInt);

打印结果:

![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzg5MDA3OTUtNjE0Mzc2OTZhM2U0ZTFjOC5wbmc?x-oss-process=image/format,png)

原理分析:

block定义时是将静态变量的指针传给Block变量所指向的结构体,因此在调用block之前对静态变量进行修改会影响block内部的值。同时内部的值也是可以修改的。

###3. 访问全局变量

在block中可以访问全局变量,也可以直接修改全局变量。
示例:

    // 先在@implementation前定义一个全局变量globalInt
    
    // 声明block
    void(^myBlock)(void) = ^{
        NSLog(@"修改前globalInt1 = %d", globalInt);
        globalInt++;    // 不会报错
        NSLog(@"修改后globalInt1 = %d", globalInt);
    };
    // 在调用block前修改globalInt值
    globalInt = 200;
    // 调用block
    myBlock();
    // 查看globalInt改变后的值
    NSLog(@"globalInt2 = %d", globalInt);

打印结果:

![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzg5MDA3OTUtODBkMmMyODUxMjc5ZTlhNi5wbmc?x-oss-process=image/format,png)

原理分析:

全局变量所占用的内存只有一份,供所有函数(方法)共同调用,在block定义时并未将全局变量的值或者指针传给block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响block内部的值。同时内部的值也是可以修改的。

#block在ARC下的内存管理

如果对象内部有一个block属性,而在block内部又访问了该对象,那么会造成循环引用
错误示例:

// 声明block成员属性
@property (nonatomic, copy) void(^myBlock)(void);

    // 定义block
    self.myBlock = ^{
        NSLog(@"%@", self);    // 此句造成循环引用,编译报错
    };
    
    // 执行block
    self.myBlock();

解决办法:使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作。
正确示例:

// 声明block成员属性
@property (nonatomic, copy) void(^myBlock)(void);

    // 声明一个弱引用指针对象
    __weak typeof(self) weakSelf = self;
    
    // 定义block
    self.myBlock = ^{
        NSLog(@"%@", weakSelf);        // 使用弱引用指针对象
    };
    
    // 执行block
    self.myBlock();

如果担心在调用Block之前引用的对象已经被释放,那么我们需要在Block内部再定义一个强指针来指向该对象
官方示例:

// 声明block成员属性
@property (nonatomic, copy) void(^myBlock)(void);

    // 声明一个弱引用指针对象
    __weak typeof(self) weakSelf = self;
    
    // 定义block
    self.myBlock = ^{
        typeof(self) strongSelf = weakSelf;     // 声明一个强引用指针对象
        NSLog(@"%@", strongSelf);               // 使用强引用指针对象
    };
    
    // 执行block
    self.myBlock();

#block的传值应用

先看看效果:

从界面1加载界面2,然后在界面2返回时回传textField里的text。方法步骤:

  • 界面1(接收方):
    1、定义(实现)一个block并传给界面2(的block属性)
    2、在block中处理传递过来的值
  • 界面2(传值方):
    1、声明一个block属性,其具体实现由接收方来完成
    2、在需要传值的地方调用block

界面1 .m文件:

// 跳转到界面2
- (IBAction)btnShowViewController2:(UIButton *)sender {
    
    // 从storyboard实例化界面2
    ViewController2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewController2"];
    
    // 定义(实现)一个block并传给界面2(的block属性)
    vc2.textBlock = ^(NSString *str) {
        self.label.text = str;      // 处理传回来的值
    };
    
    // 加载界面2
    [self presentViewController:vc2 animated:YES completion:nil];
}

界面2 .h文件

@interface ViewController2 : UIViewController

// 声明block属性
@property (nonatomic, copy) void(^textBlock)(NSString *);

@end

界面2 .m文件:

// 返回界面1
- (IBAction)btnBack:(UIButton *)sender {
    
    // 先判断block是否为空,为空则报错
    if (self.textBlock) {
        // 返回textField里的text
        self.textBlock(self.textField.text);
    }
    
    // 退出界面2
    [self dismissViewControllerAnimated:YES completion:nil];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值