[OC学习笔记]Block三种类型

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[OC学习笔记]Block三种类型相关的知识,希望对你有一定的参考价值。

Block的三种类型

block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。
使用如下代码:

void testBlockType(void) 
    int age = 10;
    
    void(^block1)(void) = ^
        NSLog(@"block1----");
    ;
    
    void(^block2)(void) = ^
        NSLog(@"block2----%d", age);
    ;
    
    NSLog(@"block1-----%@ %@ %@ %@", [block1 class], [[block1 class] superclass], [[[block1 class] superclass] superclass], [[[[block1 class] superclass] superclass] superclass]);
    
    NSLog(@"block2-----%@ %@ %@ %@", [block2 class], [[block2 class] superclass], [[[block2 class] superclass] superclass], [[[[block2 class] superclass] superclass] superclass]);
    
    NSLog(@"block-----%@ %@ %@ %@", [^
        NSLog(@"block----%d", age);
     class], [[^
        NSLog(@"block----%d", age);
     class] superclass], [[[^
        NSLog(@"block----%d", age);
     class] superclass] superclass], [[[[^
        NSLog(@"block----%d", age);
     class] superclass] superclass] superclass]);

打印结果:

三个block的类型分别为:__NSGlobalBlock____NSMallocBlock____NSStackBlock__上述三种类型最终都是继承自NSBlock,而NSBlock又是继承自NSObject:此处又进一步说明block其实就是一个OC对象。

  • NSMallocBlock(_NSConcreteMallocBlock) 对象存储在堆区
  • NSStackBlock(_NSConcreteStackBlock) 对象存储在栈区
  • NSGlobalBlock(_NSConcreteGlobalBlock)对象存储在数据区

换到MRC环境下试一下:

MRC模式下,三种block类型:__NSGlobalBlock____NSStackBlock____NSStackBlock__,为什么中间的类型由malloc变成了stack?这是因为ARC自动帮助我们对block进行了copy操作。

分析

我们先尝试转化C++代码查看类型:

struct __testBlockType_block_impl_0 
  struct __block_impl impl;
  struct __testBlockType_block_desc_0* Desc;
  __testBlockType_block_impl_0(void *fp, struct __testBlockType_block_desc_0 *desc, int flags=0) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  
;
struct __testBlockType_block_impl_1 
  struct __block_impl impl;
  struct __testBlockType_block_desc_1* Desc;
  int age;
  __testBlockType_block_impl_1(void *fp, struct __testBlockType_block_desc_1 *desc, int _age, int flags=0) : age(_age) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  
;
struct __testBlockType_block_impl_2 
  struct __block_impl impl;
  struct __testBlockType_block_desc_2* Desc;
  int age;
  __testBlockType_block_impl_2(void *fp, struct __testBlockType_block_desc_2 *desc, int _age, int flags=0) : age(_age) 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  
;

发现都是_NSConcreteStackBlock类型。不能正确反应block的实质类型(可能是LLVM编译器版本的问题,而clang又是LLVM的一部分)。
程序分区除了堆区需要程序员手动管理内存外,其他区都由系统自动管理。


    //等号左边:auto形局部变量,存放在栈区;
    //等号右边:常量,存放在数据区;
    int age = 10;
    //等号左边:auto形局部变量(指针),存放在栈区;
    //等号右边:auto形局部变量地址,存放在栈区;
    int *agePtr = &age;
    //等号左边:auto形局部变量(指针),存放在栈区;
    //等号右边:alloc开辟的对象,存放在堆区;
    NSObject *objc = [[NSObject alloc] init];

三种block类型(global、malloc、stack),从字面理解,可以推断依次存放在数据区、堆区、栈区;blcok1没有访问任何变量,后两个block都访问量变量age,而age是一个auto类型的局部变量。

int height = 150;

void testBlockType(void) 
    int age = 10;
    static int weight = 80;
    
    void(^block1)(void) = ^
        NSLog(@"block1----");
    ;
    
    void(^block2)(void) = ^
        NSLog(@"block2----%d", age);
    ;
    
    void(^block3)(void) = ^
        NSLog(@"-----%d", weight);
    ;
    
    void(^block4)(void) = ^
        NSLog(@"-----%d", height);
    ;
        
    NSLog(@"%@ %@", [block3 class], [block4 class]);

结果:

如果是static修饰的局部变量,或者访问全局变量,则block的类型都是__NSGlobalBlock__,那么基本上可以肯定,block的类型可以取决于其访问的变量的属性。
因为auto类型的局部变量是存放在栈区的,而block要访问该变量,经前面分析,block会将该变量捕获到block结构体内部,即重新开辟内存来存放该局部变量(相当于copy操作,但不是copy),那么此时的block自己是存放在哪个区呢?前面说了,auto类型的局部变量一定是存放在栈区的,这点毋庸置疑,而block虽然新开辟内存来存放该变量,但改变不了该变量是一个auto类型的局部变量的属性,因此此时的block也只能存放在栈区;既然存放在栈区,则访问的变量作用域仅限于离其最近的大括号范围内,超出则被自动释放。
MRC下测试下面代码:

void(^block4)(void);

void test3()

    int age = 10;
    
    block4 = ^
        NSLog(@"----%d", age);
    ;

int main(int argc, const char * argv[]) 
    @autoreleasepool 
        test3();
        block4();
//        NSLog(@"%@", [block4 class]);

    
    return 0;

输出:

说明age已经被自动释放,block再次调用时,访问的是被废弃的内存。手动copy

void(^block4)(void);

void test3()

    int age = 10;
    
    block4 = [^
        NSLog(@"----%d", age);
     copy];

int main(int argc, const char * argv[]) 
    @autoreleasepool 
        test3();
        block4();
        NSLog(@"%@", [block4 class]);
    
    return 0;

结果:

因为copy是把age的值直接拷贝到了一块新的内存区域,而我们知道copy操作开辟的内存必定是在堆区(同时,block的类型由之前的__NSStackBlock__类型变为__NSMallocBlock__类型)。因此,防止一个auto类型的局部变量自动释放的方法,就是将其copy到堆区进行手动管理,达到对其生命周期可控的目的(所以记得要释放[block release])——这是MRC模式下的手动管理内存,而在ARC模式下系统会自动管理内存(copyrelease)。

以上是关于[OC学习笔记]Block三种类型的主要内容,如果未能解决你的问题,请参考以下文章

(Object-C)学习笔记 --OC的Block

ios 中的block应用

iOS-block三种类型详解

ios中block的三种形式

32-oc block

【OC语法】block的循环引用