iOS——Block one

块类似于匿名函数或闭包,在许多其他编程语言中也存在类似的概念。
可以访问上下文,运行效率高

Block

以下是块的一些基本知识:

  1. 块的定义:块是由一对花括号 {} 包围的代码片段,可以包含一段可执行的代码。块的定义使用 ^ 符号,并可以带有参数列表和返回类型。例如:
^{// 代码块的内容
}
  1. 块的类型:块也是一种数据类型,与函数类似。它们可以具有参数和返回值类型。可以使用 typedef 来定义块的类型。例如:
typedef returnType (^BlockTypeName)(parameterTypes);

其中 returnType 是块的返回类型,BlockTypeName 是块的类型名称,parameterTypes 是块的参数类型。
3. 块的赋值和调用:块可以赋值给变量,并且可以像函数一样进行调用。可以使用 = 运算符将块赋值给变量,然后使用该变量调用块。例如:

ReturnType (^blockName)(ParameterTypes) = ^ReturnType (Parameters) {// 块的内容
};
blockName(argumentValues); // 调用块
  1. 块的捕获变量:块可以捕获其定义范围内的变量,并在块内部访问这些变量。捕获的变量在块中形成了一个闭包,可以在块的[[生命周期]]内保持其状态。例如:
NSInteger outsideVariable = 10;
void (^block)(void) = ^{NSLog(@"Outside variable: %ld", (long)outsideVariable);
};
block(); // 输出:Outside variable: 10

在这个例子中,块捕获了外部的 outsideVariable 变量,并在块内部访问它。
默认情况下,为块所捕获的变量是不可以在块里面修改的,如果修改了outsideVariale的值,就会报错,声明变量的时候可以加上__block修饰符,这样就可以在块内修改了
外部变量如果是数组那种的话是可以使用方法来给数组添加内容的,因为此举没有改变对象原本的地址
Pasted image 20230725110306.png

  1. 块作为参数:块可以作为方法或函数的参数进行传递,从而实现回调和异步操作等功能。可以将块作为参数声明,并在调用方法或函数时传递块。例如:
- (void)performOperationWithCompletion:(void (^)(void))completionBlock {// 执行操作// 操作完成后调用块completionBlock();
}// 调用方法,传递块作为参数
[self performOperationWithCompletion:^{NSLog(@"Operation completed!");
}];

在这个例子中,performOperationWithCompletion: 方法接受一个块作为参数,并在操作完成后调用该块。

#pragma mark **--修改为块所捕获的变量**NSArray *array = @[@0, @1, @2, @3, @4, @5];**__block** NSInteger count = 0;//块作为方法的参数[array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, **BOOL** *stop) {**if** ([number compare:@2] == NSOrderedAscending) {count++;}NSLog(@"%ld====%@",(**unsigned** **long**)idx, number);}];NSLog(@"%ld", (**long**)count);//内联块的用法,传给“numberateObjectsUsingBlock:"方法的块并未先赋给局部变量,而是直接在内联函数中调用了,如果块所捕获的变量类型是对象类型的话,那么就会自动保留它,系统在释放这个块的时候,也会将其一并释放。这就引出了一个与块有关的重要问题,块本身可以视为对象,在其他oc对象能响应的选择子中,很多块也可以响应,最重要的是,块本身也会像其他对象一样,有引用计数,为0时,块就回收了,同时也会释放块所捕获的变量,以便平衡捕获时所执行的保留操作//如果块定义在oc类的实例方法中,那么除了可以访问类的所有实例变量之外,还可以使用self变量,块总能修改实例变量,那么除了声明时无需添加__block。不过,如果通过读取或写入操作捕获了实例变量,那么也会自动把self给捕获了,因为实例变量是与self所指代的实例关联在一起的。// 需要注意的是(self也是一个对象,也会被保留),如果在块内部使用了实例变量,块会自动对self进行保留操作,以确保在块执行期间保持对象的有效性。但是,如果在块内部直接使用了self,并对其进行读取或写入操作,那么self也会被捕获,从而导致循环引用的问题。为了避免循环引用,可以在块内部使用__weak修饰符来避免对self进行保留操作。

如果某个实例在执行anInstanceMethod放法,那么self变量就会指向此实例。由于块里没有明确使用self变量,所以很容易就会忘记self变量其实也为块所捕获了。直接访问实例遍历和通过self来访问时等效的:
self->_anInstanceVariable = @“someThing”;

typedef void(^SomeBlock) (void);
@property (nonatomic, copy) BlockName someBlock;
`- (void)anInstanceMethod {self.someBlock = ^ {_anInstanceVariable = @"someThing";}}

self也是个对象,因而块在捕获它时也会将其保留。如果self所指代的那个对象同时也保留了块,那么这种情况就会导致“保留环”
修改为以下代码即可:

typedef void(^SomeBlock) (void);
@property (nonatomic, copy) BlockName someBlock;
__weak typeof(self)weakSelf = self;
`- (void)anInstanceMethod {self.someBlock = ^ {weakSelf.anInstanceVariable = @"someThing";}}

块的本质

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

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
  • block是封装函数及其上下文的OC对象
  • !Pasted image 20230726144818.png

block变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
Pasted image 20230725094004.png

block变量与c语言变量完全相同,可以作为以下用途:

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

BLOCK的三种类型

block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

__ NSGlobalBlock __ ( _ NSConcreteGlobalBlock ) 对象存储在数据区
__ NSStackBlock __ ( _ NSConcreteStackBlock ) 对象存储在栈区
__ NSMallocBlock __ ( _ NSConcreteMallocBlock )对象存储在堆区

Pasted image 20230726151722.png

捕获了自动变量block就是栈类型
没有捕获就是数据区
不存在一创建就在堆区的,堆区的意义可以理解为和autorelease一样:延长作用域 Stack类型的Block进行了copy操作之后变成了堆区

  • 堆:动态分配内存,需要程序员自己申请,程序员自己管理
  • 栈:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况

NSGlobalBlock&NSStackBlock&NSMallocBlock

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

