【OC总结- Block】

文章目录

  • 前言
  • 2. Block
    • 2.1 Block的使用规范
    • 2.2 __block修饰符
    • 2.3 Block的类型
    • 2.4 Block的循环引用及解决
      • 循环引用的场景引入
      • 解决循环引用
      • Block循环引用场景
    • 2.5 Block的实现及其本质
      • 2.5.1 初始化部分
      • 2.5.2 调用部分
      • 2.5.3 捕获变量
    • Block本质
    • 2.6 Block捕获变量 和 对象
    • 2.7 Block的内存管理
  • 3 Blcok的问题总结
    • 1. block在修改NSMutableArray,需不需要添加__block?
    • 2. __block如何达到修改内部的值?
      • __Block_byref_age_0结构体
    • __block 和Block 总结
    • Block为什么用copy修饰

前言

博客以总结为主,随时更新

2. Block

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

2.1 Block的使用规范

Block完整格式就是:变量声明 + 定义:
请添加图片描述

int(^sumBlk)(int num1, int num2) = ^int(int a, int b) {return a + b;};

Block变量类似于函数指针,

  • 用途:自动变量(局部变量)
    • 函数参数
    • 静态变量
    • 静态全局变量
    • 全局变量

截获自动变量:带有自动变量值在Block中表现为“截获自动变量值”。

值得注意的点: 在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获

2.2 __block修饰符

block可以截获变量,但是在块里不能修改变量的值

此时我们使用__block修饰符修饰变量,对需要在block内进行赋值的变量,使用修饰符修饰,保证可以对变量进行赋值。

// blk不允许修改截获变量的值,需要的时候加上__block修饰符即可id tstArray = @[@"blk", @"不允许赋值", @"给截获的变量"];__block id array = [[NSMutableArray alloc] init];void (^blk)(void) = ^{id obj = [[NSObject alloc] init];//        [array addObject:obj];array = tstArray;};

2.3 Block的类型

block分为全局 block堆 block栈 block
Block分类简单总结如下

  • A、没有引用外部变量 — block存放在全局区
  • B、引用了外部变量----显式声明为weak类型的block则存放在栈区,反之则是存在在堆区的,也就是说blockstrong类型的。
  • NSGlobalBlock
    block内部没有引用外部变量,Block类型都是NSGlobalBlock类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。如果访问了外部static或者 全局变量也是这种类型。
  • NSStackBlock
    访问了外部变量,但没有强引用指向这个block,如直接打印出来的block
  • NSMallocBlock
    ARC环境下只要访问了外部变量,而且有强引用指向该block(或者作为函数返回值)就会自动将__NSStackBlock类型copy到堆上。

2.4 Block的循环引用及解决

循环引用的场景引入

循环引用就是对方面持有导致对象不能正常释放,会发生内存泄漏

在BLock里互相的强引用可能造成循环引用。

typedef void(^TBlock)(void);@interface ViewController ()
@property (nonatomic, strong) TBlock block;
@property (nonatomic, copy) NSString *name;
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 循环引用self.name = @"Hello";self.block = ^(){NSLog(@"%@", self.name);};self.block();
}

这里self持有了block,block持有了self,导致循环引用。
只要有一方没有进行强引用就可以解除循环引用

- (void)viewDidLoad {[super viewDidLoad];// 循环引用self.name = @"Hello";void(^block)(void);block = ^(){NSLog(@"%@", self.name);};block();
}

为什么这个案例就没有出现循环引用的状况呢?因为当前self,也就是ViewController并没有对block进行强持有,block的生命周期只在viewDidLoad方法内,viewDidLoad方法执行完,block就会释放。

解决循环引用

  1. __weak 因为__weak不会对对象的引用计数操作,也就是不会进行强引用.
  2. 上面的案例修改成weak来避免循环引用如下:
    // 循环引用self.name = @"Hello";__weak typeof(self) weakSelf = self;self.block = ^(){NSLog(@"%@", weakSelf.name);};self.block();

