【Objective-C】浅析Block及其捕获机制

目录

    • Block的基本使用
      • Block的声明
      • Block的实现
      • Block的调用
    • Block作为形参使用
    • Block作为属性使用
      • 给Block起别名
      • Block的copy
    • Block的捕获机制
      • auto类型的局部变量
      • __block浅析
      • static类型的局部变量
      • 全局变量
    • 其他问题

Block的基本使用

什么是Block?

Block (块),封装了函数调用以及调用环境的 OC 对象,Objective-C闭包(可以在内部访问外部的值),相当于C语言的函数指针,把一个函数写在一个函数内部,而OC并没有函数(方法)嵌套这一语法

Block的声明

void(^blockName)();
int(^blockName2)(int a, int b, int c);

格式: 返回值 (^block名称)(形参列表)
^代表块的符号

Block的实现

  1. 无参数无返回值
void(^blockName)(void) = ^{

};
  1. 有参数无返回值
void(^blockName)(int a, int b) = ^(int a, int b){

};
  1. 无参数有返回值
int(^blockName)(void) = ^int{
    return 3;
};

实现部分的返回值可以省略,像这样:

int(^blockName)(void) = ^{
    return 3;
};
  1. 有参数有返回值
int(^blockName)(int a, int b) = ^int(int a, int b){
    return 3 + a * b;
};

实现部分的返回值int同样可以省略

Block的调用

//无参数无返回值
blockName();

//有参数有返回值
int result = blockName(7, 12);

现在已声明的blockName代表一个块,那么调用这个块既可以通过blockName(7, 12);,也可以这样:

int result = ^(int a, int b) {
    return 3 + a + b;
}(7, 12);

Block作为形参使用

Block作为形式参数在方法中的声明与上述格式略有不同(块的名称在外面)

现在Jaxon类和Jacky类中分别实现以下方法:
Jaxon.h

- (void)askJackyForHelp: (void(^)(int num))blockName isOK: (void(^)(BOOL boolValue))completion;

Jaxon.m

- (void)askJackyForHelp:(void (^)(int))blockName isOK:(void (^)(BOOL))completion {
    blockName(3);

    //传入completion块的参数非1即0
    completion(arc4random() % 2);
}

Jacky.m

- (void)helpDoWith: (int)num {
    NSLog(@"帮忙做事%d次", num);
}

接下来在main函数中调用:

Jaxon* jaxon = [[Jaxon alloc] init];
[jaxon askJackyForHelp:^(int num) {
            Jacky* jacky = [[Jacky alloc] init];
            [jacky helpDoWith: num];
        } isOK:^(BOOL boolValue) {

            //成功和失败的概率各占一半
            if (boolValue) {
                NSLog(@"帮忙成功");
            } else {
                NSLog(@"帮忙失败");
            }
        }];

运行结果:

请添加图片描述

这样是不是可以起到代理的作用,Jaxon委托Jacky帮忙做事,Jaxon实现不了的委托Jacky实现,因此Block块也可以用于界面传值或其他需要使用代理模式的程序设计中

Block作为属性使用

给Block起别名

文章开头也提到了块其实也是一种对象,可以将ta理解为一种数据类型

那么也可以用typedef关键字给Block起别名,看以下示例:

typedef void(^Help)(int num);
typedef void(^Finish)(BOOL boolValue);

上面的方法也就可以这样声明:

- (void)askJackyForHelp:(Help)blockName isOK:(Finish)completion;

块的属性关键字一般需要是是copy

@interface Jaxon : NSObject

//无别名
@property (nonatomic, copy)void(^helpBlock)(int num);
//有别名
//@property (nonatomic, copy)Help helpBlock;
- (void)askMyselfDo;

@end

@implementation Jaxon

- (void)askMyselfDo {
    self.helpBlock(5);
}

@end

main函数:

Jaxon* jaxon = [[Jaxon alloc] init];
jaxon.helpBlock = ^(int num) {
    NSLog(@"我自己做%@次", @(num));
};
[jaxon askMyselfDo];

运行结果:
请添加图片描述

Block的copy

关于copy关键字,编者也简单了解一下,底层原理以后再加以详细的剖析:

ARC 环境下,编译器会根据情况自动将上的 block 复制到上,比如以下几种情况: 手动调用 block 的copy`方法时;

  • block 作为函数返回值时(Masonry 框架中用很多);
  • 将 block 赋值给__strong指针时;
  • block 作为 Cocoa API 中方法名含有usingBlock的方法参数时;
  • block 作为 GCD API 的方法参数时。

block 作为属性的写法:
ARC下写strong或者copy都会对 block 进行强引用,都会自动将 block 从栈 copy 到堆上;
建议都写成copy,这样 MRC 和 ARC 下一致。

Block刚创建时存放在栈区,使用时copy到堆区

Block的捕获机制

为保证Block内部能正常访问到外部的变量,Block有一种变量捕获机制

auto类型的局部变量

auto变量:正常定义出来的变量默认都是auto类型,只是省略了

auto int age = 20;

auto类型的局部变量会被捕获到block块内部,访问方式为值传递

int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
    NSLog(@"%d %p", age, &age);
};
age = 20;
//可以打印出来,说明block块是可以访问到外部信息的
blockName();
NSLog(@"%d %p", age, &age);

请添加图片描述
根据运行结果可以得出以下两点:

  • auto类型的局部变量被捕获到block块内部时,block内部会自动生成一个相同的成员变量,用来存储这个变量的值,因此打印的block外部的age地址与内部age地址不一样
  • 由于值传递,修改外部age变量的值,不会影响到block内部的变量

__block浅析

Block内部只能调用外部变量,不能修改:

请添加图片描述

Block 默认情况下是使用被捕获的外部变量的只读拷贝,因此在 Block 内部无法直接修改外部变量的值

解决办法如下:

  • 变量用static修饰(原因:捕获static类型的局部变量是指针传递,可以访问到该变量的内存地址)
  • 全局变量
  • __block(我们只希望临时用一下这个变量临时改一下而已,而改为 static 变量和全局变量会一直在内存中)

当变量被__block修饰时,block可以修改外部全局变量:

__block int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
    age = 30;
    NSLog(@"%d %p", age, &age);
};
blockName();
NSLog(@"%d %p", age, &age);

请添加图片描述


static类型的局部变量

static类型的局部变量会被捕获到block内部,访问方式指针传递

static int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
    NSLog(@"%d %p", age, &age);
};
age = 20;
blockName();
NSLog(@"%d %p", age, &age);

请添加图片描述

  • static类型的局部变量被捕获到block内部时,block块内部会生成一个相同类型的指针,指向捕获到内部的age变量的地址
  • 由于指针传递,修改外部的age变量的值,会影响到block内部的age变量

全局变量

全局变量不会被捕获到block内部,访问方式为直接访问

int _age = 10;
static int _height = 175;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"%d %p", _age, &_age);
        void(^blockName)(void) = ^ {
            _age = 30;
            NSLog(@"%d %p", _age, &_age);
        };
        blockName();
        _age = 20;
        NSLog(@"%d %p", _age, &_age);
    }
    return 0;
}

请添加图片描述

其他问题

对于对象类型的局部变量,block会连同ta的所有权修饰符一起捕获

为什么局部变量需要捕获,全局变量不用捕获呢?

  • 作用域的原因,全局变量哪里都可以直接访问,所以不用捕获;
  • 局部变量,外部不能直接访问,所以需要捕获;
  • auto 类型的局部变量可能会销毁,其内存会消失,block 将来执行代码的时候不可能再去访问那块内存,所以捕获其值;
  • static 变量会一直保存在内存中, 所以捕获其地址即可

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

超市商品管理系统 毕业设计 JAVA+Vue+SpringBoot+MySQL

项目下载地址 目录 项目下载地址 一、摘要1.1 简介1.2 项目录屏 二、研究内容2.1 数据中心模块2.2 超市区域模块2.3 超市货架模块2.4 商品类型模块2.5 商品档案模块 三、系统设计3.1 用例图3.2 时序图3.3 类图3.4 E-R图 四、系统实现4.1 登录4.2 注册4.3 主页4.4 超市区域管理4…

【嵌入式开发学习】__单片机中容易造成内存泄露的几个痛点

目录 前言 一、程序运行 二、什么是内存泄露? 三、内存泄露的严重后果! 四、如何定位到泄露的要点? 五、三大痛点 1. 访问越界 2. 栈 3. 堆 六、泄露常见的场景 1. 重新赋值 2. 首先释放父块 3. 返回值的不正确处理 七、常见的…

Azure - 机器学习:创建机器学习所需资源,配置工作区

目录 一、Azure机器学习工作区与计算实例简要介绍工作区计算实例 二、创建工作区1. 登录到 Azure 机器学习工作室2. 选择“创建工作区”3. 提供以下信息来配置新工作区:4. 选择“创建”以创建工作区 三、创建计算实例四、工作室实战4.1 工作室快速导览4.2 从示例笔记…

常用linux命令 linux_cmd_sheet

查看文件大小 ls -al 显示每个文件的kb大小 查看系统日志 dmesg -T | tail 在 top 命令中,RES 和 VIRT(或者 total-vm)是用来表示进程内存使用的两个不同指标,它们之间有以下区别: RES(Resident Set Size…

TWAS模型数据*.wgt.RDat查看及导入R

TWAS模型数据*.wgt.RDat查看及导入R 1 数据导入R load(“./SLC7A8.wgt.RDat”) as.data.frame(snps) -> snp snp %>% head()V1 V2 V3 V4 V5 V6 1 12 rs10747759 0 55683634 C T 2 12 rs2293409 0 55684180 A C 3 12 rs1048103 0 55684405 C …

Pytorch代码入门学习之分类任务(二):定义数据集

一、导包 import torch import torchvision import torchvision.transforms as transforms 二、下载数据集 2.1 代码展示 # 定义数据加载进来后的初始化操作: transform transforms.Compose([# 张量转换:transforms.ToTensor(),# 归一化操作&#x…

FLStudio2024汉化破解版在哪可以下载?

水果音乐制作软件FLStudio是一款功能强大的音乐创作软件,全名:Fruity Loops Studio。水果音乐制作软件FLStudio内含教程、软件、素材,是一个完整的软件音乐制作环境或数字音频工作站... FL Studio21简称FL 21,全称 Fruity Loops Studio 21,因此国人习惯叫…

图像数据噪音种类以及Python生成对应噪音

前言 当涉及到图像处理和计算机视觉任务时,噪音是一个不可忽视的因素。噪音可以由多种因素引起,如传感器误差、通信干扰、环境光线变化等。这些噪音会导致图像质量下降,从而影响到后续的图像分析和处理过程。因此,对于从图像中获…

0027Java程序设计-房屋出租管理系统

文章目录 摘 要目 录系统设计开发环境 摘 要 随着我国市场经济的快速发展和人们生活水平的不断提高,简单的房屋出租服务已经不能满足人们的需求。如何利用先进的管理手段,提高房屋出租的管理水平,是当今社会所面临的一个重要课题。 本文采用…

I/O 模型学习笔记【全面理解BIO/NIO/AIO】

文章目录 I/O 模型什么是 I/O 模型Java支持3种I/O模型BIO(Blocking I/O)NIO(Non-blocking I/O)AIO(Asynchronous I/O) BIO、NIO、AIO适用场景分析 java BIOJava BIO 基本介绍Java BIO 编程流程一个栗子实现…

【2024秋招】小米中间件后端开发一面2023-9-13-base武汉

1 自我介绍 2 快手实习 2.1 讲讲你写的curd启动器,做了哪些工作呢 答: 2.2 网上也有一些开源的curd代码生成器,你为什么需要自研呢(重要) 答: (1)这个必须得自研,因…

『第三章』雨燕栖息地:Swift 开发环境

在本篇博文中,您将学到如下内容: 1. Swift 开发平台2. Swift 集成开发环境 Xcode?3. 原型试验场:Playground4. 另一种尝试:iPad 上的 Swift Playgrounds5. Swift 交互实验室:Swift REPL总结 咫尺春三月,寻常百姓家。为…

【RTOS学习】软件定时器 | 中断处理

🐱作者:一只大喵咪1201 🐱专栏:《RTOS学习》 🔥格言:你只管努力,剩下的交给时间! 软件定时器 | 中断处理 🏀软件定时器⚽守护任务守护任务的调度 ⚽使用软件定时器的函数…

多级缓存入门

文章目录 什么是多级缓存JVM进程缓存环境准备安装MySQL导入Demo工程导入商品查询页面 初识Caffeine Lua语法初识Lua第一个lua程序变量和循环Lua的数据类型声明变量循环 条件控制、函数函数条件控制 多级缓存安装OpenRestyOpenResty快速入门反向代理流程OpenResty监听请求编写it…

【linux】麒麟v10安装Redis哨兵集群(ARM架构)

安装redis单示例的请看:麒麟v10安装Redis(ARM架构) 安装服务器 ​Hostname​IP addressmaster,sentinel192.168.0.1slave1,sentinel192.168.0.2slave2,sentinel192.168.0.3 下载安装包 (三台都操作) wget https://re…

施工进度难以把控,项目失控怎么办?

在施工项目中,施工进度的控制是非常重要的,它直接关系到项目是否能够按时完成以及项目成本的控制。然而,有时候由于各种原因,施工进度可能会难以把控,导致项目失控。那么,当项目失控时,我们应该…

Ansible上通过roles简化playbook演示介绍

目录 一.roles介绍 1.作用 2.role的目录结构 3.role和tasks的执行优先级顺序 二.自定义一个httpd的角色 1.完整目录结构展示 2.主要的各个目录配置 (1)vars目录和templates目录 (2)tasks目录和handlers目录 &#xff08…

云原生Docker数据管理

目录 Docker的数据管理 数据卷 数据卷容器 容器互联 容器中管理数据主要有两种方式: 数据卷(Data Volumes)数据卷容器(Data Volume Dontainers) Docker的数据管理 数据卷 数据卷是一个供容器使用的特殊目录&a…

题目 1120: C语言训练-“水仙花数“问题2python详解)——练气三层后期

✨博主:命运之光 🦄专栏:算法修炼之练气篇(C\C版) 🍓专栏:算法修炼之筑基篇(C\C版) 🍒专栏:算法修炼之练气篇(Python版) ✨…

今天玩到一个微信钓鱼小游戏,很有趣,居然还能玩萝卜刀

这款钓鱼冠军微信小游戏很有创意,除了传统的钓鱼玩法,居然还融合了黄金矿工的玩法,很不错的想法,而且居然还能玩最近比较火的萝卜刀,快来扫码体验一下吧,或者微信里搜索 《钓鱼冠军》小游戏,认…