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

Blocks —— 《Objective-C高级编程 iOS与OS X多线程和内存管理》

目录

    • Blocks概要
      • 什么是Blocks
      • OC转C++方法
      • 关于几种变量的特点
    • Blocks模式
      • Block语法
      • Block类型 变量
      • 截获局部变量值
      • __block说明符
      • 截获的局部变量
    • Blocks的实现
      • Block的实质


Blocks概要

什么是Blocks

Blocks是C语言的扩充功能,即带有局部变量的匿名函数。

顾名思义,不带有名称的函数,C语言的标准可不允许存在这样的函数。就算是使用函数指针调用函数也需要知道函数名称。

OC转C++方法

因为需要看Block操作的C++源码,所以要知道转换的方法:

打开终端,cd到OC源文件.m所在的文件夹,输入clang -rewrite-objc 文件名称.m,就会在当前文件夹内自动生成对应的.cpp文件.

关于几种变量的特点

C语言函数中可能使用的变量:

  • 函数参数
  • 自动变量(局部变量)
  • 静态变量(静态局部变量)
  • 静态全局变量
  • 全局变量

而且,由于存储区域特殊,这其中有三种变量是可以在任何时候以任何状态调用的(在函数的多次调用之间能够传递值的变量):

  • 静态变量
  • 静态局部变量
  • 全局变量

虽然这些变量的作用域不同,但在整个程序中,一个变量总保持在一个内存区域。而其他两种虽然有各自相应的作用域,超过作用域后,会被销毁。

Blocks模式

Block语法

完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同:

  1. 没有函数名,即匿名函数;
  2. 带有^,因为iOS、Mac OS应用程序的源代码中将大量使用Block,所以插入该记号便于查找。

Block语法的BN范式:^ 返回值类型 参数列表 表达式。例如:

^int (int value, int count) {return count * value;}

可省略返回值类型

^(int value, int count) {return count * value;}

省略返回值类型的情况下:

  • 表达式中return的类型就是返回类型;
  • 表达式中无return语句说明是void类型;
  • 表达式中含有多个return语句时,所有return的返回值类型必须相同。

可省略参数列表,如果不使用参数:

^void (void) {printf("Blocks\n");}
//省略形式
^{printf("Blocks\n");}

Block类型 变量

定义C语言函数时,可以将所定义的函数的地址赋值给函数指针类型的变量:

int func(int count) {return count + 1;
}
int (*funcptr) (int) = &func;

同样的,Block是一种数据类型,可将Block语法赋值给声明为Block类型的变量:

//声明Block类型变量仅仅是将声明函数指针类型变量的*变为^
int (^blockName) (int);//赋值(Block内容的实现)
int (^blockName) (int) = ^(int count) {return count + 1;
};

如果我们在项目中经常使用某种相同类型的block,可以用typedef抽象出这种类型的Block:

typedef int (^AddOneBlock) (int count);
AddOneBlock blockName = ^(int count) {return count + 1;};

typedef给Block起别名,使得block的赋值和传递变得相对方便,因为block一经抽象出来了:

typedef int (^block_t) (int);//block作为参数
void func(int (^blockName) (int));
//简化后
void func(block_t blockName);//block作为返回值
int (^func() (int)) {return ^(int count) {return count + 1;};
}
//简化后
block_t func() { ... }

Block类型变量可完全像通常的C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block指针类型变量:

typedef int (^block_t) (int);
block_t blockName = ^(int count) {return count + 1;};
block_t* blockptr = &blockName;//int result = blockName(10);
int result = (*blockptr)(10);

截获局部变量值

int a = 20;
int b = 10;void (^blockName)(void) = ^{printf("%d, %d\n", a, b);
};blockName();a++;
b++;printf("%d, %d\n", a, b);  //21, 11
blockName();  //20, 10

可以看到,使用Block时,还可以使用Block外部的局部变量。而一旦在Block内部使用了其外部变量,这些变量就会被Block保存(即被截获),从而在执行块时使用。

__block说明符

实际上,局部变量值截获只能保存执行Block语法瞬间的值,保存后就不能改写改值:

请添加图片描述

可以看到,当修改截获的局部变量值时,会产生编译错误。

若想实现在Block内部将值赋给外部的局部变量,需要在该局部变量上附加__block说明符:

__block int a = 20;
void (^blockName)(void) = ^{a = 27;printf("%d\n", a);
};
blockName();  //27
a++;
printf("%d\n", a);  //28
blockName();  //27