此时self持有block,block弱引用self,弱引用会自动变为nil,强持有中断,所以不会引起循环引用。但该方法可能存在中途就释放掉的问题(手动延迟,可能需要调用self.name的时候name已经被释放了)如果self被销毁,那么block则无法获取name。

  1. 强弱共舞 :
    self.name = @"Hello";__weak typeof(self) weakSelf = self;self.block = ^(){__strong __typeof(weakSelf)strongWeak = weakSelf;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@", strongWeak.name);});};self.block();

完全解决了以上self中途被释放的问题。
原理:在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self。

weakSelf被强引用了就不会自动释放,因为strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放。

  1. 手动中断
    self.name = @"Hello";__block ViewController * ctrl = self;self.block = ^(){__strong __typeof(weakSelf)strongWeak = weakSelf;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@", ctrl.name);ctrl = nil;});};self.block();

使用ctrl之后,持有关系为: self -> block -> ctrl -> self,ctrl在block使用完成后,被置空,至此block对self持有就解除,不构成循环引用

  1. 参数形式解决循环引用 block传参(指针拷贝)
    // 循环引用self.name = @"Hello";self.block = ^(ViewController * ctrl){dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@", ctrl.name);});};self.block(self);

将self作为参数参入block中,进行指针拷贝,并没有对self进行持有

Block循环引用场景

静态变量持有

    // staticSelf_定义:static ViewController *staticSelf_;- (void)blockWeak_static {__weak typeof(self) weakSelf = self;staticSelf_ = weakSelf;}

weakSelf虽然是弱引用,但是staticSelf_静态变量,并对weakSelf进行了持有,staticSelf_释放不掉,所以weakSelf也释放不掉!导致循环引用!

2.5 Block的实现及其本质

BLock的实现是基于指针和函数指针,Block属性是指向结构体的指针

	//这是正常的block流程void(^myBlock)(void) = ^{printf("myBlock");};myBlock();

得到Block的结构的源码定义(C++):

    void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

初始化定义部分block语法变成了:&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
调用block的过程变成了:(__block_impl *)myBlock)->FuncPtr

2.5.1 初始化部分

初始化部分就是Block结构体

//Block结构体
struct __main_block_impl_0 {struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)impl.isa = &_NSConcreteStackBlock; // impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};

__main_block_impl_0结构体也就是Block结构体包含了三个部分:

  • 成员变量impl
  • 成员变量Desc指针
  • __main_block_impl_0构造函数

struct __block_impl结构:包含Block实际函数指针的结构体

struct __block_impl {void *isa;//用于保存Block结构体的实例指针int Flags;//标志位int Reserved;//今后版本升级所需的区域大小void *FuncPtr;//函数指针
};
  • _block_impl包含了Block实际函数指针FuncPtr,FuncPtr指针指向Block的主体部分,也就是Block对应OC代码中的^{…}的部分
  • 还包含了标志位Flags,在实现block的内部操作时可能会用到
  • 今后版本升级所需的区域大小Reserved
  • __block_impl结构体的实例指针isa

struct __main_block_desc_0结构:

Block附加信息结构体:包含今后版本升级所需区域的大小,Block的大小

static struct __main_block_desc_0 {size_t reserved;//今后版本升级所需区域大小size_t Block_size;//Block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

Block构造函数__main_block_impl_0

作为构造函数注意和Block结构体是一个名字。
负责初始化__main_block_impl_0结构体(也就是Block结构体struct __block_impl)的成员变量

  //可以看到里面都是一些赋值操作__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}

2.5.2 调用部分

  • 函数原型 ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

逐步解析这段代码:

  1. ((__block_impl *)myBlock)->FuncPtr:这部分将 myBlock 转换为 __block_impl 指针类型,并访问 FuncPtr 成员。它获取了块实现内部存储的函数指针。
  2. ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr):在这里,函数指针被转换为一个函数类型,该函数接受一个类型为 __block_impl* 的参数,并返回 void。它将函数指针转换为可以调用的实际函数类型。
  3. ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock):最后,使用 myBlock 作为参数,调用了所得到的函数指针。它使用块实现对象调用该函数。

总结一下,该代码获取了存储在块实现内部的函数指针,然后调用该函数,将块实现对象自身作为参数传递进去

2.5.3 捕获变量

Block捕获变量的时候 结构体里面多了 *NSObject obj; 以便Block去捕获。

Block本质

  1. 用一句话来说,Block是个对象(其内部第一个成员为isa指针)
  2. Block为什么出生就在栈上?
  • 在他的初始化函数里面请添加图片描述
    impl.isa = &_NSConcreteStackBlock;
    _NSConcreteStackBlock相当于该block实例的父类.将Block作为OC对象调用时,关于该类的信息放置于_NSConcretestackBlock中,这也证明了 block出生就是在栈上

2.6 Block捕获变量 和 对象

变量

  • 全局变量: 不捕获
  • 局部变量: 捕获值
  • 静态全局变量: 不捕获
  • 静态局部变量: 捕获指针
  • const修饰的局部常量:捕获值
  • const修饰的静态局部常量:捕获指针

对象
BLOCK 可以捕获对象,其中需要知道两个方法。

在捕获对象的时候代码出现了_main_block_copy_0_main_block_depose_0

  • __main_block_copy_0作用就是调用_Block_object_assign,相当于retain,将对象赋值在对象类型的结构体变量__main_block_impl_0中。在栈上的Block复制到堆时会进行调用。
  • __main_block_dispose_0调用_Block_object_dispose,相当于release,释放赋值在对象类型的结构体变量中的对象。在堆上的Block被废弃时会被调用。
    请添加图片描述

2.7 Block的内存管理

Block内存分析

block 在使用的时候,堆block注意作用域的问题,弱引用指针赋值,出了作用域就释放了,可以通过强引用解决

3 Blcok的问题总结

1. block在修改NSMutableArray,需不需要添加__block?

int main(int argc, const char * argv[]) {@autoreleasepool {NSMutableArray *array = [NSMutableArray array];Block block = ^{[array addObject: @"20"];[array addObject: @"30"];NSLog(@"%@",array);};block();}return 0;

可以正确执行,因为在block块中仅仅是使用了array的内存地址,往内存地址中添加内容,并没有修改arry的内存地址,因此array不需要使用__block修饰也可以正确编译。

‼️ 因此当仅仅是使用局部变量的内存地址,而不是修改的时候,尽量不要添加__block,通过上述分析我们知道一旦添加了__block修饰符,系统会自动创建相应的结构体,占用不必要的内存空间。

2. __block如何达到修改内部的值?

Block可以捕获值,看如下代码
请添加图片描述

请添加图片描述

原理: 首先被__block修饰的age变量声明变为名为age的__Block_byref_age_0结构体,也就是说加上__block修饰的话捕获到的block内的变量为__Block_byref_age_0类型的结构体

__Block_byref_age_0结构体

  • __isa指针 :__Block_byref_age_0中也有isa指针也就是说__Block_byref_age_0本质也一个对象。
  • *__forwarding :__forwarding是__Block_byref_age_0结构体类型的,并且__forwarding存储的值为(__Block_byref_age_0 )&age,即结构体自己的内存地址。
  • __flags :0
  • __size :sizeof(__Block_byref_age_0)即__Block_byref_age_0所占用的内存空间。
  • age :真正存储变量的地方,这里存储局部变量10。

接着将__Block_byref_age_0结构体age存入__main_block_impl_0结构体中,并赋值给__Block_byref_age_0 *age;
之后调用block,首先取出__main_block_impl_0中的age,通过age结构体拿到__forwarding指针,__forwarding中保存的就是__Block_byref_age_0结构体本身,这里也就是age(__Block_byref_age_0),在通过__forwarding拿到结构体中的age(10)变量并修改其值。

__forwarding是指向自己的指针。
请添加图片描述

总结:__block为什么能够修改变量的值是因为__block把变量包装成了一个带有指针的对象,然后把age封装在结构体里面,block内部存储的变量为结构体指针,也可以通过指针找到内存地址修改变量的值。

__block 和Block 总结

Block本质是一个对象,使用结构体实现。

//Block结构体
struct __main_block_impl_0 {struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)impl.isa = &_NSConcreteStackBlock; // impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};

__block作用:可以获取对应变量的指针,使其可以在block内部被修改。

__block的数据结构如下

struct __Block_byref_a_0 {void *__isa;//有isa,是一个对象
__Block_byref_a_0 *__forwarding;//指向自身类型对象的指针int __flags;//不用关心int __size;//自己所占大小int a;//被封装的 基本数据类型变量
};

Block为什么用copy修饰

因为Block的内存地址显示在栈区,栈区的特点就是创建的对象随时销毁,一旦销毁后续再次调用空对象就会造成程序崩溃。
对Block进行copy操作之后,block存在堆区,所以在使用Block属性的时候Copy修饰。

堆中的block,也就是copy修饰的block。他的生命周期就是随着对象的销毁而结束的。只要对象不销毁,我们就可以调用的到在堆中的block。

这就是为什么我们要用copy来修饰block。因为不用copy修饰的访问外部变量的block,只在他所在的函数被调用的那一瞬间可以使用。之后就消失了。

注意:ARC的Block一般都是在堆上,因为系统默认堆Block进行copy操作。 不论ARC还是MRC 用copy Strong修饰Block都是堆Block。

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

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

相关文章

数据结构-双向带头循环链表

链表的分类实现带有哨兵位的双向的循环链表**定义节点的结构**初始化单个节点初始化带有哨兵位的双向循环链表打印链表销毁链表尾插尾删头插头删find函数在任意位置之前插入任意位置的删除全部代码list.hlist.ctest.c 链表和顺序表的区别 链表的分类 如下 根据上述的三种组合…

部署langchain+chatglm

先参考:window零基础部署langchain-ChatGLM_飞奔的屎壳郎的博客-CSDN博客 安装一部分, 1.GCC安装 gcc64位下载 一定要装64位的gcc,因为我的电脑是w10 64位的,装32位运行langchain报错并配置环境变量 可直接用压缩包中的文件&am…

Verilog 学习之路

循环 7-10 代码段 generategenvar i;for (i0; i<8; i i1) begin: my_block_nameassign out[i] in[8-i-1];end endgenerate解释 该代码使用了 S y s t e m V e r i l o g SystemVerilog SystemVerilog 中的 g e n e r a t e generate generate 构造&#xff0c;它允许在…

【软考】系统架构设计风格分类的个人理解

个人适当学习了软考系统架构设计师中关于系统架构设计相关的内容&#xff0c;梳理了一下相关信息。 常见架构类型和常见分类 常见的软考中出现的系统架构列举如下&#xff1a; 分层架构管道-过滤器架构客户端-服务器架构模型-视图-控制器架构&#xff0c;即MVC架构事件驱动架…

行为树(BEHAVIOR TREES)及其工业应用

顾名思义&#xff0c;行为树是描述事物&#xff08;人&#xff0c;动物&#xff0c;机器人&#xff0c;虚拟角色等等&#xff09;行为的树形结构。游戏行业使用行为树为角色行为建模。现在行为树建模技术正在向其它领域渗透&#xff0c;比如工业产线编排&#xff0c;机器人控制…

【从零开始学习C++ | 第二十一篇】C++新增特性 (上)

目录 前言&#xff1a; 委托构造函数&#xff1a; 类内初始化&#xff1a; 空指针&#xff1a; 枚举类&#xff1a; 总结&#xff1a; 前言&#xff1a; C的学习难度大&#xff0c;内容繁多。因此我们要及时掌握C的各种特性&#xff0c;因此我们更新本篇文章&#xff0c;向…

【案例实战】高并发业务的多级缓存架构一致性解决方案

我们在高并发的项目中基本上都离不开缓存&#xff0c;那么既然引入缓存&#xff0c;那就会有一个缓存与数据库数据一致性的问题。 首先&#xff0c;我们先来看看高并发项目里面Redis常见的三种缓存读写模式。 Cache Aside 读写分离模式&#xff0c;是最常见的Redis缓存模式&a…

【状态估计】基于卡尔曼滤波器和扩展卡尔曼滤波器用于 INS/GNSS 导航、目标跟踪和地形参考导航研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【深入探究人工智能】:历史、应用、技术与未来

深入探究人工智能 前言人工智能的历史人工智能的应用人工智能的技术人工智能的未来当代的人工智能产物结语&#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &am…

LangChain大型语言模型(LLM)应用开发(三):Chains

LangChain是一个基于大语言模型&#xff08;如ChatGPT&#xff09;用于构建端到端语言模型应用的 Python 框架。它提供了一套工具、组件和接口&#xff0c;可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互&#x…

【UE4 塔防游戏系列】10-防御塔升级

目录 效果 步骤 一、根据防御塔等级修改子弹伤害 二、根据防御塔等级修改子弹速度 三、根据防御塔等级修改检测半径 四、根据防御塔等级修改子弹颜色 五、根据防御塔等级修改换弹时间 效果 步骤 一、根据防御塔等级修改子弹伤害 1. 打开“TowerBaseBullet_Child”&…

【ArcGIS Pro二次开发】(48):三调土地利用现状分类面积汇总统计

之前做了一个三调三大类面积统计&#xff0c;有小伙伴反映太粗糙&#xff0c;想要一个完整的地类面积汇总表。 【ArcGIS Pro二次开发】(35)&#xff1a;三调三大类面积统计 本质上并没有多少难度&#xff0c;之前也做过类似的用地用海汇总表&#xff0c;于是拿出来改一改就好了…

window 命令笔记

1.查看端口 输入“netstat -ano”并回车可以获得所有网络连接活动的列表&#xff0c;在表中&#xff0c;本地地址IP地址后方冒号之后的即是端口号&#xff1a; 如果想要查找特定的端口可以输入命令“netstat -aon|findstr “端口号””&#xff0c;例如“netstat -aon|findstr…

My_window类(带有next和quit按钮)

运行代码&#xff1a; //My_window类&#xff08;带有next和quit按钮&#xff09; #include"std_lib_facilities.h" #include"GUI/Simple_window.h" #include"GUI/GUI.h" #include"GUI/Graph.h" #include"GUI/Point.h"//--…

C++基础算法离散化及区间合并篇

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C算法 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 主要讲解了双指针&#xff0c;位运算&#xff0c;离散化以及区间合并。…

有效的括号(C)

bool isValid(char* s) {ST st;StackInit(&st);while (*s) //遍历 -- 与\0终止{//是左括号 压栈if (*s ( || *s [ *s {){StackPush(&st, *s);s;}else{//应对样例&#xff1a; ’]if (StackEmpty(&st)){StackDestroy(&st);return false;}//不是左括号 应该就…

Spark MLlib快速入门(1)逻辑回归、Kmeans、决策树、Pipeline、交叉验证

Spark MLlib快速入门(1)逻辑回归、Kmeans、决策树案例 除了scikit-learn外&#xff0c;在spark中也提供了机器学习库&#xff0c;即Spark MLlib。 在Spark MLlib机器学习库提供两套算法实现的API&#xff1a;基于RDD API和基于DataFrame API。今天&#xff0c;主要介绍下Data…

docker k8s

Docker docker到底与一般的虚拟机有什么不同呢&#xff1f; 我们知道一般的linux系统即GNU/Linux系统包括两个部分&#xff0c;linux系统内核GNU提供的大量自由软件&#xff0c;而centos就是众多GNU/Linux系统中的一个。 虚拟机会在宿主机上虚拟出一个完整的操作系统与宿主机完…

在 3ds Max 中对链模型进行摆放姿势处理

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 建模和“摆姿势”3D链可能看起来是一项繁琐的工作&#xff0c;但实际上可以通过使用阵列工具并将链中的链接视为骨骼来轻松完成。在本教程中&#xff0c;我将向您展示如何对链条进行建模&#xff0c;并通过…

oled拼接屏在柳州的户外广告中有哪些应用展现?

柳州oled拼接屏是一种高端的显示屏&#xff0c;它采用了OLED技术&#xff0c;具有高亮度、高对比度、高色彩饱和度、高刷新率等优点&#xff0c;能够呈现出更加真实、清晰、细腻的图像效果。 同时&#xff0c;柳州oled拼接屏还具有拼接功能&#xff0c;可以将多个屏幕拼接在一…