| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> iOS开发——Blocks -> 正文阅读 |
|
[移动开发]iOS开发——Blocks |
iOS开发——Blocks什么是BlocksBlocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。 按理说,C语言的标准不允许存在这样的函数。匿名函数就是不带函数名称的函数,那么带有自动变量是什么意思呢?Blocks提供了类似由C++和OC类生成实例或对象来保持变量值的方法。如“带有自动变量值”,Blocks保持自动变量的值。同时Blocks也被称作闭包、lambda计算。OC的Block在其他程序语言中的名称如图所示: Blocks模式Block语法Block语法与一般的C语言函数定义不同的是:
第一点不同是没有函数名,因为它是匿名函数。第二点不同是返回值类型前带有“^”(插入记号,caret)记号。 “返回值类型”同C语言函数的返回值类型,“参数列表”同C语言函数的参数列表,“表达式”同C语言函数中允许使用的表达式。当然与C语言函数一样,表达式中含有return语句时,其类型必须与返回值类型相同。
虽然前面出现过省略形式,但Block语法可省略好几个项目。首先是返回值类型。如图所示: 关于返回值类型,如果没有返回值则使用void类型,如果有返回值,无论return语句有多少个,所有return的返回值类型必须相同。 如果不使用参数,参数列表也可忽略:
返回值类型以及参数列表均被忽略的Block语法如下图所示: Block类型变量在Block语法中,可将Block语法赋值给声明为Block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Bloc语法生成的值也被称为“Block”。 Block类型变量仅仅是将声明函数指针类型变量的“*”变成了“^”。该Block类型变量与一般的C语言变量完全相同,可作为以下用途使用:
下面我们就试着使用Block语法将Block赋值为Block类型变量:
用“^”开始的Block语法生成的Block被赋值给变量blk中。因为与通常的变量相同,所以当然也可由Block类型变量向Block类型变量赋值。
在函数返回值指定Block类型,可以将Block作为函数的返回值返回。
由此可知,在函数参数和返回值中使用Block类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用typedef来解决该问题。
截获自动变量值“带有自动变量值的匿名函数”中的“带有自动变量值 ”在Blocks中表现为“截获自动变量值”。截获自动变量值的实例如下:
该源代码中,Block语法的表达式使用的是它之前声明的自动变量fmt和val。所以它的输出是2和256,也就是说Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。 __block 说明符实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写该值。 另外关于C语言中的存储域类有以下几种:
_block说明符类似于static、auto和register说明符,它们用于指定将变量值设置到哪个存储域中。例如,auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中。 若想在Blcok语法的表达式中将值赋值给在Block语法外声明的自动变量,需要在该自动变量上附加__blcok说明符。 该源代码中,如果给自动变量声明int val附加__block说明符,就能实现在Block内赋值。例如:
该源代码运行结果为: Blocks的实现Block的实质Block的实质就是通过编译器,将Block语法的源代码转换为一般C语言编译器能够处理的源代码,并作为普通的C语言源代码被编译。
此源代码的Block语法最为简单,它忽略了返回值类型以及参数列表。该源代码通过clang可变换为以下形式:
我们将转化后的C++源码分成几个部分来看:
我们来看一下:
然后是
这个结构体主要就是初始化变量impl以及desc指针 接下来看一下结构体
另外还有一个
经过变换后的源代码可以看到,通过Blocks使用的匿名函数实际上就是被作为简单的C语言函数来处理。另外,根据Block语法所属的函数名(此处为main)和该Block语法在该函数出现的 顺序值(此处为0)来给clang变换的函数命名。 最后看
我们来看一下代码,去掉转化部分
该源代码将__main_block_impl_0结构体类型的自动变量,也就是栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。
这是使用函数指针调用函数。由Block语法转换的__main_block_impl_0函数的指针被赋值成员变量FunPtr中。
id为objc_object结构体的指针类型。我们再看看Class。
Class为objc_class 结构体的指针类型。objc_class结构体在/usr/include/objc/runtime.h声明如下:
这与objc_object结构体相同。然而,objc_object结构体和objc_class结构体归根结底是在各个对象和类的实现中使用的最基本的结构体。下面我们通过编写简单的OC类声明来确认一下:
基于
MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。“Objective—C中由类生成对象”意味着,像该结构体这样“生成由该类生成的对象的结构体实例”。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。如图所示: 各类的结构体就是基于objc_class结构体的class_t结构体。class_t结构体在objc4运行时库的runtime/objc- runtime-new.h中声明如下:
在OC中,比如NSObject的class_t结构体实例以及NSMutableArray的class_t结构体实例等,均生成并保存各个类的class_t结构体实例。该实例持有声明的成员变量、方法的名称、方法的实现(即函数指针)、属性以及父类指针,并被OC运行时库所使用。
此__main_block_impl_0结构体相当于基于objc_object结构体的OC类对象的结构体。另外,对其中的成员变量isa进行初始化,具体如下:
即_NSConcreteStackBlock相当于class_t结构体实例。在将Block作为OC的对象处理时,关于该类的信息放置于 _NSConcreteStackBlock中。 Block存储域通过前面可以知道,Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。 Block也可以当作OC对象。将Block当作OC对象来看时,该Block的类为_NSConcreteStackBlcok。虽然该类并没有出现在已变换源代码中,但有三个与之类似的类,如:
这三个有啥区别呢?我们通过一个表来看一下: 我们可以看到不同的类有不同的设置对象的存储域。 如果Block用结构体实例设置在程序的数据区域中。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获。由此Blcok用结构体实例的内容不依赖于执行时的状态,所以整个程序中只需一个实例。因此将Blcok用结构体实例设置在与全局变量相同的数据区域中即可。 只有截获自动变量时,Block用结构体实例截获的值才会根据执行时的状态变化。即使在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。 虽然通过clang转换的源代码通常是_NSConcreteStackBlcok类对象,但实际上却有不同,总结如下:
在以上情况下,Block为_NSConcreteGlobalBlock类对象。即Block配置在程序的数据区域中。除此之外的Block语法生成的Block为 _NSConcreteStackBlock类对象,且设置在栈上。 那么问题来了,如果将Block配置在堆上的_NSConcreteMallocBlock类在何时使用呢? Block超出变量作用域可存在的原因?配置在全局变量上的Block,从变量作用域外也可以通过指针安全的使用。但设置在栈上的Block,如果其所属的变量作用域结束,该Block就被废弃。由于__block变量也配置在栈上,同样地,如果其所属的变量作用域结束,则该__block变量也会被废弃。如图所示: 所有Blocks提供了将Blcok和__block变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上的Blcok还可以继续存在。如图所示: 实际上复制到堆上的Block将_NSConcreteMallocBlock类对象写入结构体实例的成员变量isa。
而__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上时都能够正确的访问__block变量。 这句话具体怎么理解呢,有时在__block变量配置在堆上的状态下,也可以访问栈上的block变量。在此情形下,只要在栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__blcok变量还是从堆上的__block变量都能够正确访问。 按配置Block的存储域,将copy方法进行复制的动作总结如下: _ _block变量存储域使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响。具体情况如图所示: 若在1个Block中使用__block变量,则当该Block从栈复制到堆时。使用的所有__block变量也必定配置在栈上。这些__block变量也全部被从栈复制到堆。此时,Blcok持有__block变量。即使在该Blcok已复制到堆的情况下,复制Block也对所使用的__block变量没有任何影响。如图所示: 在多个Block中使用__block变量时,因为最先会将所有的Block配置在栈上,所以__block变量也会配置在栈上。在任何一个Blcok从栈复制到堆时,__block变量也会一并从浅复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__blcok变量的引用计数。 如果配置在堆上的Block被废弃,那么它所使用的__block变量也就被释放。如图所示: 成员变量__forwarding的用处是什么?在栈上的__block变量用结构体实例在__block变量从栈复制到堆上时,会将成员变量__forwarding的值替换为复制目标堆上的__block变量用结构体实例的地址,如图所示:
截获对象OC的运行时库能够准确把握Block从浅复制到堆以及堆上的Block被废弃的时机,因此Block结构体中即使含有附有__strong修饰符或__weak修饰符的变量,也可以恰当地进行初始化和废弃。为此需要使用在 copy函数和dispose函数调用的时机: 那么什么时候栈上的Block会复制到堆呢?
截获对象时和使用__block变量时的不同 通过 Block中使用对象类型自动变量时,除以下情形外,推荐调用Block的copy实例方法:
Block变量和对象__block说明符可指定任何类型的自动变量。 通过clang转换后的代码如下: 使用 Block循环引用什么情况下Block会造成循环引用?
对于__block变量MRC如何解决循环引用?ARC如何解决?
总结:
|
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 23:30:39- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |