zoukankan      html  css  js  c++  java
  • iOS学习笔记之Block

    写在前面

    学习iOS开发的过程中,在很多场合都遇到了Block。说实话,虽然自己依葫芦画瓢的将Block“拿来”用着,但这种“拿来主义”与学习时应持有的探索精神是背道而驰的。所以还是决定花些时间整理一下这个知识点。总的来说,我感觉Block的行为还是很复杂的,本文中的整理也不一定正确,欢迎指正。

    Block简介

    Block是一种特殊的数据类型,它本质上就是一个代码片段,跟普通的OC变量相似,它可以被声明、赋值和传递。
    下面就Block的各种使用形式做一个我理解到的归纳。

    Block作为局部变量

    Block格式

    Block基本声明格式为:

    返回值类型(^block名称)(参数列表);
    例如:
    int (^squareBlock)(int);
    上述代码声明了一个返回值和参数类型都为int的,名为squareBlock的Block变量,其中^符号就是Block变量的标识。(如同*是指针的标识)

    Block的赋值:

    下面的代码演示了为squareBlock变量赋值的过程

    squareBlock = ^(int a){
                int result = a*a;
                return result;
            };
    

    我们也可以在Block声明的时候直接赋值,例如:

    int (^squareBlock)(int) = ^(int a){
        return a*a;
    };
    

    当Block的返回值和参数列表为空时,在赋值阶段可以省略为空的部分,如:

    void (^blankBlock)(void);
    

    上述blankBlock的赋值可以有以下几种等价形式:

    blankBlock = ^void(void){};
    blankBlock = ^    (void){};
    blankBlock = ^void()    {};
    blankBlock = ^void      {};
    blankBlock = ^          {};
    

    上述代码的空格在实际应用时应该去掉,之所以保留是为了便于学习时理解记忆。

    Block调用:

    我们以squareBlock为例,其调用格式为:

    int result = squareBlock(4);
    NSLog(@"result is : %d", result);
    

    可以看到,squareBlock的调用方式和C++中函数的调用方式基本相同。

    Block与typedef

    前面我们说过,Block与变量相似,因此它也可以被传递。由于Block的写法相对比较繁琐,当它作为函数参数时,我们一般会用typedef为其定义有意义的名字,方便书写和阅读。其定义格式为:

    typedef <#returnType#>(^<#name#>)(<#arguments#>);
    

    仍然以squareBlock为例:

    typedef int(^squareBlock)(int);
    

    此时squareBlock被声明为了一个Block类型(类比int类型),其使用如下:

    //类比 int num = 10;
    //类型        变量名           值
     squareBlock mySquareBlock = ^int (int a)
            {
                return a*a;
            };
          
     int result = mySquareBlock(4);
     NSLog(@"result is : %d", result);
    

    Block作为属性

    在下面的例子中,我们定义了一个Aclass类,在这个类中添加属性
    Block作为属性时,其声明如下:

    @interface Aclass : NSObject
    //block声明为属性,用copy关键字
    @property (copy, nonatomic) void(^logBlock)();
    
    @end
    

    在类中的初始化和调用

    @implementation Aclass
    
    - (id)initWithBlock:(void (^)())logblock
    {
        self = [super init];
        if (self)
        {
            _logBlock = logblock;
        }
        
        return self;
    }
    
    - (void)print
    {
        self.logBlock();
    }
    
    @end
    

    在main函数中的赋值:

      Aclass *objA = [[Aclass alloc]initWithBlock:^{
          NSLog(@"this string is passed from main.");
      }];
    
     [objA print];
    

    执行截图:

    其用法和普通属性相似,只是其调用是由Block名+参数列表一起出现的(跟C++中的函数类似)。
    当Block作为属性时,一般会先用typedef给Block一个名称,然后在类的属性中定义,如下:

    typedef void(^printBlock)(NSString *);
    
    @interface Aclass : NSObject
    //block声明为属性,用copy关键字
    @property (copy, nonatomic)printBlock myPrintBlock;
    
    @end
    

    Block作为函数参数

    在Aclass中添加带有Block参数的方法,如下:

    - (void)printWithBlock:(void(^)())printblock;
    

    其函数内容如下:

    - (void)printWithBlock:(void(^)())printblock
    {
        if (printblock)
        {
            printblock();
        }
    }
    

    在main函数中创建Aclass的对象objA,并调用有Block参数的printWithBlock:方法,如下:

    Aclass *objA = [[Aclass alloc]init];
    [objA printWithBlock:^{
        NSLog(@"this string is typed in main.");
    }];
    

    执行截图:

    在这个例子中,main函数是调用方,它调用了Aclass的printWithBlock:方法并传入Block代码段供其执行。这里的Block已经是一种回调的用法了。

    Block作为回调

    为了说明Block的回调,我们创建Aclass和Bclass两个类,在Aclass中提供一个含有Block参数的方法,在Bclass中调用上述方法,代码如下:
    1、Aclass.h

    @interface Aclass : NSObject
    
    - (void)funInAclass:(void(^)(NSString *))callback;
    
    @end
    

    2、Aclass.m

    - (void)funInAclass:(void(^)(NSString *))callback
    {
        NSLog(@"funInAclass start.");
        NSString *strA = @"strA";
        
        if (callback)
        {
            callback(strA);
        }
        
        NSLog(@"funInAclass end.");
    }
    

    3、Bclass.h

    @interface Bclass : NSObject
    
    - (void)funInBclass;
    
    @end
    

    4、Bclass.m

    @implementation Bclass
    
    - (void)funInBclass
    {
        NSLog(@"funInBclass start");
        Aclass *objA = [[Aclass alloc]init];
        [objA funInAclass:^(NSString *str) {
            NSLog(@"In funInBclass's block");
            NSLog(@"str is: %@",str);
        }];
        
        NSLog(@"funInBclass end.");
    }
    
    @end
    

    5、main函数

    int main(int argc, const char * argv[])
    {
        @autoreleasepool
        {
           Bclass *objB = [[Bclass alloc] init];
           [objB funInBclass];
        }
        return 0;
    }
    

    6、执行结果

    Block回调的理解

    从上面的这个例子可以看出,funInAclass:方法接收一个Block类型的参数,在Bclass的funInBcalss:中调用Aclass的funInAclass:方法时,传入了Block的具体内容。
    在funInAclass中,执行到if语句时,判断自身的callback参数是否有值,如果有就执行callback这个Block,因此会执行funInBclass中的那段Block代码。也就是说,funInBclass调用了funInAclass,而funInBclass又通过Block执行了funInBclass中的代码(funInAclass执行了funInBclass传递给他的代码),这就是Block回调。

    Block的类型

    Block有三种常见类型:
    _NSConcreteGlobalBlock:全局静态Block,不会访问外部变量,不涉及任何拷贝
    _NSConcreteStackBlock:保持在栈中,函数返回时被销毁;
    _NSConcreteMallocBlock:保存在堆中,当引用计数为0时被销毁

    总结

    在调用Block时,需要对Block做非空判断,否则会崩溃;
    用Block实现回调的好处是代码直观,在调用函数时就可以直接写后续的执行代码,而不需要向delegate那样换到另一个地方写回调函数。(例如上面的例子中,funInBclass在调用funInAclass时就直接把回调代码写在了调用的地方,方面阅读);
    使用Block可能会造成循环引用;

  • 相关阅读:
    30 Day Challenge Day 4 | Leetcode 102. Binary Tree Level Order Traversal
    30 Day Challenge Day 4 | Hackrank
    30 Day Challenge Day 4 | Leetcode 104. Maximum Depth of Binary Tree
    30 Day Challenge Day 3 | Leetcode 145. Binary Tree Postorder Traversal
    30 Day Challenge Day 3 | Leetcode 144. Binary Tree Preorder Traversal
    30 Day Challenge Day 2 | Leetcode 1302. Deepest Leaves Sum
    30 Day Challenge Day 2 | Leetcode 206. Reverse Linked List
    30 Day Challenge Day 1 | Leetcode 107. Binary Tree Level Order Traversal II
    30 Day Challenge Day 1 | Hackerrank
    刷题 | Leetcode 901. Online Stock Span | Stack
  • 原文地址:https://www.cnblogs.com/scut-linmaojiang/p/iOS-Block.html
Copyright © 2011-2022 走看看