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

news2024/10/7 10:22:38

目录

    • 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;
    };
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1524768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

u盘文件损坏怎么恢复数据?分享三个数据恢复方法

随着科技的飞速发展,U盘已成为我们日常生活和工作中不可或缺的数据存储工具。然而,由于各种原因,如不当操作、病毒感染或硬件故障等,U盘中的文件可能会受到损坏。那么,当U盘文件损坏时,我们该如何恢复数据呢…

mac下Appuim环境安装

参考资料 Mac安装Appium_mac电脑安装appium-CSDN博客 安卓测试工具:Appium 环境安装(mac版本)_安卓自动化测试mac环境搭建-CSDN博客 1. 基本环境依赖 1 node.js 2 JDK(Java JDK) 3 Android SDK 4 Appium&#x…

深度学习-基于机器学习的语音情感识别系统的设计

概要 语音识别在现实中有着极为重要的应用,现在语音内容的识别技术已日趋成熟。当前语音情感识别是研究热点之一,它可以帮助AI和人更好地互动、可以帮助心理医生临床诊断、帮助随时随地高效测谎等。本文采用了中科院自动化所的CASIA语料库作为样本&#…

Qt文件以及文件夹相关类(QDir、QFile、QFileInfo)的使用

关于Qt相关文件读写操作以及文件夹的一些知识,之前也写过一些博客: Qt关于路径的处理(绝对路径、相对路径、路径拼接、工作目录、运行目录)_qt 相对路径-CSDN博客 C/Qt 读写文件_qt c 读取文本文件-CSDN博客 C/Qt读写ini文件_…

【GPT-SOVITS-01】源码梳理

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

react中hooks使用限制

只能在最顶层使用Hook 不要在循环、条件中调用hook,确保总是在React函数最顶层使用它们 只能React函数中调用Hook 不要在普通的js函数中调用 在React的函数组件中调用Hook 在自定义hook中调用其他hook 原因: 我们每次的状态值或者依赖项存在哪里&…

Unity触发器的使用

1.首先建立两个静态精灵(并给其中一个物体添加"jj"标签) 2.添加触发器 3.给其中一个物体添加刚体组件(如果这里是静态的碰撞的时候将不会触发效果,如果另一个物体有刚体可以将它移除,或者将它的刚体属性设置…

Jest:JavaScript的单元测试利器

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

挑战杯 机器视觉目标检测 - opencv 深度学习

文章目录 0 前言2 目标检测概念3 目标分类、定位、检测示例4 传统目标检测5 两类目标检测算法5.1 相关研究5.1.1 选择性搜索5.1.2 OverFeat 5.2 基于区域提名的方法5.2.1 R-CNN5.2.2 SPP-net5.2.3 Fast R-CNN 5.3 端到端的方法YOLOSSD 6 人体检测结果7 最后 0 前言 &#x1f5…

YOLOV9训练自己的数据集

1.代码下载地址GitHub - WongKinYiu/yolov9: Implementation of paper - YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information 2.准备自己的数据集 这里数据集我以SAR数据集为例 具体的下载链接如下所示: 链接:https:/…

软件测试 自动化测试selenium 基础篇

文章目录 1. 什么是自动化测试?1.1 自动化分类 2. 什么是 Selenium ?3. 为什么使用 Selenium ?4. Selenium 工作原理5. Selenium 环境搭建 1. 什么是自动化测试? 将人工要做的测试工作进行转换,让代码去执行测试工作 …

netlogo 羊-草生态系统模型的系统动力学搭建

to setupclear-allsystem-dynamics-setupendto gosystem-dynamics-gosystem-dynamics-do-plot enda 羊的净出生率 a 0.001sheep_birth a * sheep * grass羊 10 sheep 10b 羊的死亡率 0.01 b 0.01death 羊的死亡流 羊x 羊的死亡率 death b * sheep草 200 grass 200R 草的净…

2024最新PHP彩虹网盘与外链分享程序,支持所有格式文件的上传

彩虹外链网盘是一款基于PHP的在线存储和分享平台,它允许用户上传各种类型的文件,并提供了生成文件链接、图片链接、音乐和视频链接的功能。同时,它还会自动生成相应的UBB代码和HTML代码,支持文本、图片、音乐和视频的在线预览。这…

基于深度学习LSTM+NLP情感分析电影数据爬虫可视化分析推荐系统(深度学习LSTM+机器学习双推荐算法+scrapy爬虫+NLP情感分析+数据分析可视化)

文章目录 基于深度学习LSTMNLP情感分析电影数据爬虫可视化分析推荐系统(深度学习LSTM机器学习双推荐算法scrapy爬虫NLP情感分析数据分析可视化)项目概述深度学习长短时记忆网络(Long Short-Term Memory,LSTM)机器学习协…

【Frida】04_Frida中使用TypeScript脚本(采坑)

▒ 目录 ▒ 🛫 导读需求开发环境演示目标 1️⃣ 操作步骤安装node 20.10.0在 VSCode 中打开项目目录初始化一个 NodeJS 项目安装 TypeScript初始化 TypeScript 项目安装依赖配置 TypeScript编写代码编译设置编译脚本运行,查看结果 2️⃣ 采坑frida-compi…

R语言深度学习-6-模型优化与调试

本教程参考《RDeepLearningEssential》 这是本专栏的最后一篇文章,一路走来,大家应该都可以独立的建立一个自己的神经网络进行特征学习和预测了吧! 6.1 缺失值处理 在我们使用大量数据进行建模的时候,缺失值对模型表现的影响非常…

【鸿蒙HarmonyOS开发笔记】自定义组件详解

自定义组件 除去系统预置的组件外,ArkTS 还支持自定义组件。使用自定义组件,可使代码的结构更加清晰,并且能提高代码的复用性。 我们开发的每个页面其实都可以视为自定义组件内置组件的结合 语法说明 自定义组件的语法如下图所示 各部分…

Github: Github actions 自动化工作原理与多workflow创建和部署

Github actions 1 )概述 Github Actions 是Github官方推出的 CI/CD 解决方案 https://docs.githu.com/en/actions 优点 自动发布流程可减少发布过程中手动操作成本,大幅提升ci/cd效率,快速实现项目发布上线 缺点 存在较高的技术门槛需要利用…

Linux_基础指令(一)

目录 1、ls指令 1.1 ls -l 1.2 ls -a 1.3 ls -i 2、pwd指令 3、cd指令 3.1 路径的概念 3.1.1 绝对路径 3.1.2 相对路径 3.2 cd ~ 3.3 cd - 4、touch指令 5、mkdir指令 6、删除系列的指指令 6.1 rmdir 6.2 rm 7、man指令 8、cp指令 9、move指令 结…

算法——贪心

「贪心的本质是选择每一阶段的局部最优,从而达到全局最优」 贪心无套路 1. 分发饼干 贪心策略: (1)局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩 &#xff08…