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

iOS基础-Block

系列文章目录


文章目录

  • 系列文章目录
  • 一、Block是什么
  • 二、Block的使用场景
    • 1. 异步操作和完成处理器
    • 2. 动画
    • 3. 集合操作
    • 4. 定时器
    • 5. 自定义控件的事件处理
    • 6.错误处理
  • 三、Block的底层实现
    • 1.结构分析
    • 2.Block的类型
    • 3.Block的copy
    • 4.变量捕捉
  • 四、Block的使用细节
    • 1.auto变量的生命周期
    • 2.__weak修饰变量
    • 3.修改局部变量
      • a.定义成全局变量
      • b.定义成static变量
      • c.__block修饰auto变量
    • 4.循环引用


一、Block是什么

在 iOS 开发中,Block 是 Objective-C 和 Swift 中非常强大的一个特性,用于定义一段可以在任何时候执行的代码块。Block 可以捕获和存储其定义时所处上下文的状态,使得它们特别适用于处理异步操作、回调以及集合类的遍历。

Block 类似于其他语言中的匿名函数或闭包。在 Objective-C 中,Block 是一种特殊的数据类型,可以像对象一样被传递和存储。

在 Objective-C 中,你可以这样定义一个 Block:

//这里,myBlock 是一个接受一个整数参数并不返回任何值的 Block。
void (^myBlock)(int) = ^(int num) {NSLog(@"The number is %d", num);
};//调用 Block,输出: The number is 10
myBlock(10);  

如果我们觉得定义一个 Block 很复杂,也可以用 typedef 去简化:

typedef void (^myBlock)(int);myBlock b1 = ^(int num) {NSLog(@"wml->num:%d",num);
};//调用 Block,输出: wml->num:100
b1(100);

二、Block的使用场景

1. 异步操作和完成处理器

//Block 是处理异步操作如网络请求、文件读写等的理想选择。它们通常用作回调,以处理异步操作完成后的数据或状态更新。
[self fetchDataWithURL:url completion:^(NSData *data, NSError *error) {if (error) {NSLog(@"Failed to fetch data: %@", error);} else {NSLog(@"Data fetched successfully.");// 处理数据}
}];

2. 动画

Block 在定义动画过程中非常有用,特别是使用 UIKit 的动画API时。它可以在动画结束时执行一段代码,非常适合于需要在动画序列完成后更新UI的场景。

[UIView animateWithDuration:1.0 animations:^{self.myView.alpha = 0.0;
} completion:^(BOOL finished) {self.myView.hidden = YES;
}];

3. 集合操作

Block 在处理数组、字典等集合类型时非常有用,例如执行过滤、转换、排序等操作。

NSArray *numbers = @[@1, @2, @3, @4, @5];
[numbers enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BOOL *stop) {NSLog(@"Number: %@", number);if ([number integerValue] > 3) {*stop = YES; // 提前终止遍历}
}];

4. 定时器

Block 也可以用于创建定时器,特别是在需要简单任务重复执行时。

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{NSLog(@"Timer fired");
});
dispatch_resume(timer);

5. 自定义控件的事件处理

Block 允许开发者为自定义控件提供灵活的事件处理机制,使得控件的使用更加灵活和强大。

[self.customButton handleTapWithBlock:^{NSLog(@"Button was tapped!");
}];

6.错误处理

在执行某些可能会失败的任务时,Block 可用于错误处理和恢复策略。

[self performTaskWithCompletion:^(BOOL success, NSError *error) {if (!success) {NSLog(@"Task failed: %@", error);// 处理错误,尝试恢复}
}];

三、Block的底层实现

1.结构分析

我们可以将下面的 oc 代码转换成 c++ 来看看Block的实现:

int main(int argc, const char * argv[]) {int age = 20;void (^block)(void) =  ^{NSLog(@"age is %d",age);};block();return 0;
}

关键代码如下:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int age;//构造函数(类似于OC中的init方法) _age是外面传入的__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {//isa指向_NSConcreteStackBlock 说明这个block就是_NSConcreteStackBlock类型的impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_z2_5qyd6hbj171cdpwjgskps6kc0000gn_T_main_38b1a4_mi_0,age);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, const char * argv[]) {int age = 20;void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);return 0;
}static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我们可以将上述代码去除一些类型转换的逻辑,进一步简化为:

int age = 20;
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age);// block的调用
block->FuncPtr(block);

用一幅图来表示:
在这里插入图片描述

2.Block的类型

首先,Block 是一个 oc 对象,我们可以看到它的继承关系为:

在这里插入图片描述
Block 有三种基本类型,这些类型反映了 Block 在内存中的存储位置,以及它如何管理捕获的变量:

  1. _NSConcreteGlobalBlock:全局 Block,不捕获任何外部变量,或者只捕获全局变量和静态变量。存储在全局数据区。
  2. _NSConcreteStackBlock:栈 Block,捕获外部变量,并存储在栈上。这种 Block 如果需要在定义域之外使用,必须进行复制操作,将其复制到堆上。
  3. _NSConcreteMallocBlock:堆 Block,是通过复制栈 Block 得到的,存储在堆上,可以在定义域之外安全使用。

在 MRC 下测试:

因为ARC的时候,编译器做了很多的优化,往往看不到本质。

  • 改为MRC方法: Build Settings 里面的Automatic Reference Counting改为NO。

在这里插入图片描述

当 Block 被复制时(使用 [block1 copy]),它被转移到堆上,因此变成了 _NSConcreteMallocBlock 类型。

在 ARC 下测试:

在这里插入图片描述

在ARC环境下,编译器会根据情况自动将栈上的 block 复制到堆上,具体来说比如以下情况:

  • block作为函数返回值时
  • 将block赋值给__strong指针时
  • block作为Cocoa API中方法名含有usingBlock的方法参数时
  • block作为GCD API的方法参数时

情况总结如下:
在这里插入图片描述

3.Block的copy

下面测试是在 ARC 的环境下,编译器会根据情况自动将栈上的 block 复制到堆上

block作为函数返回值时:

在这里插入图片描述

Block 属性的声明

  • copy
    最常见且推荐的方式是使用 copy 修饰符。这是因为 Block 默认在栈上创建,而使用 copy 可以确保 Block 被复制到堆上,从而在其作用域之外也可以安全使用。
  • strong
    通常不推荐用于 Block,因为这不会将栈上的 Block 复制到堆上,可能会导致在 Block 执行时已经不在有效的作用域内。
  • weak
    用于避免循环引用,特别是当 Block 内部需要引用 self,且 self 同时持有这个 Block 时。通常,你会在 Block 内部使用一个弱引用的 self,而不是将 Block 本身声明为 weak。

4.变量捕捉

对于不同类型的变量,有不同的捕捉方式:
在这里插入图片描述

我们还可以用一个例子来证明变量捕捉的情况:

在这里插入图片描述

查看底层代码,可以看到对于 auto 局部变量是用值传递,static 局部变量是用指针传递,全局变量则是直接读取:
在这里插入图片描述

四、Block的使用细节

编译器默认是 ARC 环境,所以未作声明的都是 ARC 环境。

1.auto变量的生命周期

在 ARC 环境下,auto 变量在出了作用域后会被销毁:

@interface MyObj : NSObject
@property (nonatomic ,assign) int age;
@end@implementation MyObj
- (void)dealloc { NSLog(@"%s",__func__); }
@endint main(int argc, const char * argv[])
{{MyObj *person = [[MyObj alloc]init];person.age = 10;}NSLog(@"----------------");return 0;
}

结果如下:

在这里插入图片描述

我们在 Block 中创建一个对象类型的 auto 变量:

// 定义block
typedef void (^MyBlock)(void);int main(int argc, const char * argv[])
{MyBlock block;{MyObj *obj = [[MyObj alloc]init];obj.age = 10;block = ^{NSLog(@"---------%d", obj.age);};NSLog(@"block.class = %@",[block class]);}NSLog(@"block销毁");return 0;
}

运行后我们发现,Block 类型为__NSMallocBlock__时,延长了变量的生命周期,在 Block 销毁后,变量才被销毁:
在这里插入图片描述

我们将 oc 代码转换为 cpp 代码,发现变量被捕捉到了 Block 内部:

在这里插入图片描述

在 MRC 环境下

我们发现 Block 为__NSStackBlock__类型时,并没有延长变量的生命周期:

在这里插入图片描述

我们通过对 Block 进行 copy,将类型转换为__NSMallocBlock__时,变量的生命周期延长了:

在这里插入图片描述

2.__weak修饰变量

在 MRC 环境下

当 Block 类型为__NSMallocBlock__时,用 __weak修饰变量时,Block 持有变量的弱引用,不影响变量的生命周期。
左图变量属性为 weak,右图属性为 strong。

在这里插入图片描述

当 Block 类型为__NSStackBlock__时,用 __weak修饰变量时,并不起作用。
左图变量属性为 weak,右图属性为 strong。

在这里插入图片描述

