Block高级篇

又见block(一):block是什么? block的原理实质问题是一道分水岭,区分中高级程序的试金石,一般的大公司,他可能不会问你内存管理甚至多线程编程与简单的一笔带过,但是block一定会仔细问。无奈的是平时只知使用,对其原理与实质没有深究,因此数次面试倒在这道坎上,真是心有不甘,这次好好理理关于block的前因后果,以供后续随手翻阅查证。... 阅读详情

__block说明符

前面讲到Block会捕获外部变量,但是当你试图在Block里面修改捕获的外部变量时。就是出现编译错误,解决的一种办法是将外部变量使用__block修饰符修饰。下面是添加__block修饰的外部变量代码:

#include <stdio.h>

int main(int argc, const char * argv[]) {

    __block int val = 10;
    void (^blk)(void) = ^{ val = 1; };

    return 0;
}

该代码可进行编译。变换后如下:

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
}; // [1]


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; 

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
     (val->__forwarding->val) = 1; 
} // [2]


static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
{
    _Block_object_assign((void*)&dst->val, 
    (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{
    _Block_object_dispose((void*)src->val, BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_block_desc_0 
{
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 
    0, 
    sizeof(struct __main_block_impl_0), 
    __main_block_copy_0, 
    __main_block_dispose_0
};


int main(int argc, const char * argv[]) 
{
    __Block_byref_val_0 val = {        
        (void*)0,
        (__Block_byref_val_0 *)&val,
         0, 
         sizeof(__Block_byref_val_0), 
         10
    };

    blk = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344);

    return 0;
}

我们可以发现加上__block说明符,源码量就急剧的增加了(已做简化)。

  • 我们可以发现含有__block修饰的变量变成了__Block_byref_val_0结构体。[1]
  • __Block_byref_val_0结构体实例的成员变量__forwarding持有该实例自身的指针。
  • Block转化而来的的__main_block_func_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量val的地址,从而可以修改自动变量。
  • 我们需要负责Block_byref_i_0结构体相关的内存管理,所以main_block_desc_0中增加了copy和dispose函数指针,对于在调用前后修改相应变量的引用计数。

Block存储域

__block变量转换成了__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。如下表所示:

名称 实质
Block 栈上Block的结构体实例  
__block变量 栈上__block变量的结构体实例  

前面我们看到Block的类型说明_NSConcreteStackBlock.虽然该类没有出现已变换源代码中,但还有与之相识的类,如:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock

他们分别对应的存储区域如下所示:

类 设置对象的存储区域
_NSConcreteStackBlock 栈
_NSConcreteGlobalBlock 程序的数据区域(.data区域)
_NSConcreteMallocBlock 堆

定义Block时期内存区域分配在栈中,其Block类型为__NSConcreteStackBlock类对象.
那么_NSConcreteMallocBlock类型的对象由何而来?这里肯定有人还在疑惑为什么__block变量转化而来的结构体为生成指向自身的__forwarding指针变量。其目的是为了能然超出范围的Block也有效。有人会说设置个全局的Block不就可以搞定了么。

行不行先来段代码看看:

void (^blk)();
if (/* some condition */) {
    blk = ^{ NSLog(@"Block A"); };
}else {
    blk = ^{ NSLog(@"Block B"); };
}
blk();

看这段代码我声明了全局的Block变量blk,然后在if语句中定义。如果你不理解block那么就很容易写出这样的代码,其实这段代码是很危险的。因为全局的blk变量是分配在栈上的。在if和else语句中定义的blk内容,编译器会给每个块分配好栈内存,然后等离开了相应的范围之后,编译器有可能把分配给块的内存覆写了。如果编译器未覆写这块栈内存则程序照常运行,如果这块内容被覆写那么程序就会崩溃。

解决上面问题的方法就是使用copy方法,将block拷贝到堆中。拷贝完之后就是接下来要将的_NSConcreteMallocBlock类型。该类型是带有引用计数的对象,如果在ARC下,只要引用计数不为0,可以随意的访问,后继的内存管理就交给编译器来完成了。

还有一种类型是_NSConcreteGlobalBlock类型,这类Block不会捕捉任何状态的外部变量。块所使用的整个内存区域,在编译器已经完全的确定了,不需要每次调用时在栈中创建,如下就是一个全局快:

void (^blk)() = {
    NSLog("This is a global block");
}

void main() {
}

__block变量存储域

如果Block配置在栈中,则在Block中使用的__block变量也分配在栈中。当Block被复制到堆中时,__block变量也一并被复制在堆中,并被Block所持有。如果非配在堆中的Block被废弃,那么它所使用的__block变量也就被释放了。下面来看堆和栈上__block混用的例子

__block int val = 0;

void (^blk) (void) = [^{val++;} copy];

++val;

blk();

NSLog(@"val:%d",val);

执行结果为:

val: 2

在Block中和在Block外修改__block变量完全等效,它是怎么实现的呢?
是因为执行copy方法之后,Block被复制到堆中,其内部捕获的__block变量也一并被复制。而此时分配在栈上的val任然存在的,栈上的__block变量val会将原本指向自身的__forwarding指针指向复制到堆中的__block变量val的地址。这样堆中的__block变量被修改之后就等同于栈上的block被修改。

通过该功能,无论是在Block的语法中、Block语法外使用__block变量,还是__block变量配置在栈上还是堆上,都可以顺利地访问一个__block变量。

截获对象

先来看一段Blcok截获可变数组对象的例子:

blk blk;
{
    NSMutableArray *array = [[NSMutableArray alloc] init];
    blk = ^(id obj){

        [array addObject:obj];
        NSLog(@"arrayCount = %lu",(unsigned long)array.count);
    };
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);

执行该段代码的结果为

arrayCount = 1;
arrayCount = 2;
arrayCount = 3;

从表面上看是没什么问题,运行的结果也是正确的。而实际上如果我们大量的调用block向可变数组中添加对象元素程序会强制结束。原因是block截获的NSMutableArray对象是分配在栈上的,随着当可变数组元素增加到一定程度会造成栈溢出。

解决方法是调用copy方法,形式如下:

blk = [^(id obj){
   [array addObject:obj];

   NSLog(@"arrayCount = %lu",(unsigned long)array.count);
} copy];

Block循环引用

在实际项目中对于Block最常见的问题应该是循环引用。如果Block中使用了__strong修饰符的对象,那么当block从栈复制到堆时,该对象为Block所持有。这样容易造成循环引用,比较明显的我想大家肯定遇到过,我们来看一个比较隐蔽的,源代码如下:

typedef void (^blk_t)(void);
@interface MyObject : NSObject


@property (nonatomic, strong) id obj;
@property (nonatomic, strong) blk_t blk;

@end

@implementation MyObject

- (id)init
{
    self = [super init];

    _blk = ^{NSLog(@"obj = %@",_obj);};

    return self;
}

@end

通过编译器我们可以看到造成了循环引用,即Block语法内部使用了_obj变量,是因为_obj变量实际上截获了self。对编译器来说,_obj变量只不过是对象的成员变量罢了。

解决的方法便是便是通过__weak修饰符来修饰会被Block捕获的变量:

id __weak obj = _obj;

_blk = ^{NSLog(@"obj = %@",obj);};

还有一种定义设置weak变量的方式,可以用于宏定义。代码如下:

#define WS(weakSelf)  __weak __typeof(&*self)weakSelf = self;

调用WS(ws)之后ws就变成为了__weak修饰符修饰的self了。

如果你觉得我写的东西对你有点价值的话,希望你能为我增加一个关注量。我的公众号「iOSTalk」。


​
重学OC第二十三篇:block 文章目录一、Block是什么?1.1 __block二、Block源码分析 一、Block是什么? int main(int argc, const char * argv[]) { int a = 0; void (^aBlock)(void) = ^{ NSLog(@"%d", a); }; aBlock(); } 用clang转为cpp Block是带有自动变量(局部变量)的匿名函数,但在OC中Block本质上就是一个对象(__main_block_impl_0结构体类比obj 阅读详情

相关推荐

iOS高级开发工程师面试——关于Block

和函数类似, 只不过是直接定义在另一个函数里的, 和定义它的那个函数共享同一个范围内的东西。block可以实现闭包, 有些人也称它作。结构如下: 由上图可知,block实际上是由6部分组成的:指针,用于按位表示的一些附加信息,保留变量,函数指针,指向具体的实现的函数调用地址,从它的结构体可以看出,主要表示该的附加描述信息,主要是大小,以及和函数的指针,捕获的变量,能访问它的外部的局部变量,就是因为将这些变量(或变量地址)复制到了结构体中是类。 它有三种类型:分别是ARC下:和,切换到非ARC下的;这种块不会

wahkim的博客 766

block 捕获外部变量解析

先看一张全图 在上面的图片中可以看到: block 内部不可以修改自动变量的值,但是加上__block以后就可以 block 内部可以修改对象属性的值,但是不可以修改对象的指向 接下来会逐个分析1. 全局变量 编译前 编译后 可以发现编译后是直接复制的,没有特殊操作 原因:全局变量是存放在全局符号表里面的,在整个 app 生命周期

li15809284891的专栏 972

MindIE与vLLM-Ascend性能对决:Qwen3在昇腾910B上的推理效率实测

本文深度对比了昇腾910B平台上两大推理框架MindIE与vLLM-Ascend的性能表现。通过实测Qwen3系列模型,详细分析了二者在单卡、多卡场景下的推理效率、扩展性及生产环境适用性,为AI算力基础设施选型提供关键参考。

weixin_29214335的博客 1183

block的四种基本使用方式

1.单例声明的block 2.被包含的对象声明的block 3.调用者的block被当变量传入被调用对象 4.block参数

jia12216的专栏 5845

【IC设计实战】去耦电容decap在电源完整性中的关键作用与选型策略

本文深入探讨了去耦电容(decap)在IC设计电源完整性中的核心作用。文章分析了decap如何通过提供本地电荷缓冲来抑制电源噪声与电压波动,并详细阐述了其选型与布局策略,包括MIM、MOM、MOS电容的特性对比,以及如何权衡峰值抑制与建立速度的矛盾,为设计稳定可靠的芯片电源系统提供实践指导。

weixin_29233857的博客 248

小案例:利用图床自动化批量上传图片并获取图片链接

用python爬虫写了一个小脚本,用来自动上传图片到图床,然后返回链接,我们只需直接复制链接就可以。(需要安装Requests库) 因为不同图床需要提交的POST表单不一致,所以在这里我用ImgURL图床做示范,如果大家有需求,可自行修改代码。使用脚本的时候,我们只需把需要上传的图片和这个脚本放在同一个文件夹中,然后运行脚本,稍作等待,看到提示时就可以在脚本运行文件夹中发现一个名为“图片链接地址.txt”的文件,里面就是图片链接啦! 四、图片链接地址.txt(单张图片):...

采菊东篱下,Python满乾坤! 4409

Block从简单到高级的使用,以及项目中经常使用的场景

Block从简单到高级的使用,以及项目中经常使用的场景 一、Block的简单使用        使用Block的三个步骤:1.定义Block变量;2.定义Block(即创建block代码块)3.调用block匿名函数; 1>以下是使用的两个简单的例子: 无参数,无返回值 有参数有返回值 2>定义Block变量的时候一般大家都是用typedef重定义,

u014536527的专栏 8026

手把手教你理解特斯拉Model 3的域控制器架构:RBCM如何成为“多面手”?

本文深入解析特斯拉Model 3的域控制器架构,重点介绍右车身域控制器(RBCM)如何整合车身电子系统功能。RBCM作为多任务处理中枢,管理空调系统、尾部电气设备和超声波雷达等关键功能,展现了特斯拉在汽车电子架构上的创新设计。文章详细探讨了RBCM的硬件架构、功能版图及信号网络,揭示其如何通过高性能MCU和智能开关技术实现功能集中与效率提升。

weixin_30919429的博客 383

block的高级用法(一) -- block传值

我们先从简单的入手,先讲下block的传值,后面会有更精彩的。 在讲传值之前我们先复习下block的基础知识

Ocean WANG的博客 1907

Unity VR 零基础开发之 Pico4 MR

2、弹出弹窗后,点击Android选项,然后再点击Switch Platform按钮切换成安卓平台。3、切换完成后Android选项后面会显示unity图标。

十画的博客 5096

关于使用EB tresos出现无法激活的情况解决

我在安装eb studio时就是在激活方面有问题导致无法使用,下面讲解出现了什么问题以及我如何去解除的。EB安装完成时需要激活才能使用的,不然都打开或者建立不了工程,一直都是报的dns服务器错误,好的,那我改dns服务器地址。填写dns服务器地址,也可以换成别的,自己百度一下适合自己的。这是解决方案哈哈哈哈。

weixin_43673603的博客 6182

IOS开发Block代码块基本语法使用-从基础到高级

1,Block的定义格式  返回值类型 (^block变量名)(形参列表) = ^(形参列表) {}; 2,使用typedef定义Block类型 typedef 返回值类型 (^block类型名称)(形参列表); 定义好的Block变量可以像其它数据类型那样方便的使用了。注意:Block语法是苹果公司提出的C语言的新功能,并不是标准功能。 另外,当函数的返回值是block代码

hell03W 2425

华为OD技术面真题 - 计算机网络 - 1

汇总华为OD技术面八股文计算机网络相关八股文,高频技术真题并提供详细解答。

qq_45776114的博客 1327

《iOS高级编程》中的Block:概要规范篇

文章目录关于我的仓库前言准备工作Blocks概要疑惑: p.81 buttonIdBlock语法与一般C语言函数不同点:格式标准格式省略格式block变量截获自动变量__block说明符截获的自动变量 关于我的仓库 这篇文章是我为面试准备的iOS基础知识学习中的一篇 我将准备面试中找到的所有学习资料,写的Demo,写的博客都放在了这个仓库里iOS-Engineer-Interview 欢迎sta...

KevinAshen的博客 485

打脸来的太快了,又发现一个Trae的宝藏功能--内置浏览器可以指定机型来显示前端界面

今天给大家分享一个Trae的实用功能,就是SOLO模式下内置浏览器可以指定不同的手机、平板机型来查看前端页面显示效果,我感觉这个功能很棒,忍不住就跟大家分享一下。

cpp加油站的专栏 6564

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

【iOS高级资深工程师面试篇】⑤、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 Block部分 Block介绍-截获变量-__block修饰符-Block的内存管理、循环引用

⑤、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 Block部分

慢慢来就很快。 356

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

【iOS高级资深工程师面试篇】⑫、2022年,金九银十我为你准备了iOS面试总结 UI视图-OC语言-Runtime-内存-block-多线程-RunLoop-网络-设计模式-架构/框架-算法-第三方

⑫、2022年,金九银十我为你准备了iOS面试总结 UI视图-OC语言-Runtime-内存-block-多线程-RunLoop-网络-设计模式-架构/框架-算法-第三方

慢慢来就很快。 810

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

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

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

长沙火山 4262

Block高级用法(传值)

Block

liangshi1的专栏 650

AutoGPT Platform Block SDK 实战指南:用 ProviderBuilder 与 Block 基类构建高级功能块

本篇技术指南以 [docs/platform/block-sdk-guide.md](https://link.gitcode.com/i/a2cf3b35416cdef17051b69a4c62f747) 为骨架,围绕 AutoGPT Platform 的 Block SDK 开发模式展开,讲解如何从零编写带 Provider 配置、输入/输出 Schema、OAuth/Webhook 支持、媒

gitblog_00130的博客 339

create-guten-block高级技巧:提升你的Gutenberg区块开发效率

你是否正在寻找一种简单高效的方式来开发WordPress Gutenberg区块?create-guten-block正是你需要的零配置开发工具包!这个强大的工具让开发者能够在几分钟内创建专业的WordPress Gutenberg区块插件,无需手动配置React、webpack、ES6/7/8/Next、ESLint、Babel等复杂工具。通过本文的5个高级技巧,你将掌握如何最大化利用creat

gitblog_00862的博客 758

【Node高级进阶】聊聊 Node 源码中常用到的 Block 知识点

小伙伴们感兴趣可以去源码 https://github.com/nodejs/node/blob/main/lib/buffer.js查看具体实现,并且在用时可以根据具体场景调整相关参数以优化性能。感兴趣的可以看下源码:https://github.com/nodejs/node/blob/main/lib/buffer.js。1,2,3 场景基本都是使用官方api默认就好,感兴趣的可以微调 第4部分一个合适的block大小比较重要。固定长度,最少2字节,中文也是2字节,处理简单。

程序员成长指北 855

iOS高级理论:Block的应用

Block 是 Objective-C 和 Swift 中的一种语言特性,可以用来封装一段代码并在需要时执行。

长沙火山 1213

iOS开发 - OC - block的详解 - 基础篇

深入理解oc中的block 苹果在Mac OS X10.6 和iOS 4之后引入了block语法。这一举动对于许多OC使用者的编码风格改变很大。就我本人而言,感觉block用起来还是很爽的,但一直以来,都是知其然,而不知所以然。这篇文章一共有两篇,其中基础篇讲解了block的基本的使用和创建,以及一些注意事项。在深入篇中,我将会对block的一些原理陈述出来,探讨block的...

a26579424的博客 310

【block第三篇】内存管理——如何验证block在栈上,还是堆上

这就需要引入三个名词: ● _NSConcretStackBlock ● _NSConcretGlobalBlock ● _NSConcretMallocBlock 正如它们名字说的那样,说明了block的三种存储方式:栈、全局、堆。 【在ARC下】:似乎已经没有栈上的block了,要么是全局的,要么是堆上的。有一个特殊情况:如果仅仅定义了block没有赋值给变量的话,仍是在栈上,比如: 【在非ARC下】:存在这栈、全局、堆这三种形式。

郭晓东的专栏 1万+

block传值以及利用block封装一个网络请求类

1.block在俩个UIViewController间传值 最近刚学了几招block 的高级用法,其实就是利用block语法在俩个UIViewController之间传值,在这里分享给初学者,同时也方便我自己理解。我们知道UINavigationController类管理UIViewController的时候,利用的是“栈”的思想,在这里不做过多解释,切入正题,假设我们现在有俩个UIViewCo

逍遥居 2504
上一篇: 升级Xcode7问题
下一篇: iOS实现截屏并保存到相册
joneOS
博客等级 码龄12年 1粉丝 19原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值