【Objective -- C】—— block

【Objective -- C】—— block

  • Block简介
  • 语法
    • 初始化和声明
  • Block类型变量
    • typedef
    • 截获自动变量
    • __block说明符
    • 截获的自动变量
  • block的实现
    • Block的存储域
      • NSGlobalBlock
      • NSStackBlock
      • NSMallocBlock
    • 深入理解Block的存储域
      • Blocks如何实现复制到堆上
    • __block变量的存储域
    • 截获对象
      • 调用时机
  • __block变量和对象
    • __block
    • __weak

Block简介

block是一个带有自动变量值的匿名函数,它也是一个数据类型,跟int double float一样都是数据类型.所以我们是可以创建一个block类型的变量。

总结来说:block类似于一个方法。而每一个方法都是在被调用的时候从硬盘到内存,然后去执行,执行完就消失,所以,方法的内存不需要我们管理,也就是说,方法是在内存的栈区。 所以,block不像OC中的类对象(在堆区),他也是在栈区的。如果我们使用block作为一个对象的属性,我们会使用关键字copy修饰他,因为他在栈区,我们没办法控制他的消亡,当我们用copy修饰的时候,系统会把该 block的实现拷贝一份到堆区,这样我们对应的属性,就拥有的该block的所有权。就可以保证block代码块不会提前消亡。

语法

初始化和声明

在这里插入图片描述

 返回值类型 (^block变量的名称)(参数列表)
void (^block1)()//无返回值,无参数
int (^block2)(int num1,int num2)//int类型返回值i,两个int类型参数

Block类型变量

Block类型变量与一般C语言函数变量完全相同,可作为以下用于使用:

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

使用Block语法,将Block赋值为Block变量。

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

因为与通常的变量相同,所以当然也可以有Block类型变量赋值给Block类型变量。

int (^bilk1)(int) = blk;
- (void)blk3 {int (^blk)(int) = ^(int count){return count + 1;};int (^blk1)(int) = blk;int (^blk2)(int);blk2 = blk1;}

typedef

函数参数中使用Block类型变量可以向函数传递Block。但是在参数和函数返回值中使用Block类型变量极为复杂。这时,我们可以使用typedef来解决该问题。

typedef int (^blk_t)(int);

截获自动变量

blk的带有自动变量的匿名函数,什么是带有自动变量?
带有自动变量值在Block中表现为“截取自动变量值 ”。

NSString *c = @"ff";
NSString* (^addBlock)(NSString *a, NSString *b) = ^(NSString *a, NSString *b) {return [NSString stringWithFormat:@"%@%@%@", a, b, c];
};

__block说明符

自动变量值截获只能保存执行Block语法瞬间的值。
以下面的代码为例:

{int val = 10;const char* fmt = "val = %d\n";//自动变量值截获只能保存执行Block语法瞬间的变量的值。//执行瞬间fmt = "val = %d\n",val = 10;void (^blk)(void) = ^ {printf(fmt, val);};val = 2;fmt = "These values were changed. val = %d\n";blk();return 0;}

输出的结果为:
在这里插入图片描述
不允许块属性,仅允许在局部变量上 __block attribute not allowed, only allowed on local variables。
保存后就不能改写该值。若想在Block语法的表达式中将值赋给Block语法外声明的自动变量,需要在该自动变量上附加 __block说明符 。我们称这种变量为__block变量。

截获的自动变量

对于块来说截获Objective-C对象,调用变更该对象的方法不会产生编译错误。

- (void)blk4 {id tstArray = @[@"123", @"234", @"345"];id array = [[NSMutableArray alloc] init];void (^blk)(void) = ^{id obj = [[NSObject alloc] init];[array addObject:obj];};}

而向截获的变量array赋值则会产生编译错误。

- (void)blk4 {id tstArray = @[@"123", @"234", @"345"];id array = [[NSMutableArray alloc] init];void (^blk)(void) = ^{id obj = [[NSObject alloc] init];array = tstArray;};}

block的实现

Block的存储域

Block 和 ——block变量的实质
在这里插入图片描述
block既然是OC的对象,那么也存在许多的类似的类。
在这里插入图片描述
三种类对应了三种不同的区域:
在这里插入图片描述

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);

运行结果:
在这里插入图片描述
在Block内部不使用外部变量(不使用应截获的自动变量),或者只使用静态变量和全局变量。

