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 = 2

        NSLog(@"%@---%ld",objc, CFGetRetainCount((**__bridge** CFTypeRef)(objc)));

    };

    strongBlock();

  

    **void**(^**__weak** weakBlock)(**void**) = ^{ // + 1

        NSLog(@"%@---%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

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

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

相关文章

Stable Diffusion - Stable Diffusion WebUI 支持 SDXL 1.0 模型的环境配置

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/132056980 SDXL 1.0 版本 是 Stable Diffusion 的最新版本,是基于潜在扩散模型的文本到图像生成技术,能够根据输入的任何文…

机器学习知识经验分享之六:决策树

python语言用于深度学习较为广泛,R语言用于机器学习领域中的数据预测和数据处理算法较多,后续将更多分享机器学习数据预测相关知识的分享,有需要的朋友可持续关注,有疑问可以关注后私信留言。 目录 一、R语言介绍 二、R语言安装…

ansible的脚本——playbook剧本

目录 一、playbook的组成 二、 playbook安装httpd服务 1.编写playbook剧本 2.运行playbook 三、定义、引用变量 四、 指定远程主机sudo切换用户 五、when条件判断 六、迭代 七、Templates 模块 1.先准备一个以 .j2 为后缀的 template 模板文件,设置引用的变…

数据库:MYSQL参数max_allowed_packet 介绍

1、参数作用 max_allowed_packet参数是指mysql服务器端和客户端在一次传送数据包的过程当中最大允许的数据包大小。如果超过了设置的最大长度,则会数据库保持数据失败。 2、问题场景 ● 有时候业务的需要,可能会存在某些字段数据长度非常大(比如富文本编辑器里面的内容),…

docker 哨兵模式和集群模式安装Redis7.0.12

docker 哨兵模式和集群模式安装Redis7.0.12 1.下载镜像 1.1 配置阿里云加速源 墙外能访问https://hub.docker.com/_/redis 的可跳过 https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 登录后选择左侧的镜像工具>镜像加速器,获取加速器地址&#…

Vue3和typeScript路由传参

1 params传的参数,页面刷新就消失,而query传的参数,页面刷新还会存在,所以通常用query。 query传参 跳转页面:拿到router对象,调用push方法做跳转. import { useRoute,useRouter} from "vue-router"; export default…

GPIO简介

一、GPIO GPIO(General-purpose input/output)即通用型输入输出,GPIO可以控制连接在其之上的引脚实现信号的输入和输出 芯片的引脚与外部设备相连,从而实现与外部硬件设备的通讯、控制及信号采集等功能 LED实验步骤 实验步骤 以L…

图文演示:如何三分钟极速搭建一个元宇宙3D虚拟展厅

引言: 元宇宙3D虚拟展厅时代已经来临。元宇宙是一个虚拟的、立体的数字空间,可以让用户沉浸在其中进行交互操作,并体验无限可能。如何快速搭建一个属于自己的虚拟展厅则受到越来越多人的关注。 一.虚拟展厅类型 1.党建展馆 实现…

小研究 - 微服务系统服务依赖发现技术综述(一)

微服务架构得到了广泛的部署与应用, 提升了软件系统开发的效率, 降低了系统更新与维护的成本, 提高了系统的可扩展性. 但微服务变更频繁、异构融合等特点使得微服务故障频发、其故障传播快且影响大, 同时微服务间复杂的调用依赖关系或逻辑依赖关系又使得其故障难以被及时、准确…

mysql月统计数据,没有的填充为0

要按时间戳字段按月份分组查询记录表,可以使用DATE_FORMAT函数将时间戳字段格式化为年月格式,然后将结果按照该字段进行分组。 SELECT a.month month,ifnull(b.count, 0) count FROM (SELECT 1 month UNION ALL SELECT 2 month UNION ALL SELECT 3 mont…

Vue实现leafletMap自定义绘制线段 并且删除指定的已绘制的点位

效果&#xff1a;点击表格可实现选中地图点位&#xff0c;删除按钮点击可删除对应点位并且重新绘制线段&#xff0c;点击确定按钮 保存已经绘制的点位信息传给父组件 并且该组件已实现回显 完整的组件代码如下 文件名称为&#xff1a; leafletMakePointYt <!--* Descripti…

小红书APP出现闪退问题,电商erp系统接口测试怎么做?(一)

7月27日凌晨&#xff0c;部分网友反馈小红书APP出现闪退问题。对此&#xff0c;小红书客服微博发文称&#xff0c;如遇到小红书APP无法启动的情况&#xff0c;用户可前往AppStore下载最新版本。 大家在讨论崩溃原因时&#xff0c;提到大概是接口出现了问题&#xff0c;对于软件…

05-向量的意义_n维欧式空间

线性代数 什么是向量&#xff1f;究竟为什么引入向量&#xff1f; 为什么线性代数这么重要&#xff1f;从研究一个数拓展到研究一组数 一组数的基本表示方法——向量&#xff08;Vector&#xff09; 向量是线性代数研究的基本元素 e.g. 一个数&#xff1a; 666&#xff0c;…

企业大数据可视化案例专题分享-入门

一、什么是数据可视化&#xff1f; 基本概念&#xff1a;数据可视化是以图示或图形格式表示的数据。让决策者可以看到以直观方式呈现的分析&#xff0c;以便他们可以掌握困难的概念或识别新的模式。借助交互式可视化&#xff0c;可以使用技术深入挖掘图表和图形以获取更多详细…

Pytorch基础

文章目录 一、Pytorch简介二、安装2.1 安装GPU环境2.2 安装Pytorch2.3 测试 三、Tensor3.1 Tensor创建3.1.1 torch.tensor() && torch.tensor([])3.1.2 torch.randn && torch.randperm3.1.3 torch.range(begin,end,step)3.1.4 指定numpy 3.2 Tensor运算3.2.1 A…

开源Blazor UI组件库精选:让你的Blazor项目焕然一新!

今天给大家推荐一些开源、美观的Blazor UI组件库&#xff0c;这些优秀的开源框架和项目不仅能够帮助开发者们提高开发效率&#xff0c;还能够为他们的项目带来更加丰富的用户体验。 注&#xff1a;排名不分先后&#xff0c;都是十分优秀的开源框架和项目 ​Ant Design Blazor…

深度学习(34)—— StarGAN(1)

深度学习&#xff08;34&#xff09;—— StarGAN&#xff08;1&#xff09; 文章目录 深度学习&#xff08;34&#xff09;—— StarGAN&#xff08;1&#xff09;1. 背景2. 基本思路3. 整体流程4. StarGAN v2(1) 网络结构(2) mapping network(3) style encoder(4)Loss 和之前…

【机器学习】西瓜书习题3.5Python编程实现线性判别分析,并给出西瓜数据集 3.0α上的结果

参考代码 结合自己的理解&#xff0c;添加注释。 代码 导入相关的库 import numpy as np import pandas as pd import matplotlib from matplotlib import pyplot as plt导入数据&#xff0c;进行数据处理和特征工程 得到数据集 D { ( x i , y i ) } i 1 m , y i ∈ { 0 ,…

安装企业级高负载web服务器tomcat,并部署应用

web服务器Tocamt 1.Tocmat简介2.Tocmat安装1.安装jdk2.部署Tomcat1.配置环境变量2.启动tocmat3.Tomcat web管理功能 3.部署jpress应用 1.Tocmat简介 Tomcat是Apache软件基金会&#xff08;Apache Software Foundation&#xff09;的Jakarta 项目中的一个核心项目&#xff0c;由…

C. Binary String Copying - 思维

分析&#xff1a; 赛时我是直接模拟的&#xff0c;tle然后mle&#xff0c;补提&#xff0c;发现规律&#xff0c;每一个改变的字符串都只会对应一个需要改变的区间&#xff0c;例如第一个样例前两个101100 -> 011100和101100 -> 011100&#xff0c;对应区间在确定改变的范…