- (**void**)NSGlobalBlock {//block1没有引用到局部变量**int** a = 10;**void** (^block)(**void**) = ^{NSLog(@"hello world");};NSLog(@"block:%@", block);//    block2中引入的是静态变量**static** **int** a1 = 20;**void** (^block1)(**void**) = ^{NSLog(@"hello - %d",a1);};NSLog(@"block:%@", block);}- (**void**)NSStackBlock {**__block** **int** a = 10;**static** **int** a1 = 20;**void** (^**__weak** block)(**void**) = ^{NSLog(@"hello - %d",a);NSLog(@"hello - %d",a1);};NSLog(@"block:%@", block);}- (**void**)NSMallocBlock {**int** a = 10;**void** (^block1)(**void**) = ^{NSLog(@"%d",a);};NSLog(@"block1:%@", block1);**__block** **int** b = 10;**void** (^block2)(**void**) = ^{NSLog(@"%d",b);};NSLog(@"block2:%@", block2);}//block继承与nsobject- (**void**)blockFromNSObject {**void** (^block1)(**void**) = ^{NSLog(@"block1");};NSLog(@"%@",[block1 class]);NSLog(@"%@",[[block1 class] superclass]);NSLog(@"%@",[[[block1 class] superclass] superclass]);NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);NSLog(@"%@",[[[[[block1 class] superclass] superclass] superclass] superclass]);}

Pasted image 20230725105743.png

  • 上述代码输出了block1的类型,也证实了block是对象,最终继承NSObject

栈区block和堆区block的区别

- (**void**)diffStackAndHip {**__block** **int** a = 10;**__block** **int** b = 20;NSLog(@"a:%p---b:%p", &a, &b);**void** (^**__weak** block)(**void**) = ^{NSLog(@"hello - %d---%p",a, &a);a++;};**void** (^block1)(**void**) = ^{NSLog(@"hello - %d---%p",b, &b);b++;};block();block1();NSLog(@"block:%@---block1:%@", block, block1);NSLog(@"a:%d---b:%d", a, b);NSLog(@"a:%p---b:%p", &a, &b);
}

Pasted image 20230725110848.png

  • 通过结果我们看到,首先block的地址是在栈区,而block1的地址是在堆区,而栈block引用的变量a的地址并没有变化,而堆block1引用的变量b的地址也相应变成了堆区`0x6,并且后面使用的b的地址都是堆区上的。
    #栈block存放在栈区,对局部变量引用只拷贝局部变量的地址,而堆block存放在堆区,并且直接将局部变量拷贝了一份到堆空间。
- (**void**)diffStackAndHip2 {NSObject *objc = [NSObject new];NSLog(@"%@---%ld",objc, CFGetRetainCount((**__bridge** CFTypeRef)(objc)));// 1// block 底层源码// 捕获 + 1// 堆区block// 栈 - 内存 -> 堆  + 1**void**(^strongBlock)(**void**) = ^{ // 1 - block -> objc 捕获 + 1 = 2NSLog(@"%@---%ld",objc, CFGetRetainCount((**__bridge** CFTypeRef)(objc)));};strongBlock();**void**(^**__weak** weakBlock)(**void**) = ^{ // + 1NSLog(@"%@---%ld",objc, CFGetRetainCount((**__bridge** CFTypeRef)(objc)));};weakBlock();**void**(^mallocBlock)(**void**) = [weakBlock copy];mallocBlock();}

Pasted image 20230725111249.png

奇怪为什么堆区block里面的对象引用计数加2呢?而后面的mallocBlock只加1呢?
首先objc在strongBlock里面必然会拷贝一份到堆区,所以会加1,但是他是从当前函数的栈区拷贝吗?并不是,对于堆区Block一开始编译时是栈block这时候objc对象地址拷贝了一份引用计数加1,后面从栈block变成堆block,又拷贝了一份引用计数又加1,所以这时候是3,weakBlock是栈block仅拷贝了一份,所以引用计数加1,这时候是4,mallocBlock从weakblock拷贝了一份,所以引用计数再加1,这时候是5,相当于strongBlock = weakblock + void(^mallocBlock)(void) = [weakBlock copy];

- (**void**)diffStackAndHip3 {NSObject *a = [NSObject alloc];NSLog(@"1---%@--%p", a, &a);**void**(^**__weak** weakBlock)(**void**) = **nil**;{// 栈区**void**(^**__weak** strongBlock)(**void**) = ^{NSLog(@"2---%@--%p", a, &a);};weakBlock = strongBlock;strongBlock();NSLog(@"3 - %@ - %@",weakBlock,strongBlock);}weakBlock();NSLog(@"4---%@--%p", a, &a);}

Pasted image 20230725114806.png

  • 当前是栈区strongBlock的赋值给外面的栈区weakBlock因为都是存放在栈空间的,只有当前函数结束才会被销毁,随意这边weakBlock调用并不会有什么问题。如果换成堆区block就不一样了。
  • 这边的a对象在weakBlock()调用时是nil,通过上面打印可以看出a对象在进入到strongblock里,&a拷贝了一份,拷贝的这一份地址指向的跟外面一样,但是当strongblock出了{}刚才复制的对象就要销毁了,(栈区的对象在函数结束的时候就会被销毁,函数本身会在作用域结束的时候被销毁)尽管strongblock对象不再了,但是其指向的内存空间还在,销毁之前给了外面的weakBlock,同理a也一样,对象(此时a指向的内容)不在了,但是内存空间却还在
    #这边建议打断点看一看运行流程,方便理解
- (**void**)diffStackAndHip4 {NSObject *a = [NSObject alloc];NSLog(@"1---%@--%p", a, &a);**void**(^**__weak** weakBlock)(**void**) = **nil**;{// 栈区**void**(^**__strong** strongBlock)(**void**) = ^{NSLog(@"2---%@--%p", a, &a);};weakBlock = strongBlock;strongBlock();NSLog(@"3 - %@ - %@",weakBlock,strongBlock);}//  weakBlock();//测试的时候取消注释NSLog(@"4---%@--%p", a, &a);}

Pasted image 20230725115803.png

  • 为什么呢?因为在{}里面的堆区strongBlock出了大括号就会被销毁,此时你去调用这个block就会崩溃
  • 注意:这边weakBlock为什么也是__NSMallocBlock__,其实weakBlock相当于是指针,此时指向的是一个堆上的内存所以是__NSMallocBlock__
    Pasted image 20230725120024.png

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

银河麒麟v10 vnc环境配置

方法一、启用自带远程桌面 银河麒麟默认已经自带远程桌面,如下图。此时即可用Realvnc Viewer访问该终端,仔细查看后自带的远程桌面是开源组件gnome-remote-desktopGNOME / gnome-remote-desktop GitLabhttps://gitlab.gnome.org/GNOME/gnome-remote-de…

13个ChatGPT类实用AI工具汇总

在ChatGPT爆火后,各种工具如同雨后春笋一般层出不穷。以下汇总了13种ChatGPT类实用工具,可以帮助学习、教学和科研。 01 / ChatGPT for google/ 一个浏览器插件,可搭配现有的搜索引擎来使用 最大化搜索效率,对搜索体验的提升相…

Kindling the Darkness: A Practical Low-light Image Enhancer论文阅读笔记

这是ACMMM2019的一篇有监督暗图增强的论文,KinD其网络结构如下图所示: 首先是一个分解网络分解出R和L分量,然后有Restoration-Net和Adjustment-Net分别去对R分量和L分量进一步处理,最终将处理好的R分量和L分量融合回去。这倒是很常…

【机器学习】Gradient Descent for Logistic Regression

Gradient Descent for Logistic Regression 1. 数据集(多变量)2. 逻辑梯度下降3. 梯度下降的实现及代码描述3.1 计算梯度3.2 梯度下降 4. 数据集(单变量)附录 导入所需的库 import copy, math import numpy as np %matplotlib wi…

备战秋招 | 笔试强训19

目录 一、选择题 二、编程题 三、选择题题解 四、编程题题解 一、选择题 1、二分查找的时间复杂度() A. O(N*log(N)) B. O(N) C. O(log(N)) D. O(N^2) 2、有一个单向链表中有一个A、B两个相邻元素,有一个指针p指向元素A,现将…

谷歌云 | 电子商务 | 如何更好地管理客户身份以支持最佳的用户体验

【本文由Cloud Ace整理发布。Cloud Ace是谷歌云全球战略合作伙伴,拥有 300 多名工程师,也是谷歌最高级别合作伙伴,多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商,我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证培训…

台式机/工控机通过网线共享笔记本电脑无线网络linux系统下 usb网卡的驱动安装

一、台式机/工控机通过网线共享笔记本电脑无线网络 1、 将台式机通过网线和笔记本连接。 2、 将笔记本的“本地连接”和“无线网络连接”的ipv4均设置为自动获取。 4.修改台式机的IP地址为如下(对应笔记本信息) IP地址为192.168.XXX.12 子网掩码为255.2…

弘扬“两弹一星”精神,勇攀科学技术高峰——道本科技商业大学党日活动圆满落幕

2023年8月2日,道本科技与商业大学携手举办了一场主题为“弘扬‘两弹一星’精神,勇攀科学技术高峰”的党日活动。本次活动旨在了解党领导下的中国核工业发展历程,传承和弘扬“两弹一星”精神,同时展示道本科技创新产品,…

【Linux】在服务器上创建Crontab(定时任务),自动执行shell脚本

业务场景:该文即为上次编写shell脚本的姊妹篇,在上文基础上,将可执行的脚本通过linux的定时任务自动执行,节省人力物力,话不多说,开始操作! 一、打开我们的服务器连接工具 连上服务器后,在任意位置都可以执行:crontab -e 如果没有进入编辑cron任务模式 根据提示查看…

如何使用vue ui创建一个项目?

首先打开cmd 输入vue ui 等待浏览器打开一个窗口,按照下图操作 在"功能页面"中,各个插件代表以下意思: Babel:Babel是一个JavaScript编译器,用于将ES6代码转换为向后兼容的JavaScript版本,以确保…

【计算机网络】数据链路层

文章目录 1. 数据链路层1.1 数据链路层简介1.2 数据链路层做了什么 2. 以太网协议2.1 以太网2.2 以太网帧的格式2.3 MAC地址2.4 MTU 3. 数据跨网络传输的整体过程4. ARP协议4.1 认识ARP协议4.2 ARP协议的格式4.3 ARP协议的工作流程 1. 数据链路层 1.1 数据链路层简介 数据链路…

ELK 企业级日志分析系统

ELK 企业级日志分析系统 一、ELK 概述1.ELK 简介2.日志分析系统 二、为什么要使用 ELK1.原因:2.完整日志系统基本特征3.ELK 的工作原理 三、部署ELK1.ELK Elasticsearch 集群部署(在Node1、Node2节点上操作)2.部署 Elasticsearch 软件&#x…

自然语言处理学习笔记(二)————语料库与开源工具

目录 1.语料库 2.语料库建设 (1)规范制定 (2)人员培训 (3)人工标注 3.中文处理中的常见语料库 (1)中文分词语料库 (2)词性标注语料库 (3…

刷题笔记 day7

力扣 209 长度最小的子数组 解法:滑动指针(对同向双指针区间内的数据处理) 1)先初始化 两个指针 left ,right。 2)右移指针right的同时使用sum记录指针right处的值,并判断sum的值是否满足要求&…

linux下性能分析工具Perf安装与用法

目录 1、Perf介绍 2、火焰图分类 (1)CPU (2)Memory Flame Graphs (3)Off-CPU Flame Graphs (4)Hot/Cold Flame Graphs (5)Differential 3、火焰图安装命令 …

Ubuntu安装harbor(http模式)并随便上传一个

Ubuntu安装harbor(http模式) docker和harbor的介绍就免了,都不知道啥东西,还安装搞毛 先安装docker环境 不要问,软件源之类的配置,挨个梭就行 sudo apt update sudo apt install apt-transport-https ca…

uniapp 路由跳转方式

export function goBack(index, url) {if (index 1) { // 关闭当前页,返回上一页面或多级页面。uni.navigateBack({delta: url,animationType: pop-out,animationDuration: 300});} else if (index 2) { // 保留当前页,跳转到非tabbar页面,…

不规则文件转JSON

需求分析: 有时候,我们取出来的数据并不是一个规则的JSON文件,这个时候面对存库还是ES检索都是一个问题,所以我们就需要进行解析,然而用字符串分割是不现实的,我们需要一种快速的方法。 问题解决&#x…

C++ ------ 类和对象的深究

文章目录 构造函数初始化列表概念特性 explicit关键字 static成员概念特点 友元友元函数友元类概念特性 内部类概念特点 匿名对象拷贝对象时的一些编译器优化 构造函数 我们来看下面的代码&#xff1a; #include <iostream> using namespace std;class Date { public:D…

MyBatis-XML映射文件

XML映射文件 规范 XML映射文件的名称与Mapper接口名称一致&#xff08;EmpMapper对应EmpMpper.xml&#xff09;&#xff0c;并且将XML映射文件和Mapper接口放置在相同包下&#xff08;同包同名&#xff09; ​​​ 在maven项目结构中所有的配置文件都在resources目录之下&…