在 ARC 环境下

使用__weak 可以让 Block 对变量由默认的强引用变为弱引用,从而影响变量的生命周期。

在这里插入图片描述

无论是 MRC 还是 ARC:

  • 当 block 为__NSStackBlock__类型时候,是在栈空间,无论对外面使用的是 strong 还是 weak 都不会对外面的对象进行强弱引用。

  • 当 block 为__NSMallocBlock__类型时候,是在堆空间,block是内部的_Block_object_assign函数会根据strong或者 weak对外界的对象进行强引用或者弱引用。

3.修改局部变量

我们有三种方式可以在 Block 中去修改一个变量:

a.定义成全局变量

在这里插入图片描述

b.定义成static变量

在这里插入图片描述

c.__block修饰auto变量

在这里插入图片描述

4.循环引用

例如下面这段代码,在对象中持有了 Block,而 Block 又持有了对象的指针,出现了循环引用问题,导致资源泄露:

@interface MyObj : NSObject
@property (nonatomic ,assign) int age;
@property void (^MyBlock)(void);
@end@implementation MyObj
- (void)dealloc { NSLog(@"%s",__func__);}
@endint main(int argc, const char * argv[])
{MyObj *obj = [[MyObj alloc] init];obj.MyBlock = ^{NSLog(@"age->%d",obj.age);};return 0;
}

我们可以用 __weak 来修饰这个在 Block 中用到的指针:

在这里插入图片描述

用__unsafe_unretained 也可以解决循环引用问题,但它是不安全的:

  • __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil。
  • __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变。

在这里插入图片描述

用__block 也可以解决循环引用,Block 需要被调用一次,来执行 obj = nil:

在这里插入图片描述

总结:

在 ARC 环境下,最好使用 __weak 来修饰变量避免循环引用。
在 MRC 环境下,因为不支持弱指针__weak,所以,只能是 __unsafe_unretained 或者 __block 来解决循环引用。

相关文章:

iOS基础-Block

系列文章目录 文章目录 系列文章目录一、Block是什么二、Block的使用场景1. 异步操作和完成处理器2. 动画3. 集合操作4. 定时器5. 自定义控件的事件处理6.错误处理 三、Block的底层实现1.结构分析2.Block的类型3.Block的copy4.变量捕捉 四、Block的使用细节1.auto变量的生命周期…...

本地图片瀑布流浏览器asonry Image Viewer

本地图片瀑布流浏览器asonry Image Viewer 前言效果图部分源码领取完整源码下期更新 前言 一款采用 HTML 的瀑布流本地图片浏览器「Masonry Image Viewer」只需要把你的图片文件夹拖到下载的 index 网页文件里面就可以实现瀑布流效果。项目免费开源,据介绍采用了HT…...

macos重装系统 启动U盘制作方法 - createinstallmedia 命令使用方法总结

macos重装系统比windows要稍微复杂一些,不过还好,macos系统安装app这个Apple官方提供的系统软件里面默认就内置了一个可用为我们制作启动盘的工具 createinstallmedia 我们下载的apple安装镜像要门是 dmg/pkg/iso 的压缩档案格式的,要么是 x…...

八问八答搞懂Transformer内部运作原理

最近这一两周看到不少互联网公司都已经开始秋招提前批了。 不同以往的是,当前职场环境已不再是那个双向奔赴时代了。求职者在变多,HC 在变少,岗位要求还更高了。 最近,我们又陆续整理了很多大厂的面试题,帮助一些球友…...

MySQL增删改查(基础)

