【iOS】—— 初识block

news2025/1/15 20:45:28

block

文章目录

  • block
    • 什么是block?
    • block语法
    • Block变量
    • 截获自动变量值
    • __block说明符
    • 截获的自动变量
    • block的三种存储类型
      • NSGlobalBlock
      • NSStackBlock
      • NSMallocBlock
    • block的父类
    • block循环引用
    • 未完待续······

什么是block?

Blocks是带有自动变量(局部变量)的匿名函数。

block语法

标准格式:
在这里插入图片描述

例子:

^int (int count) {
	return count + 1;
}

返回值类型默认void,如果是void 我们也可以默认省略(void)。
在这里插入图片描述

Block变量

在c中,我们可以将函数地址赋值给函数指针类型,同理,Block语法也可以赋值给Block类型的变量,声明Block类型变量示例如下:

int (^blk) (int);

Block类型变量和一般变量完全相同,可以作为以下用途:

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

将Block赋值为Block类型变量:

void (^blk) (void) = ^{
	
};

函数中Block作为参数:

int func(int (^blk) (int));

在函数返回值中指定Block类型,可以将Block作为返回值:

blk func() {
    return ^(void){
        printf("!");
    };
}

截获自动变量值

通过Block语法和Block类型变量的说明,我们己经理解了“ 带有自动变量值的匿名函数” 中的“匿名函数”。而“带有自动变量值” 究竟是什么呢?“带有自动变量值” 在Blocks中表现 为“ 截获自动变量值”。截获自动变量值的实例如下:

void interceptAutomaticVariable() {       //截获自动变量
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{
        printf(fmt, val);
    };
    val = 2;
    fmt = "These values were changed. val = %d\n";
    blk();
}

该源代码中,Block 语法的表达式使用的是它之前声明的自动变量fmt 和val 。Blocks 中, Block 表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为Block 表达式保存 了自动变量的值,所以在执行Block 语法后,即使改写Block 中使用的自动变量的值也不会影响 Block 执行时自动变量的值。该源代码就在Block语法后改写了Block中的自动变量val 和fmt。 下面我们一起看一下执行结果:
在这里插入图片描述
执行结果并不是改写后的值“These values were changed. val= 2”,而是执行Block语法时的自动变量的瞬间值。该Block语法在执行时,字符串指针“val = %d” 被赋值到自动变量fmt中,int 值10被赋值到自动变量val 中,因此这些值被保存(即被截获),从而在执行块时使用。

__block说明符

实际上,自动变量值截获只能保存执行Block 语法瞬间的值。保存后就不能改写该值。下面我们来尝试改写截获的自动变量值,看看会出现什么结果。下面的源代码中,Block 语法之前声明的自动变量val 的值被赋予1。

void the__block() {     //__block测试
    int val = 0;
    void (^blk) (void) = ^{
        val = 1;
    };
    blk();
    printf("val = %d\n", val);
}

该代码会出现报错:
在这里插入图片描述

这时候就需要给val加上__block关键字了。

void the__block() {     //__block测试
    __block int val = 0;
    void (^blk) (void) = ^{
        val = 1;
    };
    blk();
    printf("val = %d\n", val);
}

输出结果:
在这里插入图片描述

使用附有__block 说明符的自动变量可在Block 中赋值,该变量称为__block 变量。

截获的自动变量

那么截获Objective-C 对象,调用变更该对象的方法也会产生编译错误吗?

id array = [[NSMutableArray alloc] init];
    void (^blk) (void) = ^ {
        id obj = [[NSObject alloc] init];
        [array addObject:obj];
    };

这是没有问题的,而向截获的变量array 赋值则会产生编译错误。该源代码中截获的变量值 为NSMutableArray 类的对象。如果用C语言来描述,即是截获NSMutableArray类对象用的结构体实例指针。虽然赋值给截获的自动变量array 的操作会产生编译错误,但使用截获的值却不会有任何问题。下面源代码向截获的自动变量进行赋值,因此会产生编译错误。

    id array2 = [[NSMutableArray alloc] init];
    void (^blk2) (void) = ^ {
        array2 = array;
    };

这时候就会有和之前类似的错误:
在这里插入图片描述

block的三种存储类型

  • _NSConcreteStackBlock(栈区)
  • _NSConcreteGlobalBlock(数据区)
  • _NSConcreteMallocBlock(堆区)

在这里插入图片描述

那么三种储存方式有什么区别呢?
简单来说,没有捕获自动变量的就是数据区,捕获了自动变量但是没有进行copy操作就是栈区,copy之后就变成了堆区。

NSGlobalBlock

如果一个 block 没有访问外部局部变量,或者访问的是全局变量,或者静态局部变量,此时的 block 就是一个全局 block ,并且数据存储在全局区。

//block1没有引用到局部变量
    int a = 10;
    void (^block1)(void) = ^{
         NSLog(@"hello world");
    };
    NSLog(@"block1:%@", block1);

    //    block2中引入的是静态变量
    static int a1 = 20;
    void (^block2)(void) = ^{
        NSLog(@"hello - %d",a1);
    };
    NSLog(@"block2:%@", block2);

在这里插入图片描述

NSStackBlock

    int b = 10;
    NSLog(@"%@", ^{
        NSLog(@"hello - %d",b);
    });

输出结果:
在这里插入图片描述

我们神奇的发现,按照概念上所说写出的为什么不是NSStackBlock呢,这个问题困扰了我很久,经过查阅了很多资料后明白,在ARC环境下,系统会自动将block进行拷贝操作,我们改成MRC试试,果然,结果正确了。
那么在ARC下怎么让block存储在栈上呢?

    int c = 10;
    NSLog(@"%@", (__weak)^{
        NSLog(@"hello - %d",c);
    });

我们可以这么做,在之前我们学习过weak关键字,weak关键字的作用是弱引用避免循环引用
我们来看下面一段话:
在这里插入图片描述

基本可以明白,weak避免了循环引用。

除此之外还发现了一个神奇的问题:
我们分别打印这四个值:

    int b = 10;
    NSLog(@"%@", ^{
        NSLog(@"hello - %d",b);
    });
    
    int c = 10;
    NSLog(@"%@", (__weak)^{
        NSLog(@"hello - %d",c);
    });
    
    int a = 10;
    NSLog(@"%@", [^{
        NSLog(@"hello - %d",a);
    } description]);
    
    int age = 15;
    NSLog(@"%@", [^{
        NSLog(@"block----%d", age);
    } class]);

在这里插入图片描述

这个东西真的蛮抽象的,只有第一个打印了malloc类型,其他都是stack类型,这又是什么原因呢?

这个问题我也解释不太清楚,等后面学清楚了再来补充,敬请期待······

NSMallocBlock

    int a = 10;
    void (^block1)(void) = ^{
        NSLog(@"%d",a);
    };
    NSLog(@"block1:%@", [block1 copy]);

    __block int b = 10;
    void (^block2)(void) = ^{
        NSLog(@"%d",b);
    };
    NSLog(@"block2:%@", [block2 copy]);

输出结果:
在这里插入图片描述

block的父类

    void (^block1)(void) = ^{
        NSLog(@"block1");
    };
    NSLog(@"%@",[block1 class]);
    NSLog(@"%@",[[block1 class] superclass]);
    NSLog(@"%@",[[[block1 class] superclass] superclass]);
    NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);

在这里插入图片描述

block循环引用

如果在Block中使用附有__strong修饰符的对象类型自动变量,那么当Block 从栈复制到堆时,该对象为Block 所持有。这样容易引起循环引用。我们来看看下面的源代码:

typedef void (^blk_t)(void);
@interface MyObject : NSObject
{
	blk_t blk_;
}
@end

@implementation MyObject

- (id)init {
	self = [super init];
	blk_ = ^{
		NSLog(@"self = %@", self);
	};
	return self;
}
- (void)dealloc {
	NSLog(@"dealloc");
}
@end

int main() {
	id o = [[MyObject alloc] init];
	NSLog(@"%@", o);
	return 0;
}

该源代码中MyObject 类的 dealloc 实例方法一定没有被调用。
MyObject 类对象的 Block 类型成员变量blk_持有赋值为Block 的强引用。即 MyObject 类对象持有Block。init 实例方法中执行的 Block 语法使用附有__strong 修饰符的 id 类型变量 self。并且由于Block 语法赋值在了成员变量blk_中,因此通过Block 语法生成在栈上的 Block 此时由栈复制到堆,并持有所使用的self。self持有Block,Block 持有 self。这正是循环引用。
在这里插入图片描述
为避免此循环引用,可声明附有__weak 修饰符的变量,并将 self 赋值使用。

-(id)init {
	self = [super init];
	id __weak tmp = self;
	blk_ = ^{
		NSLog(@"self = %@",tmp);
	};
	return self;
}

在这里插入图片描述
在该源代码中,由于Block存在时,持有该Block 的 MyObject 类对象即赋值在变量tmp中的 self 必定存在,因此不需要判断变量 tmp 的值是否为nil。
另外,还可以使用__block变量来避免循环引用。

typedef void (^blk_t)(void);
@interface MyObject : NSObject {
	blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
	self = [super init];
	
