• [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]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    打印结果:
    请添加图片描述
    三个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;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

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

    {
        //等号左边:auto形局部变量,存放在栈区;
        //等号右边:常量,存放在数据区;
        int age = 10;
        //等号左边:auto形局部变量(指针),存放在栈区;
        //等号右边:auto形局部变量地址,存放在栈区;
        int *agePtr = &age;
        //等号左边:auto形局部变量(指针),存放在栈区;
        //等号右边:alloc开辟的对象,存放在堆区;
        NSObject *objc = [[NSObject alloc] init];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    三种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]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    结果:
    请添加图片描述
    如果是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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出:
    请添加图片描述
    说明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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

  • 相关阅读:
    应用回归分析,第4章,思考与练习,答案,r语音代码
    近期局势较多变化 适合黄金代理入场吗?
    企业大语言模型智能问答的底层基础数据知识库如何搭建?
    git commit 提交信息规范
    MC Layer Target
    Docker网络问题:容器无法访问外部网络
    从头学前端-HTML简介
    C语言之字符串
    基于CentOS 7.6安装及配置APISIX 3.0环境
    K8s小白?应用部署太难?看这篇就够了!
  • 原文地址:https://blog.csdn.net/weixin_52192405/article/details/125910498