Block

251 阅读5分钟


什么是block

  • block 实际上就是 Objective-C 语言对于闭包的实现。
  • 闭包:闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。

  • Block的几种形式
    • 分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式
      其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区

    1、不使用外部变量的block是全局block

    比如:

        NSLog(@"%@",[^{
            NSLog(@"globalBlock");
        } class]);
    

    输出:

    __NSGlobalBlock__
    

    2、使用外部变量并且未进行copy操作的block是栈block

    比如:

      NSInteger num = 10;
        NSLog(@"%@",[^{
            NSLog(@"stackBlock:%zd",num);
        } class]);
    

    输出:

    __NSStackBlock__
    

    日常开发常用于这种情况:

    [self testWithBlock:^{
        NSLog(@"%@",self);
    }];
    
    - (void)testWithBlock:(dispatch_block_t)block {
        block();
    
        NSLog(@"%@",[block class]);
    }
    

    3、对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block

    • 比如堆1中的全局进行copy操作,即赋值:
    void (^globalBlock)(void) = ^{
            NSLog(@"globalBlock");
        };
    
     NSLog(@"%@",[globalBlock class]);
    

    输出:

    __NSGlobalBlock__
    

    仍是全局block

    • 而对2中的栈block进行赋值操作:
    NSInteger num = 10;
    
    void (^mallocBlock)(void) = ^{
    
            NSLog(@"stackBlock:%zd",num);
        };
    
    NSLog(@"%@",[mallocBlock class]);
    

    输出:

    __NSMallocBlock__
    

    对栈blockcopy之后,并不代表着栈block就消失了,左边的mallock是堆block,右边被copy的仍是栈block
    比如:

    [self testWithBlock:^{
        
        NSLog(@"%@",self);
    }];
    
    - (void)testWithBlock:(dispatch_block_t)block
    {
        block();
        
        dispatch_block_t tempBlock = block;
        
        NSLog(@"%@,%@",[block class],[tempBlock class]);
    }
    

    输出:

    __NSStackBlock__,__NSMallocBlock__
    
    • 即如果对栈Block进行copy,将会copy到堆区,对堆Block进行copy,将会增加引用计数,对全局Block进行copy,因为是已经初始化的,所以什么也不做。
    _______________________________________________________________








    _______________________________________________________________

    _NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:

    int main()
    {
        ^{ printf("Hello, World!\n"); } ();
        return 0;
    }
    

    _NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:

      {
      char a = 'A';
        ^{ printf("%c\n",a); } ();
    }
    _NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁



        //1  block的第一种形式  NSGlobalBlock 全局block 根函数一样 存储在代码区
    //    void (^myblock)() = ^{
    //        NSLog(@"hello");
    //    };
    //
    //    NSLog(@"%@",myblock);
        
        //block的本质是结构体
        //2  block的第二种形式  NSStackBlock 栈block  存储在栈上
    //    __block int n = 5;
    //    void (^myblock)() = ^{
    //        n = 6;
    //        NSLog(@"hello");
    //        NSLog(@"%d",n);
    //    };
    //    
    //    NSLog(@"%@",myblock);
        
        //3 block的第三种形式  NSMallocBlock  堆blcok  存储在堆上  对栈block做一次copy操作
            __block int n = 5;
            void (^myblock)() = ^{
                n = 6;
                NSLog(@"hello");
                NSLog(@"%d",n);
            };
        NSLog(@"%@",[myblock copy]);
    

block的基本使用

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
  • block的底层结构如下图

block的底层结构

typedef 定义Block

实际开发中,经常需要把block作为一个属性,我们可以定义一个block

eg:定义一个有参有返回值的block

typedef int (^MyBlock)(int a, int b);

定义属性的时候,如下即可持有这个block

@property (nonatomic,copy) MyBlock myBlockOne;

block实现

self.myBlockOne = ^int(int a, int b) {
      return a + b;
};

调用

self.myBlockOne(2, 5);


block 结构分析

如下代码

int age = 20;
void (^block)(void) =  ^{
     NSLog(@"age is %d",age);
 };
        
block();
  • 打开终端,cd到main.h目录下

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

生成main.cpp

int age = 20;

// block的定义
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
// block的调用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

上面的代码删除掉一些强制转换的代码就就剩下如下所示

int age = 20;
void (*block)(void) = &__main_block_impl_0(
						__main_block_func_0, 
						&__main_block_desc_0_DATA, 
						age
						);
// block的调用
block->FuncPtr(block);
复制代码

看出block的本质就是一个结构体对象,结构体__main_block_impl_0代码如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
    //构造函数(类似于OC中的init方法) _age是外面传入的
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    //isa指向_NSConcreteStackBlock 说明这个block就是_NSConcreteStackBlock类型的
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

图片表示


block类型

block也是一个OC对象

在进行分析block类型之前,先明确一个概念,那就是block中有isa指针的,block是一个OC对象,例如下面的代码

void (^block)(void) =  ^{
      NSLog(@"123");
};

NSLog(@"block.class = %@",[block class]);
NSLog(@"block.class.superclass = %@",[[block class] superclass]);
NSLog(@"block.class.superclass.superclass = %@",[[[block class] superclass] superclass]);
NSLog(@"block.class.superclass.superclass.superclass = %@",[[[[block class] superclass] superclass] superclass]);
复制代码

输出结果为

iOS-block[18429:234959] block.class = __NSGlobalBlock__
iOS-block[18429:234959] block.class.superclass = __NSGlobalBlock
iOS-block[18429:234959] block.class.superclass.superclass = NSBlock
iOS-block[18429:234959] block.class.superclass.superclass.superclass = NSObject

复制代码

说明了上面代码中的block的类型是__NSGlobalBlock,继承关系可以表示为__NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject

block有3种类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

  • __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
  • __NSStackBlock__ ( _NSConcreteStackBlock )
  • __NSMallocBlock__ ( _NSConcreteMallocBlock )

其中三种不同的类型和环境对应如下

block类型 环境
__NSGlobalBlock__ 没有访问auto变量
__NSStackBlock__ 访问了auto变量
__NSMallocBlock__ __NSStackBlock__调用了copy

其在内存中的分配如下对应

每一种block copy后的

_NSConcreteStackBlock   存储在栈        copy  后 从栈复制到堆

_NSConcreteMallocBlock    存储在堆         copy 后   引用计数加1

_NSConcreteGlobalBlock    存储在数据存储data区   copy后什么也不做

这三个

int age = 10;
int main (int argc, const char * argv[]){
   @autoreleasepool {
        int a = 100;

        NSLog(@"数据段(全局区):age %p", &age);
        NSLog(@"栈: %p", &a);
        NSLog(@"堆: %p", [[NSObject alloc] init]);
       NSLog(@"类对象的内存 class在数据段: %p", [Person class]);
    }}

ARC环境下,编译器会根据如下情况 自动将栈上的Block复制到堆上:
1. block 作为函数返回值时
2. 将block 赋值给strong指针时
3. BLock作为cocoa api 中方法名含有Usingblock的方法参数时
4. block作为GCD api的方法参数时

ARC下 block 属性建议写法

@property(copy, nonatomac) void(^block) (void);