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

请添加图片描述

✅作者简介:大家好我是瓜子三百克,一个非科班出身的技术程序员,还是喜欢在学习和开发中记录笔记的博主小白!
📃个人主页:瓜子三百克的主页
🔥系列专栏:OC语法
🤟格言:作为一个程序员都应该认识到,好的代码是初级工程师都可以理解的代码, 伟大的代码是可以被学习一年计算机专业的新生所理解。
💖如果觉得博主的文章还不错的话,请点赞👍+收藏⭐️+留言📝支持一下博主哦🤞

在这里插入图片描述
让我们一起卷起来吧!!!


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的内存分配

在这里插入图片描述
说明:

1、程序区域(.text区):俗称代码段。特点:一般来说内存地址是比较小的,也就是比较前面,是内存的低地址。
2、数据区域(.data区):数据段。特点:一般放一些全局变量。
3、:动态分配内存的,如alloc出来的对象。特点:需要开发者申请内存,也需要程序员自己管理内存,当然ARC环境内部已经自动帮我们管理了。
4、:放一写局部变量。特点:系统自动分配内存,也会自动销毁内存,当局部变量离开作用域就会自动销毁。

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

还是先公布结果:
在这里插入图片描述对于之前在栈上的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

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

总结以上,不同类型的block调用copy后的结果如下所示:
在这里插入图片描述

**🏆结束语🏆 **

最后如果觉得我写的文章对您有帮助的话,欢迎点赞✌,收藏✌,加关注✌哦,谢谢谢谢!!

在这里插入图片描述

  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瓜子三百克

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值