1、. 新增(Create) 语法: INSERT [INTO] table_name[(column [, column] ...)] VALUES (value_list) [, (value_list)] ... 例子: -- 创建一张学生表 DROP TABLE IF EXISTS student; CREATE TABLE student (id INT,sn INT com…...

Cairo库移植到安卓记录

前言 接Android Studio引入ndk编译的so库的故事,这个东西搞了两周以后,由于自己不熟悉Java和安卓开发,踩了不少坑,其中一周时间都是花在怎么用Android Studio上的。。。AS下的新版本Koala,结果网上资料全是旧版本&…...

Redis 哈希类型的常用命令总结

1. hset 设置哈希表中字段的值。 hset key field value示例: hset user:1000 name "Alice"2. hget 获取哈希表中字段的值。 hget key field示例: hget user:1000 name3. hgetall 获取哈希表中所有的字段和值。 hgetall key示例&#x…...

【物联网设备端开发】ESP开发工具:QEMU如何模拟以太网口接入网络

以太网口支持 ESP-IDF中添加了对Opencores以太网MAC的支持。 运行以太网示例时,启用CONFIG_EXAMPLE_CONNECT_ETHERNET和 CONFIG_EXAMPLE_USE_OPENETH.。运行自定义应用程序时,启用CONFIG_ETH_USE_OPENETH 并初始化以太网驱动程序,如示例 /c…...

Python学习笔记(四)

# 数据容器分为5类,分别是:列表(list)、元组(tuple)、字符串(str)、集合(set)、字典(dict)""" 演示数据容器之:list列表 语法:[元素&#xff…...

跨域:安全分步实施指南

什么是跨域问题? 跨域(Cross-Origin Resource Sharing,CORS)问题发生在浏览器的同源策略(Same-Origin Policy)限制下。当一个域上的网页试图访问另一个域上的资源时,浏览器会阻止这些操作以保护…...

【iOS】AutoreleasePool自动释放池的实现原理

目录 ARC与MRC项目中的main函数自动释放池autoreleasepool {}实现原理AutoreleasePoolPage总结 objc_autoreleasePoolPush的源码分析autoreleaseNewPageautoreleaseFullPageautoreleaseNoPage autoreleaseFast总结 autorelease方法源码分析objc_autoreleasePoolPop的源码分析po…...

stm32—GPIO

0. 引入 在单片机产品中,我们常常可以见到三种模块:LCD灯、KEY按键、BEEP蜂鸣器 LED灯: 一个比较常见的LED电路LED0 ---------- 通过控制LED0引脚(电线) 给它一个低电平(低电压),LED灯就会亮 给它一个高电平(高电压),LED灯就会灭 …...

CocosCreator使用 ProtoBuf WebSocket与服务器对接方法

在 Cocos Creator 中使用 .proto 文件和转换成 TypeScript(TS)两者各有其优缺点,具体选择取决于你的项目需求和团队的开发习惯。以下是两者的一些比较: 1、使用 .proto 文件的优点: 跨语言支持:Protocol B…...

【python基础】while循环语句练习

明显可以感觉到循环比判断要更加难以理解一些,这个就只能通过练习来提高理解和思维能力了。 学习视频:第一阶段-第四章-05-while循环案例-九九乘法表_哔哩哔哩_bilibili 练习一:计算1-10的和 i1#循环的起始值 sum0 while i&l…...

【SpringBoot系列】WebMvcConfigurer配置

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

学懂C++ (十九):高级教程——深入详解C++信号处理

目录 C中的信号处理 1. 信号处理的本质 2. 主要信号类型 3. 核心关键点 4. 经典实例 代码分析 5. 进阶:信号屏蔽与多线程 例子:使用sigaction() 6. Windows中的信号处理 7. 比较与总结 示例:Windows控制台事件处理 总结 C中的信号…...

SOMEIP_ETS_032:echoUINT8ArrayMinSize

测试目的: 确保DUT能够正确处理最小尺寸的UINT8数组参数,并且在发送和接收过程中保持参数值和顺序不变。 描述 本测试用例旨在验证DUT在处理包含最小尺寸UINT8数组参数的SOME/IP消息时,是否能够准确地发送和接收这些参数,确保返…...

JS+CSS案例:可适应上下布局和左右布局的菜单(含二级菜单)

今天,我给大家分享一个原创的CSS菜单,整个菜单全由CSS写成,仅在切换布局时使用JS。合不合意,先看看效果图。 本例图片 接下来,我来详细给大家分享它的制作方法。 文件夹结构 因为涉及到了样式表切换,所以,你需要借鉴一下我的文件夹结构。 CSS文件夹: reset.css 用于…...

【数据结构】线性表,顺序表

一. 线性表 1. 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 2. 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 3. 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理…...

Spring之最基础的创建与Bean的存储与获取(还有Maven加载jar包报错的解决方法)

目录 创建Spring项目 如何解决Maven加载jar包报错 存储Bean和取Bean 在Java中对象也称为Bean。所以后面我提到Bean,大家要知道我说的是什么。 创建Spring项目 我的idea是2022版本的,如果大家和我一样可以参考我的。 1.创建一个Maven项目。图片上忘了…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

6.9-QT模拟计算器

源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...

STM32标准库-ADC数模转换器

文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”&#xff1a;输入模块&#xff08;GPIO、温度、V_REFINT&#xff09;1.4.2 信号 “调度站”&#xff1a;多路开关1.4.3 信号 “加工厂”&#xff1a;ADC 转换器&#xff08;规则组 注入…...