iOS中block实现的探究

logisim实验——通过2个半加器实现1-bit全加器,通过4个一位全加器构成4-bit加法器(详解) 一、由异或门和与门所构成的半加器: 二、由两个半加器构成的1-bit全加器: .. 阅读详情

[0. Brief introduction of block]

Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性。

用维基百科的话来说,Block是Apple Inc.为C、C++以及Objective-C添加的特性,使得这些语言可以用类lambda表达式的语法来创建闭包。

用Apple文档的话来说,A block is an anonymous inline collection of code, and sometimes also called a "closure".

关于闭包,我觉得阮一峰的一句话解释简洁明了:闭包就是能够读取其它函数内部变量的函数。

这个解释用到block来也很恰当:一个函数里定义了个block,这个block可以访问该函数的内部变量。

一个简单的Block示例如下:

[cpp]  view plain copy
  1. int (^maxBlock)(int, int) = ^(int x, int y) { return x > y ? x : y; };  

如果用Python的lambda表达式来写,可以写成如下形式:

[python]  view plain copy
  1. f = lambda x, y : x if x > y else y  

不过由于Python自身的语言特性,在def定义的函数体中,可以很自然地再用def语句定义内嵌函数,因为这些函数本质上都是对象。

如果用BNF来表示block的上下文无关文法,大致如下:

[cpp]  view plain copy
  1. block_expression  ::=  ^  block_declare  block_statement  
  2. block_declare  ::=  block_return_type  block_argument_list  
  3. block_return_type ::=  return_type  |  空  
  4. block_argument_list  ::=  argument_list  |  空  


[1. Why block]

Block除了能够定义参数列表、返回类型外,还能够获取被定义时的词法范围内的状态(比如局部变量),并且在一定条件下(比如使用__block变量)能够修改这些状态。此外,这些可修改的状态在相同词法范围内的多个block之间是共享的,即便出了该词法范围(比如栈展开,出了作用域),仍可以继续共享或者修改这些状态。

通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。

比如我们可以在遍历NSArray时做一些事情:

[cpp]  view plain copy
  1. - (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;  

其中将stop设为YES,就跳出循环,不继续遍历了。

而在很多框架中,block越来越经常被用作回调函数,取代传统的回调方式。

  • 用block作为回调函数,可以使得程序员在写代码更顺畅,不用中途跑到另一个地方写一个回调函数,有时还要考虑这个回调函数放在哪里比较合适。采用block,可以在调用函数时直接写后续处理代码,将其作为参数传递过去,供其任务执行结束时回调。
  • 另一个好处,就是采用block作为回调,可以直接访问局部变量。比如我要在一批用户中修改一个用户的name,修改完成后通过回调更新对应用户的单元格UI。这时候我需要知道对应用户单元格的index,如果采用传统回调方式,要嘛需要将index带过去,回调时再回传过来;要嘛通过外部作用域记录当前操作单元格的index(这限制了一次只能修改一个用户的name);要嘛遍历找到对应用户。而使用block,则可以直接访问单元格的index。

这份文档中提到block的几种适用场合:

  • 任务完成时回调处理
  • 消息监听回调处理
  • 错误回调处理
  • 枚举回调
  • 视图动画、变换
  • 排序


[2. About __block_impl]

Clang提供了中间代码展示的选项供我们进一步了解block的原理。

以一段很简单的代码为例:


使用-rewrite-objc选项编译:


得到一份block0.cpp文件,在这份文件中可以看到如下代码片段:


从命名可以看出这是block的实现,并且得知block在Clang编译器前端得到实现,可以生成C中间代码。很多语言都可以只实现编译器前端,生成C中间代码,然后利用现有的很多C编译器后端。

从结构体的成员可以看出,Flags、Reserved可以先略过,isa指针表明了block可以是一个NSObject,而FuncPtr指针显然是block对应的函数指针。

由此,揭开了block的神秘面纱。

不过,block相关的变量放哪里呢?上面提到block可以capture词法范围内(或者说是外层上下文、作用域)的状态,即便是出了该范围,仍然可以修改这些状态。这是如何做到的呢?


[3. Implementation of a simple block]

先看一个只输出一句话的block是怎么样的。


生成中间代码,得到片段如下:


首先出现的结构体就是__main_block_impl_0,可以看出是根据所在函数(main函数)以及出现序列(第0个)进行命名的。如果是全局block,就根据变量名和出现序列进行命名。__main_block_impl_0中包含了两个成员变量和一个构造函数,成员变量分别是__block_impl结构体和描述信息Desc,之后在构造函数中初始化block的类型信息和函数指针等信息。

接着出现的是__main_block_func_0函数,即block对应的函数体。该函数接受一个__cself参数,即对应的block自身。

再下面是__main_block_desc_0结构体,其中比较有价值的信息是block大小。

最后就是main函数中对block的创建和调用,可以看出执行block就是调用一个以block自身作为参数的函数,这个函数对应着block的执行体。

这里,block的类型用_NSConcreteStackBlock来表示,表明这个block位于栈中。同样地,还有_NSConcreteMallocBlock和_NSConcreteGlobalBlock。

由于block也是NSObject,我们可以对其进行retain操作。不过在将block作为回调函数传递给底层框架时,底层框架需要对其copy一份。比方说,如果将回调block作为属性,不能用retain,而要用copy。我们通常会将block写在栈中,而需要回调时,往往回调block已经不在栈中了,使用copy属性可以将block放到堆中。或者使用Block_copy()和Block_release()。


[4. Capture local variable]

再看一个访问局部变量的block是怎样的。


生成中间代码,得到片段如下:


可以看出这次的block结构体__main_block_impl_0多了个成员变量i,用来存储使用到的局部变量i(值为1024);并且此时可以看到__cself参数的作用,类似C++中的this和Objective-C的self。

如果我们尝试修改局部变量i,则会得到如下错误:


错误信息很详细,既告诉我们变量不可赋值,也提醒我们要使用__block类型标识符。

为什么不能给变量i赋值呢?

因为main函数中的局部变量i和函数__main_block_func_0不在同一个作用域中,调用过程中只是进行了值传递。当然,在上面代码中,我们可以通过指针来实现局部变量的修改。不过这是由于在调用__main_block_func_0时,main函数栈还没展开完成,变量i还在栈中。但是在很多情况下,block是作为参数传递以供后续回调执行的。通常在这些情况下,block被执行时,定义时所在的函数栈已经被展开,局部变量已经不在栈中了(block此时在哪里?),再用指针访问就⋯⋯。

所以,对于auto类型的局部变量,不允许block进行修改是合理的。


[5. Modify static local variable]

于是我们也可以推断出,静态局部变量是如何在block执行体中被修改的——通过指针。

因为静态局部变量存在于数据段中,不存在栈展开后非法访存的风险。


上面中间代码片段与前一个片段的差别主要在于main函数里传递的是i的地址(&i),以及__main_block_impl_0结构体中成员i变成指针类型(int *)。

然后在执行block时,通过指针修改值。

当然,全局变量、静态全局变量都可以在block执行体内被修改。更准确地讲,block可以修改它被调用(这里是__main_block_func_0)时所处作用域内的变量。比如一个block作为成员变量时,它也可以访问同一个对象里的其它成员变量。


[6. Implementation of __block variable]

那么,__block类型变量是如何支持修改的呢?


我们为int类型变量加上__block指示符,使得变量i可以在block函数体中被修改。

此时再看中间代码,会多出很多信息。首先是__block变量对应的结构体:


由第一个成员__isa指针也可以知道__Block_byref_i_0也可以是NSObject。

第二个成员__forwarding指向自己,为什么要指向自己?指向自己是没有意义的,只能说有时候需要指向另一个__Block_byref_i_0结构。

最后一个成员是目标存储变量i。

此时,__main_block_impl_0结构如下:


__main_block_impl_0的成员变量i变成了__Block_byref_i_0 *类型。

对应的函数__main_block_func_0如下:


亮点是__Block_byref_i_0指针类型变量i,通过其成员变量__forwarding指针来操作另一个成员变量。 :-)

而main函数如下:


通过这样看起来有点复杂的改变,我们可以修改变量i的值。但是问题同样存在:__Block_byref_i_0类型变量i仍然处于栈上,当block被回调执行时,变量i所在的栈已经被展开,怎么办?

在这种关键时刻,__main_block_desc_0站出来了:


此时,__main_block_desc_0多了两个成员函数:copy和dispose,分别指向__main_block_copy_0和__main_block_dispose_0。

当block从栈上被copy到堆上时,会调用__main_block_copy_0将__block类型的成员变量i从栈上复制到堆上;而当block被释放时,相应地会调用__main_block_dispose_0来释放__block类型的成员变量i。

一会在栈上,一会在堆上,那如果栈上和堆上同时对该变量进行操作,怎么办?

这时候,__forwarding的作用就体现出来了:当一个__block变量从栈上被复制到堆上时,栈上的那个__Block_byref_i_0结构体中的__forwarding指针也会指向堆上的结构。


转:http://blog.youkuaiyun.com/jasonblog/article/details/7756763


PD3.0详解 协议的时序timer和counter计数 前面介绍了关于PD协议的交互,还有协议层数据各个字段的含义,但是一直没有系统的说关于协议的时序,还有部分counter,就是协议允许发送的次数,或者一些其他计数。 大师匈今天说一下关于这方面的东西,这些东西都是非常关键的东西,但是相对来说PD标准内对时序的要求还是比较宽泛,非常容易能够实现这样的要求。 Timer 1、CRCReceiveTimer 0.9~1.1ms 表示每次通信收到goodcrc的时间,必须在这个时间内收到goodcrc, 我们要注意不是在0.9~1.1ms内,而是第三方可以在这个范 阅读详情