NSStackBlock

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

运行结果:
在这里插入图片描述
可以发现,这边显示的不是NSStackBlock,原因是:在ARC环境下,系统会自动将block进行拷贝操作。只要改成MRC就行了。
与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者Copy修饰的变量

NSMallocBlock

什么时候栈上的 Block 会复制到堆呢?

  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block 赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或 Grand Central Dispatch的 API 中传递 Block 时
    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]);

运行结果:
在这里插入图片描述
那么三种储存方式有什么区别呢?
简单来说,没有捕获自动变量的就是数据区,捕获了自动变量但是没有进行copy操作就是栈区,copy之后就变成了堆区。

深入理解Block的存储域

为什么Block超出变量作用区域可以存在?
配置在全局变量上的 Block,从变量作用域外也可以通过指针安全地使用。 但放置在栈上的Block,如果其所属的变量作用城结束,该Blook 就被废弃由于__block 交量也配置在栈上,同样地,如果其所属的变量作用域结束,则该_block 变量也会被废弃。
在这里插入图片描述
Blocks提供了把Block和__block复制到堆上的方法,防止block被废弃
在这里插入图片描述
复制到堆上的block将NSMallocBlock类对象写入Block用结构体实例的成员变量指针。

Blocks如何实现复制到堆上

对于OC来说,当ARC处于有效情况的时候,大多数情况下编译器会自己进行判断,自动生成把block从栈复制到堆上的代码。
以返回block的函数为例:

blk_t func(int rate) {return ^(int count) {return rate * count;};
};
- (void)testBlk {func(4);NSLog(@"%@", func(4));
}

在这里插入图片描述
该代码本应该是返回配置在栈上的block函数当程序结束的时候block应该被废弃,但实际打印出来的结果是mallocblock。
上面的代码翻译成源码为:
在这里插入图片描述
在ARC处于有效状态下,blk_t tmp 和blk_t strong tmp是一样的效果。
源码的 objc_retainBlock(tmp)其实就是Block_copy函数。

tmp = Block_copy(tmp)
return objc_autoReleaseReturnValue(tmp)

翻译源码的注释看实现栈到堆的过程发生了什么?
在这里插入图片描述
也就当Block作为函数的返回值返回的时候,编译器会自动生成复制到堆上的代码。

大多数情况下编译器会自主的进行判断拷贝,但是如下情况需要手动copy:

  • 向方法或者函数里传递Block时。
    但是在方法或者函数里面适当的复制了传递的参数时,那么就不需要手动复制。
  • GCD的API不需要。
  • cocoa框架方法且方法名包含usingBlock。

对于Block语法和Block类型变量都可以直接调用copy方法。

blkCopy = [blk copy];

对于配置在栈上的Block我们知道调用copy方法会讲Block从栈复制到堆上,那么存储在数据域和堆上Block调用copy会发生如下情况:
在这里插入图片描述
在不确定的情况下调用copy方法不会出现问题,即使是引用计数也有ARC帮我们自动管理。多次调用copy也不会出现问题。

 int (^blk)(int) = ^(int count){return count + 1;};blk = [[[[blk copy] copy] copy] copy];

多次的copy就是一个持有,废弃,持有,废弃。。。的重复过程,对于ARC我们有自动管理,所以不会出现问题。
在这里插入图片描述

__block变量的存储域

__block变量的存储域和引用计数的模式类似,因为__block对象是在Block里被引用或者调用的,当Block被从栈复制到堆时,__block也有一样的变化。
在这里插入图片描述
在一个Block使用__block对象的时候,若Block从栈复制到堆,__block对象也从栈复制到堆,此时Block持有__block。
在这里插入图片描述
多个Block使用__block对象的时候,当一个Block从栈复制到堆,此时和一个Block情况一样,当其余Block从栈复制到堆的时候,Block会持有__block对象,此时__block对象引用计数加1;
在这里插入图片描述
当Block被废弃,那么它持有的__block对象也会被释放,这个是一一对应的关系,__block的思考方式和OC的引用计数内存管理方式完全相同。
在这里插入图片描述
通过Block的复制,可以同时访问堆和栈的__block变量。因为此时__block也从栈复制到堆,这与__block结构体使forward ing指针变量有关。

当栈上的__block被复制到堆上的时候,栈上的__block变量用结构体实例在__block变量从栈复制到堆上的时候,_forwarding成员变量的值从指向栈上的自身替换为目标堆上__block变量用结构体实例的的地址。
在这里插入图片描述

截获对象

在 ARC 中,捕获了外部变量的 block 的类会是 NSMallocBlock 或者 NSStackBlock,如果 block 被赋值给了某个变量,在这个过程中会执行 __Block__copy 将原有的 NSStakeBlock 变成 NSMallocBlock ;但是如果 block没有赋值给某个变量,那他的类型就是 NSStakeBlock ;没有捕获外部变量的 block 的类会是 NSGlobalBlock 既不在堆上,也不在栈上,它类似 C 语言函数一样会在代码段中。

在非 ARC 中,捕获了外部变量的 block 的类会是 NSStackBlock ,放在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同。

书上展示了如下的代码:

typedef void (^blk_t1) (id);
//  截获对象
- (void)blkObj {id array = [[NSMutableArray alloc] init];blk_t1 blk;blk = [^(id obj) {[array addObject:obj];NSLog(@"array count = %ld", [array count]);} copy];blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);NSLog(@"%@", blk);
}

代码首先生成并持有NSMutableArray对象,由于附有__strong目标变量的作用域立即结束,那么该对象应该被废弃,其强引用失效并释放,但是代码打印结果如图:
在这里插入图片描述
结果表示可变数组的类对象在Block的执行过程超出了变量的作用域而存在。
在这里插入图片描述
源码理解:
被赋值的自动变量array对象发现其在Block结构体中是带有__strong关键字的成员变量, 书上的解释是OC库能够很准确的把握从栈复制到堆,以及堆上被废弃的Block 因此即使结构体的成员变量含有__strong或者__weak关键字修饰的变量,编译器也可以适当的初始化和废弃。

Block源码里出现了 _main_block_copy_0_main_block_depose_0 两个关键函数。

_main_block_copy_0

因为带有_-strong修饰符,所以 _main_block_copy_0函数调用 _Block_object_assgin函数来持有该array对象
在这里插入图片描述
_Block_object_assgin函数 相当于retain实例方法,将对象赋值在对象类型的结构体成员变量当中。

_main_block_depose_0

_main_block_depose_0函数调用 _Block_object_dispose,释放赋值在Block用结构体成员变量array中的对象。
在这里插入图片描述
_Block_object_dispose函数 相当于release实例方法,释放在对象类型的结构体成员变量中的对象。

调用时机

在这里插入图片描述
Block复制到堆上的情况:

  • 调用 Block 的 copy 实例方法时。
  • Block 作为函数返回值返回时。
  • 将 Block 赋值给附有strong 修饰符 id 类型的类或 Block 类型成员变量时。
  • 在方法名中含有usingBlock 的 Cocoa 框架方法或 Grand Central Dispatch 的 API 中传递
    Block 时。

其表面看来是从栈栈复制到堆,本质是当调用到了_Block_copy函数的时候从栈复制到堆
相对的就是释放的时候,对于谁都不持有的对象会调用_Block_object_dispose函数,相当于dealloc方法,所以解释了上述截获对象array为什么可以超出其变量作用域而存在。

__block变量和对象

__block说明符可以指定任何类型的自动变量。

__block id obj = [[NSObject alloc] init];

该代码等同于:

__block id __strong obj1 = [[NSObject alloc] init];

ARC有效的时候 id类型以及对象类型变量必定附加所有权修饰符。

__block

该代码通过clang翻译源码如下:
在这里插入图片描述
上述出现了 _Block_object_assgin函数和 _Block_object_dispose函数。

**当在Block使用附有strong修饰符修饰的id类型或对象类型的自动变量时,**当Block从栈复制到堆的时候,使用_Block_object_assgin函数,持有被捕获对象,当堆上的Block被废弃的时候使用_Block_object_dispose函数,释放截获对象。

对于__block变量会发生相同的事情。所以即使对象赋值到堆上的附有strong修饰符的对象类型的__block变量中,只要__block变量在堆上继续存在,那么该对象就会继续处于被持有的状态。

__weak

对于之前的array代码,我们加入__weak修饰符试试.
这里的代码和书上的有所不同,按书上的我们需要重新初始化array释放,达到效果。

 array = [[NSMutableArray alloc] init];