	__block id tmp = self;
	blk_ = ^{
		NSLog(@"self = %@", tmp);
		tmp = nil;
	};
	return self;
}
- (void)execBlock {
	blk_();
}
-(void)dealloc {
	NSLog(@"dealloc");
}
@end

int main() {
	id o = [[MyObject alloc] init];
	[o execBlock];
	return 0;
}

该源代码没有引起循环引用。但是如果不调用execBlock实例方法,即不执行赋值给成员变量blk_的 Block,便会循环引用并引起内存泄漏。在生成并持有MyObject 类对象的状态下会引起以下循环引用。

  • MyObject 类对象持有Block
  • Block 持有__block变量
  • __block变量持有MyObject类对象
    在这里插入图片描述

如果不执行 execBlock 实例方法,就会持续该循环引用从而造成内存泄漏。
通过执行 execBlock实例方法,Block 被实行,nil 被赋值在__block变量tmp中。

 blk_ = ^{
	NSLog(@"self = %@", tmp);
	tmp = nil;
};

因此,__block 变量 tmp 对 MyObject 类对象的强引用失效。避免循环引用的过程如下所示:

  • MyObject类对象持有 Block
  • Block 持有__block变量
    在这里插入图片描述
    下面我们对使用block变量避免循环引用的方法和使用__weak修饰符及__unsafe_unretained 修饰符避免循环引用的方法做个比较。
    使用__block变量的优点如下:
  • 通过__block 变量可控制对象的持有期间
    在不能使用__weak修饰符的环境中使用__unsafe_unretained 修饰符即可(不必担心悬垂指针)
  • 在执行 Block 时可动态地决定是否将 nil 或其他对象赋值在__block 变量中。使用__block变量的缺点如下:
  • 为避免循环引用必须执行Block
    存在执行了 Block 语法,却不执行 Block 的路径时,无法避免循环引用。若由于 Block 引发了循环引用时,根据 Block 的用途选择使用__block变量、__weak 修饰符或__unsafe_unretained修饰符来避免循环引用。

关于block的知识很重要也很繁杂,包括书上一些源码和前面遇到的一些问题目前还没能太理解,后续会继续补充。

未完待续······

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React--》初识React框架及其基本使用

目录 React React的安装与使用 JSX语法及使用 模块与组件 React开发者工具的安装 面向组件编程 React React是一个用于构建用户界面的JavaScript库。用户界面:HTML页面(前端)。React主要用来写HTML页面,或构建Web应用。 如果从 MVC的角度来看,…

第一天总结之后端登录功能的实现

第一天总结之后端登录功能的实现 一、 前端页面 从图片 很明显知道 两个intput输入框 一个输入username 一个输入password 从前端的页面代码 可以找到form表单 根据form表单的action属性了解到 点击登录跳转到 controller 层的 LoginServlet 二、controller 层 创建一个 Log…

2023年跨境电商新趋势,新手小白还有出路吗?

跨境电商一直位于我国对外开放的最前沿,当下已经成为我国进出口贸易的关键组成部分之一,是外贸企业顺利开展进出口业务的重要保障,更是拥有庞大发展潜力以及活力的贸易新业态。在经济全球化趋势下,充分发挥出跨境电商的战略新通道…

Java 包的使用详解

文章目录1. 概念2. 导入包中的类2.1 使用类的全路径2.2 导入包2.3 静态导入包3. 自定义包4. 包的访问权限控制5. 常用的包Java编程基础教程系列1. 概念 在开发过程中,会定义很多的类,随着类的定义越来越多,难免会出现类名重复的情况&#xf…

mac 安装redis

文章目录mac 安装redis使用Homebrew安装Redis1.搜索redis版本2.使用Homebrew安装命令3.查看是否安装完成4.启动redis服务5.查看redis服务进程6.redis-cli连接redis服务7.检测 redis 服务是否启动8.修改密码mac 安装redis 使用Homebrew安装Redis 首先这里需要安装homebrew 1.搜…

【Kubernetes 企业项目实战】03、基于 Alertmanager 发送报警到多个接收方(上)

目录 一、配置 Alertmanager 发送报警到 qq 邮箱 1.1 设置 163 邮箱 1.2 创建 alertmanager 配置文件 1.3 创建 prometheus 告警规则配置文件 1.4 安装 prometheus 和 alertmanager 1.5 部署 alertmanager 的 service 1.6 浏览器访问 Prometheus 和 alertmanager 二、配…

ELK日志(2)

elasticsearch群集状态颜色:灰色:未连接绿色:数据完整态黄色:副本不完整红色:数据分片不完整紫色:数据分片复制过程群集主机角色:主节点master:负责管理调度工作节点: 负…

