当前位置: 首页 > news >正文

Objective-C blocks 概要

1.block的使用

1.1什么是block?

Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数。

“带有自动变量”在Blocks中表现为“截取自动变量"
“匿名函数”就是“不带名称的函数”

块,封装了函数调用及调用环境的OC对象

  • block的声明
// 1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:类型别名
typedef void(^BlockType)(void);
@property (nonatomic, copy) BlockType myBlock2;
// 3.
// 返回值类型(^block变量名)(参数1类型,参数2类型,...)
void(^block)(void);
  • block的定义
    // ^返回值类型(参数1,参数2,...){};// 1.无返回值,无参数void(^block1)(void) = ^{};// 2.无返回值,有参数void(^block2)(int) = ^(int a){};// 3.有返回值,无参数(不管有没有返回值,定义的返回值类型都可以省略)int(^block3)(void) = ^int{return 3;};// 以上Block的定义也可以这样写:int(^block4)(void) = ^{return 3;};// 4.有返回值,有参数int(^block5)(int) = ^int(int a){return 3 * a;};
  • block的调用
    // 1.无返回值,无参数block1();// 2.有返回值,有参数int a = block5(2)

2.block的底层数据结构

通过Clang将以下的OC代码转化为C++代码

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

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

转化为C++代码

//参数结构体
struct __main_block_impl_0 {struct __block_impl impl;// block 结构体struct __main_block_desc_0* Desc;// block 的描述对象
/*
block 的构造函数** 返回值:__main_block_impl_0 结构体** 参数一:__main_block_func_0 结构体** 参数二:__main_block_desc_0 结构体的地址** 参数三:flags 标识位
*/__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;// &_NSConcreteStackBlock 表示存储在栈上impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};//block 结构体
struct __block_impl {void *isa;//block 的类型int Flags;int Reserved;void *FuncPtr;// block的执行函数指针,指向__main_block_func_0
};//封装了 block 中的代码
//参数是__main_block_impl_0结构体的指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_03dcda_mi_0);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;// block 所占的内存空间
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
/*** void(^block)(void) = ^{NSLog(@"调用了block");};** 定义block的本质:** 调用__main_block_impl_0()构造函数** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA** __main_block_func_0 封装了block里的代码** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,** 把这个地址赋值给 block*/void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/*** block();** 调用block的本质:** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用*/    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);}return 0;
}

3.block的变量捕获机制

  • 对于全局变量,不会捕获到block内部,访问方为直接访问
  • 对于auto类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,访问方式为值传递
  • 对于static类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,访问方式为指针传递
  • 对于对象类型的局部变量,block会连同修饰符一起捕获

3.1 auto自动变量

将以下 OC 代码转换为 C++ 代码,并贴出部分变动代码

int main(int argc, const char * argv[]) {@autoreleasepool {int age = 10;void(^block)(void) = ^{NSLog(@"%d",age);};block();}return 0;
}
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int age;//生成的变量__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_40c716_mi_0,age);}

可以看出:

  • 在__main_block_impl_0结构体中会自动生成一个相同类型的age变量
  • 构造函数__main_block_impl_0中多出了一个age参数,用来捕获外部的变量

由于传递方式为值传递,所以我们在block外部修饰age变量时,不会影响到block中的age变量

总的来说,所谓“截获自动变量”意味着在执行Block语法时,Block语法表达式所用的自动变量被保存到Block的结构体实例中(即Block自身)中

3.2static类型的局部变量

将以下OC代码转化为C++代码,并贴出部分变动代码

    static int age = 10;void(^block)(void) = ^{NSLog(@"%d",age);};age = 20;block();// 20
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int *age;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int *age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_cb7943_mi_0,(*age));}

可以看出:

  • __main_block_impl_0结构体中生成了一个相同类型的age变量
  • __main_block_impl_0构造函数多了个参数,用来捕获外部的age变量的地址

由于传递方式是指针传递,所以修改局部变量age时,age的值会随之变化

3.3全局变量

将以下OC代码转化为C++代码,并贴出部分变动代码

int height = 10;
static int age = 20;
int main(int argc, const char * argv[]) {@autoreleasepool {void(^block)(void) = ^{NSLog(@"%d,%d",height,age);};block();}return 0;
}
int height = 10;
static int age = 20;struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_7a340f_mi_0,height,age);}

可以看出:

  1. __main_block_impl_0结构体中,并没有自动生成age和height全局变量,也就是说没有将变量捕获到block内部

虽然block语法的匿名函数部分简单地变换为了C语言函数,但从这个变换的函数中访问静态全局变量/全局变量并没有任何改变,可直接使用。

但是静态变量的情况下,转换后的函数原本就设置在含有Block语法的函数外,所以无法从变量作用域访问

为什么局部变量需要捕获,而全局变量不用呢?

  • 作用域的原因,全局变量哪里都可以直接访问,所以不用捕获
  • 局部变量,外部不能直接访问,所以需要捕获
  • auto类型的局部变量可能会销毁,其内存会消失,block将来执行代码的时候不可能再去访问呢块内存,所以捕获其值
  • static变量会一直保存在内存中,所以捕获其地址即可

3.4 _block修饰的变量

3.4.1 _block的使用

默认情况下block是不能修改外面的auto变量,解决办法?

  • 变量用static修饰(原因:捕获static类型的局部变量是指针传递,可以访问到该变量的内存地址
  • 全局变量
  • _block(我们只是希望临时用一下这个变量临时改一下而已,而改为static变量和全局变量会一直在内存中)
3.4.2 _block修饰符
  • _block同于解决block内部无法修改auto变量值的问题;
  • _block不能修饰全局变量,静态变量;
  • 编译器会将_block变量包装成一个对象(struct __Block_byref_age_0byref:按地址传递));
  • 加_block修饰不会改变变量的性质,他还是auto变量;
  • 一般情况,对捕获变量进行赋值(赋值!=使用)操作需要添加_block修饰符,比如给数组添加或删除对象,就不用加_bolck修饰符;
  • 在 MRC 下使用 __block 修饰对象类型,在 block 内部不会对该对象进行 retain 操作,所以在 MRC 环境下可以通过 __block 解决循环引用的问题;

将以下 OC 代码转换为 C++ 代码,并贴出部分变动代码

int main(int argc, const char * argv[]) {@autoreleasepool {__block int age = 10;void(^block)(void) = ^{age = 20;NSLog(@"block-%d",age);};block();NSLog(@"%d",age);}return 0;
}
struct __Block_byref_age_0 {void *__isa;
__Block_byref_age_0 *__forwarding;//持有指向该实例自身的指针int __flags;int __size;int age;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_age_0 *age; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_age_0 *age = __cself->age; // bound by ref(age->__forwarding->age) = 20;NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_75529b_mi_0,(age->__forwarding->age));}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

可以看出:

  • 编译器会将_block修饰的变量包装成一个__Block_byref_age_0结构体对象
  • 以上age = 20 的赋值过程:通过block的__main_block_func_0结构体实例中( __Block_byref_age_0)类型的age指针,找到 __Block_byref_age_0结构体的对象, __Block_byref_age_0结构体对象持有指向实例本身的__forwarding指针,通过成员变量_forwarding访问 __Block_byref_age_0结构体里的age变量,并将值改为20;

在这里插入图片描述

相关文章:

  • golang中fallthrough简介及用法
  • Java学习笔记18——SQLite3数据库安装与使用
  • Java中 常见的开源树库介绍
  • 如何选择好用的ai写作软件?
  • 实体门店运营方案:揭秘行业分类与拓展优质客户之道
  • 华为机考:HJ2 计算某字符出现次数
  • 147.乐理基础-七和弦是什么、七和弦的名字
  • python的数据容器--字符串
  • C++标准库中的多线程编程
  • 什么是VR虚拟现实体验店|VR主题馆加盟|元宇宙文化旅游
  • MongoDB聚合运算符:$derivative
  • 波司登高德康:创新引领品质为先 以匠心擦亮民族品牌
  • 智慧城市与绿色出行:共同迈向低碳未来
  • ReentrantReadWriteLock学习
  • javaEE13(网站第8章两个课后题)
  • 【译】JS基础算法脚本:字符串结尾
  • 分享的文章《人生如棋》
  • “大数据应用场景”之隔壁老王(连载四)
  • 「译」Node.js Streams 基础
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • CentOS从零开始部署Nodejs项目
  • emacs初体验
  • Flannel解读
  • js作用域和this的理解
  • k个最大的数及变种小结
  • oldjun 检测网站的经验
  • React as a UI Runtime(五、列表)
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • spring cloud gateway 源码解析(4)跨域问题处理
  • uni-app项目数字滚动
  • Web标准制定过程
  • windows-nginx-https-本地配置
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 面试总结JavaScript篇
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • ​LeetCode解法汇总518. 零钱兑换 II
  • ​油烟净化器电源安全,保障健康餐饮生活
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (C++17) std算法之执行策略 execution
  • (c语言)strcpy函数用法
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (三)docker:Dockerfile构建容器运行jar包
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (转)Mysql的优化设置
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • .bat批处理出现中文乱码的情况
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .Net(C#)常用转换byte转uint32、byte转float等