iOS开发之基础篇(14)—— Block

版本

Xcode 9.1

block简介

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

block代码结构图

注:图片来自这里

示例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);

结果如下:

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

结果:

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

结果:

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

打印结果:

原理分析:

在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);

打印结果:

原理分析:

在局部变量前使用__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);

打印结果:

原理分析:

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

打印结果:

原理分析:

全局变量所占用的内存只有一份,供所有函数(方法)共同调用,在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];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,791评论 10 69
  • iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,B...
    smile刺客阅读 2,328评论 2 26
  • Block使用场景,可以在两个界面的传值,也可以对代码封装作为参数的传递等。用过GCD就知道Block的精妙之处。...
    Coder_JMicheal阅读 716评论 2 1
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,744评论 5 61
  • 在介绍Block之前通过一个简单的应用场景认识下Block 场景描述如下:TableView上面有多个Custom...
    黑_白_灰阅读 1,378评论 4 29