- (void)blkWeak {void(^blk)(id);id array = [[NSMutableArray alloc] init];id __weak weakArray = array;array = [[NSMutableArray alloc] init];blk = [^(id obj) {[weakArray addObject:obj];NSLog(@"array count = %ld", [weakArray count]);} copy];blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);blk([[NSObject alloc] init]);NSLog(@"%@", blk);
}

在这里插入图片描述
同时加入__block试一试。
在这里插入图片描述
这是因为即使附加了__block说明符,附有__strong修饰符的变量array也会在该变量作用域结束的同时被释放被废弃,nil被赋值给附有__weak修饰符的变量的weakArray中。

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

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

相关文章

网络安全JavaSE第一天

1.环境的搭建 首先从 http://www.oracle.com 上下载对应的 JDK 版本,然后安装(或解压)到一个没有中文没有空格 的目录中。如:d:\jdk-21 然后在系统环境变量中先定义一个叫 JAVA_HOME 的环境变量,它的值就是JDK的安装/…

Github 2024-03-16 Rust开源项目日报 Top10

根据Github Trendings的统计,今日(2024-03-16统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10TypeScript项目2Go项目1RustDesk: 用Rust编写的开源远程桌面软件 创建周期:1218 天开发语言:Rust, Dart协议类型:GNU Affero Gene…

蓝桥杯单片机快速开发笔记——独立键盘

一、原理分析 二、思维导图 三、示例框架 #include "reg52.h" sbit S7 P3^0; sbit S6 P3^1; sbit S5 P3^2; sbit S4 P3^3; void ScanKeys(){if(S7 0){Delay(500);if(S7 0){while(S7 0);}}if(S6 0){Delay(500);if(S6 0){while(S6 0)…

高端全场景厨电,中国厨房的时代之路

作者 | 辰纹 来源 | 洞见新研社 “我们透着后视镜来观察目前,我们倒着走向未来。” 20世纪原创媒介理论家麦克卢汉在其《开脑术》(1967)的演讲中提出了,关于由技术创新所带来的新旧环境/场景更替的“后视镜”理论:环境在其初创期是看不见的…

动态规划8, 摆动序列,最长递增子序列,最长数对链

本次的题与动态规划7 的题有相似与共通之处,建议先去看 动态规划7:动态规划7 摆动序列 什么是摆动序列? 就像这种: 一个数,一个下降,上升,来回上升下降都可以叫摆动序列。 思路: …

c++中类的6个默认成员函数

构造函数 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员 都有 一个合适的初始值,并且在对象的生命周期内只调用一次。 构造函数是特殊的成员函数,需要注意的是,构…

2024.04

每一点建议都会包含为什么这么做的理由,帮助用户理解每个步骤背后的重要性和效果。 b004071ozy_05_amzn 第三步:授权开通资源中心(点开通后,需要等待几分钟时间) 图片 本文由 mdnice 多平台发布

深入了解 大语言模型(LLM)微调方法

引言 众所周知,大语言模型(LLM)正在飞速发展,各行业都有了自己的大模型。其中,大模型微调技术在此过程中起到了非常关键的作用,它提升了模型的生成效率和适应性,使其能够在多样化的应用场景中发挥更大的价值。 那么&…

C++的类和对象(七):友元、内部类

目录 友元 友元函数 友元类 内部类 匿名对象 拷贝对象时的一些编译器优化 再次理解类和对象 友元 基本概念:友元提供了一种突破封装的方式,有时提供了便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用&…

记录工作中莫名其妙的bug

1、问题:办公室的电脑突然除了我之外,都不能访问我们的线上系统了 原因:因为是内网,同事有刚刚升级了Windows11,配置的DNS被清了,还有同事换了公司的新电脑,还没有配DNS 位于:C /Win…

C#,入门教程(27)——应用程序(Application)的基础知识

上一篇: C#,入门教程(26)——数据的基本概念与使用方法https://blog.csdn.net/beijinghorn/article/details/124952589 一、什么是应用程序 Application? 应用程序是编程的结果。一般把代码经过编译(等)过程,最终形成的可执行 或 可再用 的文件称为应用程序。可执行文…

vue3项目随笔1

1,Eslint Prettier 报错情况: 解决办法: (1)下载Prettier - code formatter (2)配置setting.json文件 文件 -> 首选项 -> 设置 -> 用户 -> Eslint "editor.defaultFormatter":…