从IPv6的普及看中国未来网络的发展

最近看了一篇《邬贺铨:IPv6或是未来主流网络》的文章,谈到了未来网络的发展问题。IPv6也许是未来主流网络的发展方向。那么什么是IPv6呢,不妨来看下关于他的另一篇文章《邬贺铨:IPv6是IPv6规模部署第三阶段重要抓手》。 他谈到,IPv6是下一代互…

单绞机张力开环控制(绞臂行星差速机构算法)

PLC的开环和闭环张力控制算法,可以参看下面的文章链接: PLC张力控制(开环闭环算法分析)_plc张力控制程序_RXXW_Dor的博客-CSDN博客里工业控制张力控制无处不在,也衍生出很多张力控制专用控制器,磁粉制动器等,本篇博客主要讨论PLC的张力控制相关应用和算法,关于绕线机的…

动态内存管理(1)

TIPS 1. 2. malloc, free, calloc, realloc 这些的基本前提都是在内存堆区 内存堆区不能与内存栈区两者混淆乱套 动态内存管理存在的原因 1. 为什么要有动态内存管理?其实我们之前学过比如说对内存的管理,比方说我申请一块内存空间: 1.…

任意方向边界框——day64 读论文:基于自适应目标定位特征卷积神经网络的高分辨率遥感影像多面向目标检测

Multi-Oriented Object Detection in High-Resolution Remote Sensing Imagery Based on Convolutional Neural Networks with Adaptive Object Orientation Features 基于自适应目标定位特征卷积神经网络的高分辨率遥感影像多面向目标检测1. Introduction2. Materials and Met…

jQuery ajax中dataFilter的用法

参考资料 jquery的ajax的dataFilter参数的使用 ⏹用于处理 XMLHttpRequest 原始响应数据的函数 运行在success函数之前, 对Ajax请求返回的原始数据进行预处理 可以对返回的json数据中的null属性进行过滤可以对返回的json数据添加一些自定义的属性 如果不返回原始数据,返回其他…

零代码连接邮箱腾讯云企业网盘,附件管理超轻松

在日常工作中,想必大家每天都会收到各种各样的工作邮件,并且很多重要的文件材料也是通过邮件附件的形式来传输的,那么如何一站式管理这些文件,对于提高办公效率就至关重要了。关于邮件附件管理,相信大家也都碰到过这样…

全面了解文件上传漏洞, 通关upload-labs靶场

靶场简介 upload-labs是一个专门用于学习文件上传漏洞攻击和防御的靶场。它提供了一系列模拟文件上传漏洞的实验环境,用于帮助用户了解文件上传漏洞的原理和防御技术。 这个靶场包括了常见的文件上传漏洞类型,如文件名欺骗、文件类型欺骗、文件上传功能…

1582_C代码实现的快速、可移植MD5信息摘要算法

全部学习汇总: GreyZhang/c_units: A small piece of code which can be reuse anywhere, I call it a unit. This is a collection of unit in C language! Ok, yes, it would be my toolbox. (github.com) 工作之中,同事用到了MD5信息摘要算法&#x…

面试加分题--socket是否是并发安全的?

今天和大家聊一个有点儿东西的面试题:socket是否是并发安全的? 为了帮助大家理解,我们先假设一个场景。 就拿游戏架构来说,我们想象中的游戏架构是下面这样的。 想象中的游戏架构 也就是用户客户端直接连接游戏核心逻辑服务器&…

解决⾃动驾驶中计算机视觉的⽬标检测问题

来源:投稿 作者:cairuyi01 编辑:学姐 最近读了《Object detection with location-aware deformable convolution and backward attention filtering》,这是⼀篇2019年刊登在CVPR上的CV论⽂。与解决普适性的CV任务不同&#xff0c…

SpringMVC如何优化Ajax技术

SpringMVC如何优化Ajax技术? AJAX Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。 AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。 Ajax 不是一种新的编程语言,而是一种用于创…

EIoU和Focal-EIoU Loss

1、论文 论文题目:《Focal and Efficient IOU Loss for Accurate Bounding Box Regression》 2、引言 CIoU Loss虽然考虑了边界框回归的重叠面积、中心点距离、高宽比。但是其公式中的v反映的是高宽的差异,而不是高宽分别与其置信度的真实差异。因此&…

蚂蚁智能内容合规审核产品探秘

随着互联网服务的不断深化,产品营销的形式从传统文本、长图文,增加到短视频、直播等新媒介形态,展现形式愈加丰富的同时,也为营销宣传内容合规审核带来了诸多难题。如何解决与日俱增的审核量与合规审核人员有限之间的矛盾&#xf…