block有哪些对象类型,你知道了吗?

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 从block的本质这篇文章中,我们已经知道block的本质是一个OC对象。既然是OC对象。那肯定有对象类型,还是从block的本质这篇文章中搜索impl.ipa字段,发现block指向了一个对象 _NSConcreteStackBlock。那么block还有什么类型呢?怎么实现呢?它们又是分配在哪些内存区呢?让我们带着这些疑问往下看。


1、探寻block类型

既然block是对象,可以通过调用class方法或者isa指针查看具体类型,通过superclass获取其父类,最终都是继承自NSBlock类型


#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"Hello");
        };
        NSLog(@"----%@",[block class]);
        NSLog(@"----%@",[[block class] superclass]);
        NSLog(@"----%@",[[[block class] superclass] superclass]);
        NSLog(@"----%@",[[[[block class] superclass] superclass] superclass]);
        block();
            }
    return 0;
}


2022-05-07 23:46:46.041962+0800 block-01[68178:2905102] ----__NSGlobalBlock__
2022-05-07 23:46:46.042535+0800 block-01[68178:2905102] ----NSBlock
2022-05-07 23:46:46.042595+0800 block-01[68178:2905102] ----NSObject
2022-05-07 23:46:46.042635+0800 block-01[68178:2905102] ----(null)

从上面的打印日志可以知道:


block对象类型:NSGlobalBlock_

父类:NSBlock

父类的父类:NSObject


2、获取block具体类型


#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block1)(void) = ^{
            NSLog(@"Hello1");
        };
        int age = 10;
        void(^block2)(void) = ^{
            NSLog(@"Hello2:%d",age);
        };
        NSLog(@"----%@",[block1 class]);
        NSLog(@"----%@",[block2 class]);
        NSLog(@"----%@",[[^{
            NSLog(@"Hello3:%d",age);
        } class] class]);
    }
    return 0;
}


2022-05-07 23:59:21.573913+0800 block-01[68605:2913488] ----__NSGlobalBlock__
2022-05-07 23:59:21.574601+0800 block-01[68605:2913488] ----__NSMallocBlock__
2022-05-07 23:59:21.574695+0800 block-01[68605:2913488] ----__NSStackBlock__

由以上可以看到 block 有3种类型:


NSGlobalBlock ( _NSConcreteGlobalBlock )

NSStackBlock ( _NSConcreteStackBlock )

NSMallocBlock ( _NSConcreteMallocBlock )


但是编译完毕之后,情况又不一样了:


struct __main_block_impl_0 {
  struct __block_impl impl;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
  }
};
struct __main_block_impl_1 {
  struct __block_impl impl;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
  }
};
struct __main_block_impl_2 {
  __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
  }
};


1、发现编译完毕之后,block的类型都是 _NSConcreteStackBlock。

2、这里指向的对象类型名称(编译出来)跟打印出来的类型名称(动态运行后)也是有所不一样的。但是他们是一一对应的。


那么为什么打印出来的跟编译出来的类型不一样?


首先要明白我们的程序最终是以动态运行之后才是准确的。

那么从编译完之后,动态运行期间,还可能根据参数动态调整block对象类型。


3、不同对象类型block的内存分配

2c159fa8a6e24b07abf9649e9e1db531.png

说明:


1、程序区域(.text区):俗称代码段。特点:一般来说内存地址是比较小的,也就是比较前面,是内存的低地址。

2、数据区域(.data区):数据段。特点:一般放一些全局变量。

3、堆:动态分配内存的,如alloc出来的对象。特点:需要开发者申请内存,也需要程序员自己管理内存,当然ARC环境内部已经自动帮我们管理了。

4、栈:放一写局部变量。特点:系统自动分配内存,也会自动销毁内存,当局部变量离开作用域就会自动销毁。


4、如何实现不同对象类型block?

还是先公布结果:

13e68f5cbe1c4072a2b4ab3d91196c3a.png

对于之前在栈上的block,copy到堆上时。当不再使用的时候需要调用 release 释放内存。当然如果我们的编译环境时 ARC 环境,是不需要调用该方法释放,系统会自动管理。


栈空间的 block 不会保持外部变量的引用


代码实现:


注意:这个要在MRC环境下编译。因为ARC环境下编译,它会已经帮我们做了好多事情。

Build Setting 下面,搜索 Automatic Reference Counting 改成NO就可以了。


4.1、NSGlobalBlock 数据区的block

实现条件:block内部没有访问auto就可以。


代码示例:


#import <Foundation/Foundation.h>
void(^block)(void);
void test() {
    block = ^{
        NSLog(@"Hello1");
    };
    NSLog(@"block:%@",[block class]);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}