小结

  • 修改Block外部的局部变量,Block内部被截获的局部变量不受影响;
  • 修改Block内部的局部变量,编译不通过;
  • 附有 __block说明符的局部变量可在Block中赋值,该变量也称__block变量。

截获的局部变量

截获变量为OC对象

从前面一部分可以得知,将值赋给Block中截获的局部变量会产生编译错误。
那么截获OC对象,调用变更该对象的方法也会产生编译错误吗?

id array = [[NSMutableArray alloc] init];void (^blockName) (void) = ^{id object = [[NSObject alloc] init];[array addObject: object];
};blockName();

截获的变量值array为NSMutableArray类的对象,用C语言描述,即是截获NSMutableArray类对象用的结构体实例指针

使用截获的值,这是没有问题的,而向截获的变量array赋值则会产生编译错误:

请添加图片描述

这种情况下,需要给截获的局部变量附加__block说明符:

__block id array = [[NSMutableArray alloc] init];

截获对象为C语言数组

请添加图片描述
看似没有任何问题,只是使用了C语言的字符串字面量数组,而并没有截获的局部变量赋值。但由于在目前的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,所以无法通过编译。

使用指针就可以解决该问题:

const char* text = "hello";void (^blockName) (void) = ^{printf("%c\n", text[2]);
};blockName();  //l

Blocks的实现

Block的实质

Block语法实际上是作为极普通的C语言源代码来处理的。
通过支持Block的编译器,含有Block语法的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C语言源代码被编译。

Block其实就是Objective-C对象,因为它的结构体中含有isa指针。
下面在终端通过clang将OC中Block语法转换为C++代码:clang -rewrite-objc main.m请添加图片描述

main.m

int main(void) {void (^blockName) (void) = ^{printf("Block\n");};blockName();return 0;
}

main.cpp
请添加图片描述