相关推荐

TLS回调函数–一文看懂(详)

TLS回调函数 CTF RE

Joyce_hjll的博客 3716

ios-block的作为参数

大家在学习block的时候,不会的人,会觉得block比较复杂,很难理解. 尤其是在作为参数的时候. 一般在实际使用的时候我们一般会遇到block的两种方式, 一种是 作为 “成员变量”, 另一种是作为参数(初学者比较难理解的); 我们会用一些比较简单的方式进行理解.初学者如果不熟悉的话,可以使用inline(ps:最好完全手打,不要用), 作为成员变量使用(就像普通变量),一般就是初始化,赋

sooner78的博客 1370

Linux-BSP-WIFI驱动移植与调试实战——以RTL8189FS为例

本文以RTL8189FS为例,详细介绍了Linux BSP中WiFi驱动移植与调试的全流程。涵盖硬件原理图分析、内核配置、设备树适配、驱动编译加载及问题排查等关键步骤,提供实用的调试技巧和解决方案,帮助开发者高效完成嵌入式WiFi驱动开发。

moon9的博客 396

Block作为参数时的使用

Block作为参数使用,常见于各框架之中,比如在封装一个类时,当做什么事情由外界去决定,什么时候调用由自己的类决定时,这时候就需要将block作为参数使用。 下面我们模仿AFNetworking的manager,以自定义一个简单的工具类CalculatorManager为例: 1.CalculatorManager.h文件 #import <Foundation/Foundati...

a447780100的博客 412

DSSAT模型实战:从基础配置到气候情景模拟

本文详细介绍了DSSAT模型在农业模拟中的应用,从基础配置到气候情景模拟的全流程实战指南。通过土壤-植物-大气连续体的动态关系建模,DSSAT能精准预测作物产量,误差率低至8%。文章涵盖气象数据处理、土壤参数配置、管理措施调控等核心技巧,并提供了Python脚本和可视化方法,助力农业科研与决策。

weixin_29264201的博客 1011

Mumax3代码模板:从基础设置到材料参数优化

本文提供了一份详尽的Mumax3微磁学模拟代码模板,从基础网格设置、边界条件到核心材料参数(如钴的Msat、Aex、Ku1)配置与优化均有涵盖。文章深入解析了特征长度计算、初始化方法、弛豫与动力学模拟流程,并分享了并行计算与参数扫描等效率提升技巧,旨在帮助用户快速上手并优化模拟工作流。

moss5的博客 702

iOS高级理论: Block介绍和使用

在 iOS 开发中,Block 是一种闭包(Closure)的概念,可以将一段代码块作为一个对象进行传递和存储,类似于函数指针。Block 可以捕获其定义时所在范围内的变量,并在需要的时候执行这段代码块。Block 的使用可以方便地实现回调、异步操作、事件处理等功能。

长沙火山 4261

DeepSeek Harness接入QQ实战:把DSH变成赛博群友

大模型应用正从单机问答走向多平台集成,如何将模型能力嵌入即时通讯工具成为工程实践的热点。消息接入、会话管理、插件扩展是构建智能群聊机器人的核心三要素。DeepSeek Harness(DSH)作为一款模型会话工作流管理工具,凭借多会话管理、模型调用编排以及灵活的插件机制,能够在QQ群聊场景中实现“接收消息→模型生成→回复推送”的完整闭环。本文从基础概念切入,详解DSH接入QQ的两种实现路线——外部API转发与原生插件适配,覆盖环境准备、启动调试、接口调用与性能观察,并给出常见问题排查清单。无论是本地AI助理

weixin_27427619的博客 1835

【iOS底层探索- Block原理分析-循环引用】

Block循环引用

weixin_61639290的博客 1701

Python爬虫实战:美团外卖商家数据抓取与分析

随着互联网技术的快速发展,外卖平台已成为人们日常生活中不可或缺的一部分。本文基于Python爬虫技术,使用最新的异步爬虫框架和反爬虫绕过技术,实现了对美团外卖平台商家信息与评分数据的自动化抓取。通过对抓取数据的清洗、存储和分析,为餐饮行业市场研究和商业决策提供数据支持。本文详细介绍了爬虫的设计思路、技术实现方案、数据处理方法以及可视化分析结果,为相关领域的研究者和开发者提供参考。关键词:Python爬虫;美团外卖;数据抓取;数据分析;异步爬虫。

2201_76125261的博客 1821

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

关于iOS中block如何实现的探究

/* ---------------------------------------------------------------------------------------------------- */ [0. Brief introduction of block] Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性。

volcan1987的专栏 1159

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

上一篇: 多线程基础基础
下一篇: Block全面分析
直隶码农
博客等级 码龄19年 95粉丝 103原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值