打印日志:


2022-05-08 01:43:02.443356+0800 block-01[72187:2987373] block:__NSGlobalBlock__
2022-05-08 01:43:02.443825+0800 block-01[72187:2987373] Hello1

4.2、NSStackBlock 栈区的block

实现条件:block内部访问了auto变量


#import <Foundation/Foundation.h>
void(^block)(void);
void test() {
    int age = 10;
    block = ^{
        NSLog(@"Hello1:%d",age);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
     }
    return 0;
}


打印日志:


2022-05-08 01:15:42.458205+0800 block-01[71217:2967549] Hello1:-1074793848

发现运行后的结果,取到的age值是一个脏数据,并不是我们想要的。

原因分析:


1、test函数内部的变量是存放在栈区,待函数运行完毕,内部的对象会全部被销毁。

2、所以在执行block内部函数时,age变量已经销毁,取出来的值一个乱值。


那么怎么处理这个问题呢?


block 方法放到堆上, 即将 __NSStackBlock__ 对象类型升级为 __NSMallocBlock__ 对象类型,实现方式也很简单,直接在后block后面调用一次copy,那么这时返回的block就是在堆上的block。 这样就不会被自动销毁,想要什么时候销毁,由我们程序员自己决定。


具体验证看下一部分。


4.3、NSMallocBlock 堆区的block

实现条件:block内部访问了auto变量,并且执行了一次copy操作

代码实现:


#import <Foundation/Foundation.h>
void(^block)(void);
void test() {
    int age = 10;
    block = [^{
        NSLog(@"Hello1:%d",age);
    } copy];
    NSLog(@"block:%@",[block class]);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        // 此时调用的block时堆上的block
        block();
     }
    return 0;
}

打印日志:


2022-05-08 01:34:07.307367+0800 block-01[71852:2980649] block:__NSMallocBlock__
2022-05-08 01:34:07.308459+0800 block-01[71852:2980649] Hello1:10

结果分析:

从以上知道,block被放到了堆上,age也能正常取值。


4.4、不同对象类型的block 与copy之间的关系

如果数据区的block执行了copy操作呢,会是什么结果?我们可以尝试一下:

代码示例:


#import <Foundation/Foundation.h>
void(^block)(void);
void test() {
    block = [^{
        NSLog(@"Hello1");
    } copy];
    NSLog(@"block:%@",[block class]);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}


2022-05-08 01:50:10.096000+0800 block-01[72430:2991254] block:__NSGlobalBlock__
2022-05-08 01:50:10.103313+0800 block-01[72430:2991254] Hello1

ca061b1b9d174d26be90213999982d9b.png发现block还是 __NSGlobalBlock__ 类型,内存分配在数据区。


总结以上,不同类型的block调用copy后的结果如下所示:


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
开发框架 .NET 编译器
C# 中的记录(record)类型和类(class)类型对比总结
C# 中的记录(record)类型和类(class)类型对比总结
|
3月前
|
传感器 开发框架 JSON
聊聊 C# dynamic 类型,并分享一个将 dynamic 类型变量转为其它类型的技巧和实例
聊聊 C# dynamic 类型,并分享一个将 dynamic 类型变量转为其它类型的技巧和实例
152 0
|
6月前
|
调度
A包含B,用block实现A和B互相调用更新数据
A包含B,用block实现A和B互相调用更新数据
34 0
|
6月前
|
JavaScript
为什么data属性是一个函数而不是一个对象?
为什么data属性是一个函数而不是一个对象?
83 1
|
存储 关系型数据库 数据库
Data dictionary header(2) --系统表空间结构(三十四)
Data dictionary header(2) --系统表空间结构(三十四)
|
JSON C# 数据格式
C# 使用dynamic类型来访问JObject对象
原文:C# 使用dynamic类型来访问JObject对象 dynamic是C#里面的动态类型,可在未知类型的情况访问对应的属性,非常灵活和方便。 使用Json.Net可以把一个Json字符串转换成一个JObject对象,如果有已知强类型,如果有已知对应的强类型,可以直接转成对应的类型。
1362 0
|
SQL 存储 关系型数据库
LOB类型数据的MOVE
近日,新接手了一个数据库。检查某系统的时候发现,部分数据表存储在USERS表空间下了。我们怎么对LOB进行管理呢?
1579 0
|
Linux C语言
利用__attribute__((section()))构建初始化函数表【转】
转自: https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652663356&idx=1&sn=779762953029c0e0946c22ef2bb0b754&chksm=810f28a1b678a1b747520ba3ee47c9e...
1760 0