下面,我们将源代码分成几个部分逐步理解:

  1. 源代码中的Block语法

    //void (^blockName) (void) = ^{printf("Block\n");};
    //通过Blocks使用的匿名函数被作为简单的C语言函数来处理
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");}
    

    根据Block所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。
    该函数的参数_cself相当于C++实例方法中指向实例自身的变量this,或是OC实例方法中指向对象自身的变量self,即参数__cself为指向。

    C++的this,Objective-C的self
    定义类的实例方法:

    //C++
    void MyClass::method(int arg) {printf("%p %d", this, arg);}
    MyClass cls;
    cls.method(10);
    //OC
    - (void)method: (int)arg {printf("%p %d", self, arg);}
    MyObject* obj = [[MyObject alloc] init];
    [obj method: 10];
    

    C++、Objective-C编译器将该方法作为C语言函数来处理:

    //C++转成C
    void __ZN7MyClass6methodEi(MyClass* this, int arg) {printf("%p %d", this, arg);
    }
    struct MyClass cls;
    __ZN7MyClass6methodEi(&cls, 10);
    //OC转成C
    void _I_MyObject_method_(struct MyObject* self, SEL _cmd, int arg) {printf("%p %d", self, arg);
    }
    MyObject* obj = objc_msgSend(objc_getClass("MyObject"), sel_registerName("alloc"));
    obj = objc_msgSend(obj, sel_registerName("init"));
    objc_Send(obj, sel_registerName("method:"), 10);
    

    objc_msgSend函数根据指定的对象和函数名,从对象持有类的结构体中检索_I_MyObject_method_函数的指针并调用。
    objc_msgSend函数的第一个参数objc作为_I_MyObject_method_的第一个参数self进行传递。

  2. 来看看参数的声明:struct __main_block_impl_0* __cself,该结构体的声明如下:

    struct __main_block_impl {void* isa;int Flags;  //标志int Reserved;  //今后版本升级所需的区域void* FuncPtr;  //指针函数
    }
    struct __main_block_impl_desc_0 {unsigned long reserved;  //今后版本升级所需的区域unsigned long Block_size;  //Block大小
    }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;}
    }
    

    来看看构造函数的调用,因为转换较多,看起来比较复杂,以下去掉转换的部分:

    //void (*blockName) (void) = (void (*) void)&__main_block_impl_0 ((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0* blockName = &tmp;
    

    该源代码将__main_block_impl_0结构体类型的局部变量,即栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型的变量blockName

    这部分代码对应的最初源代码:void (^blockName) (void) = ^{printf("Block\n");};将Block语法生成的Block赋给Block类型变量blockName,它等同于将__main_block_impl_0结构体实例的指针赋给变量blockName。

    构造函数是C++中一种特殊的成员函数,用于在创建结构体对象时对其进行初始化操作,避免对象处于未定义状态。构造函数名称必须和类(包括结构体)的名称完全相同,无返回类型(包括void),若构造函数名称和结构体名不一致,编译器将不认为这是一个有效的构造函数,而是一个普通的成员函数。

  3. 下面来分析一下该构造函数__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)中的参数
    第一个参数是由Block语法转换的C语言函数指针。第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针:

    static struct __main_block_desc_0 __main_block_desc_0_DATA = {0,sizeof(struct __main_block_impl_0)  //Block大小
    };
    
  4. 接下来来看看调用Block的部分:blockName();
    这部分源代码:

    ((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
    

    去掉转换部分:

    (*blockName->impl.FuncPtr)(blockName);
    

    可以看出这是简单的函数指针调用函数。

  5. 最后探究一下上面没有提到的_NSConcreteStackBlock

    isa = &_NSConcreteStackBlock;
    

    首先要理解OC类和对象的实质,所谓Block就是Objective-C对象。
    “id”这一变量类型用于存储OC对象,在usr/include/objc/runtime.h中是如下进行声明的:

    typedef struct objc_object {Class isa;
    }* id;typedef struct objc_class {Class isa;
    }* Class;
    

    这两种结构体归根结底是在各个对象和类的实现中使用的最基本的结构体。
    下面通过编写OC类来确认一下:

    @interface MyObject : NSObject {int val0;int val1;
    }//基于objc_object结构体,该类的对象的结构体如下:
    struct MyObject {Class isa;int val0;int val1;
    }
    

    MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。

    请添加图片描述

    各类的结构体就是基于objc_class结构体的class_t结构体。class_t结构体在objc4运行时库的runtime/objc-runtime-new.h中声明如下:

    struct class_t {struct class_t* isa;struct class_t* superclass;Cache cache;IMP* vtable;unitptr_t data_NEVER_USE;
    };
    

相关文章:

Blocks —— 《Objective-C高级编程 iOS与OS X多线程和内存管理》

目录 Blocks概要什么是BlocksOC转C方法关于几种变量的特点 Blocks模式Block语法Block类型 变量截获局部变量值__block说明符截获的局部变量 Blocks的实现Block的实质 Blocks概要 什么是Blocks Blocks是C语言的扩充功能,即带有局部变量的匿名函数。 顾名思义&#x…...

Python零基础---爬虫技术相关

python 爬虫技术,关于数据相关的拆解: 1.对页面结构的拆解 2.数据包的分析(是否加密了参数)(Md5 aes)难易程度,价格 3.对接客户(433,334) # 数据库 CSV 4.结单(发一部分数据&a…...

利用 STM32 TIMER 触发 ADC 实现分组转换

1、问题描述 使用 STM32G4 系列芯片开发产品,用到其中一个 ADC 模块的多个通道,他希望使 用 TIMER 来定时触发这几个通道的转换。不过他有两点疑惑。第一,他期望定时器触发这几个 通道是每触发一次则只转换一个通道,这样依次触发…...

2024 年(第 12 届)“泰迪杯”数据挖掘挑战赛——B 题:基于多模态特征融合的图像文本检索完整思路与源代码分享

一、问题背景 随着近年来智能终端设备和多媒体社交网络平台的飞速发展,多媒体数据呈现海量增长 的趋势,使当今主流的社交网络平台充斥着海量的文本、图像等多模态媒体数据,也使得人 们对不同模态数据之间互相检索的需求不断增加。有效的信…...

Java12~14 switch语法

JDK8以后的语法没学习了,现在时代发展这么快,所以得加紧时间学习了。JDK12只有一个特性就是switch语法,算是比较容易学习的一个版本吧。总体来说就是三部分内容。具体内容可以看JEP-325的内容。 箭头语法 每个case可以放箭头了。以下是一个例…...

小狐狸ChatGPT智能聊天系统源码v2.7.6全开源Vue前后端+后端PHP

测试环境包括Linux系统的CentOS 7.6,宝塔面板,PHP 7.4和MySQL 5.6。网站的根目录是public, 使用thinkPHP进行伪静态处理,并已开启SSL证书。 该系统具有多种功能,包括文章改写、广告营销文案创作、编程助手、办公达人…...

The Rise and Potential of Large Language Model Based Agents: A Survey

OpenAI AI的应用研究主管Lilian Weng发布了关于AI Agents的《大语言模型(LLM)支持的自主代理》,在文章中她定义了基于LLM构建AI Agents的应用框架:AgentLLM(大型语言模型)记忆(Memory&#xff0…...

【GPT-SOVITS-06】特征工程-HuBert原理

说明:该系列文章从本人知乎账号迁入,主要原因是知乎图片附件过于模糊。 知乎专栏地址: 语音生成专栏 系列文章地址: 【GPT-SOVITS-01】源码梳理 【GPT-SOVITS-02】GPT模块解析 【GPT-SOVITS-03】SOVITS 模块-生成模型解析 【G…...

ros小问题之差速轮式机器人轮子不显示(rviz gazebo)

在rviz及gazebo练习差速轮式机器人时,很奇怪,只有个机器人的底板及底部的两个万向轮,如下图, 后来查看相关.xacro文件,里面是引用包含了轮子的xacro文件,只需传入不同的参数即可调用生成不同位置的轮子&…...

网络安全实训Day5

写在前面 昨天忘更新了......讲的内容不多,就一个NAT。 之前记的NAT的内容:blog.csdn.net/Yisitelz/article/details/131840119 网络安全实训-网络工程 NAT 公网地址与私网地址 公网地址 可以在互联网上被寻址,由运营商统一分配全球唯一的I…...

【Unity入门】详解Unity中的射线与射线检测

目录 前言一、射线的创建方法二、射线检测1、Raycast()Raycast()不使用射线RayRaycast()使用射线Ray 2、RaycastAll()使用射线RayRaycastAll() 不使用射线Ray 3、射线的碰撞信息 三、示例四、具体使用场景射线的调试方法1、Debug.DrawLine()2、Debug.DrawRay利用Gizmos 前言 碰…...

实验11-2-5 链表拼接(PTA)

题目: 本题要求实现一个合并两个有序链表的简单函数。链表结点定义如下: struct ListNode {int data;struct ListNode *next; }; 函数接口定义: struct ListNode *mergelists(struct ListNode *list1, struct ListNode *list2); 其中lis…...

Mybatis Plus + Spring 分包配置 ClickHouse 和 Mysql 双数据源

目录 一、背景 二、各个配置文件总览(文件位置因人而异) 2.1 DataSourceConfig 2.2 MybatisClickHouseConfig (ClickHouse 配置类) 2.3 MybatisMysqlConfig(Mysql 配置类) 2.4 application.propertie…...

27-3 文件上传漏洞 - 文件类型绕过(后端绕过)

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 导语 后端校验由开发决定是检测文件后缀,还是文件内容。 文件类型绕过(Content-Type) 1)MIME 类型 定义:MIME(Multipurpose Internet Mail Extensions)类型是一种标准,…...

widget一些控件的使用

QRadioButton使用 先使用ui界面拖拽创建radio button #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->male->setChecked(true);//选中按钮ui->o…...

Python基础(七)之数值类型集合

Python基础(七)之数值类型集合 1、简介 集合,英文set。 集合(set)是由一个或多个元素组成,是一个无序且不可重复的序列。 集合(set)只存储不可变的数据类型,如Number、…...

电脑充电器能充手机吗?如何给手机充电?

电脑充电器可以给手机充电吗? 电脑充电器可以给手机充电,但前提是电脑充电器的功率输出与手机的功率匹配且接口匹配。 假设电脑充电器的输出功率为5V/2A,手机也支持5V/2A的输入功率。 只要接口匹配,就可以使用电脑充电器给手机充…...

矩阵中移动的最大次数

文章目录 所属专栏:BFS算法 题目链接 思路如下: 1.首先我们需要从第一列开始遍历,寻找每一个都能够满足条件的位置,将它插入到数组里面 2.第一列遍历完了后我们先判断第一列的数是否都满足条件插入到数组里面,如果数组为空&#…...

Linux:系统初始化,内核优化,性能优化(3)

优化系统的文件句柄数(全局) 也就是系统的最大文件数量 查看最大数量 cat /proc/sys/fs/file-max 当我们的服务器有非常大的一个数据并发的时候十几二十万的文件需要去配置,可能这个是远远不够的,我们就要去修改 vim /etc/sy…...

使用 GitHub Actions 通过 CI/CD 简化 Flutter 应用程序开发

在快节奏的移动应用程序开发世界中,速度、可靠性和效率是决定项目成功或失败的关键因素。持续集成和持续部署 (CI/CD) 实践已成为确保满足这些方面的强大工具。当与流行的跨平台框架 Flutter 和 GitHub Actions 的自动化功能相结合时,开发人员可以创建无…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)&#xff0…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

django filter 统计数量 按属性去重

在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...