【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。

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

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

相关文章

基于 ChatGPT 的 helm 入门

1. 写在最前面 公司最近在推业务上云(底层为 k8s 管理),平台侧为了简化业务侧部署的复杂度,基于 helm 、chart 等提供了一个发布平台。 发布平台的使用使业务侧在不了解 helm 、chart 等工具的时候,「只要点点」就可…

LCD—STM32液晶显示(1.显示器简介及LCD显示原理)(6000字详细介绍)

目录 显示器简介 液晶显示器 液晶 像素 液晶屏缺点 LED显示器 OLED显示器 显示器的基本参数 STM32板载液晶控制原理(不带微控制器) 液晶控制原理 控制信号线(不带液晶控制器) 液晶数据传输时序 显存 总结 3.2寸液晶屏介绍(搭载…

IIS Express本地开发测试如何映射到外网访问?

1.IIS Express是什么 IIS Express是为开发人员优化的轻量级、自包含版本的IIS。它具有IIS 7及以上的所有核心功能,以及为简化网站开发而设计的附加功能。 IIS Express(跟ASP.NET开发服务器一样)可以快速地从硬盘上的某个文件夹上启动网站…

SylixOS下SSH和SFTP连接

简要 基于网络的连接(telnet,ftp)方便高效,但其是基于明文的通信,容易被窃取、篡改和攻击,存在网络安全问题,尤其在进行远程访问时,穿过复杂未知的公网环境非常危险,为此…

中信银行西安分行举办金融助力外贸企业“走出去“高端论坛

7月14日,中信银行西安分行联合中国出口信用保险公司陕西分公司、西安市工商联举办"智汇西安、信融全球"——金融助力外贸企业"走出去"高端论坛。该论坛紧跟“加快建设贸易强国”的战略指引,以创新金融服务助力外贸企业融入高水平对外…

C++-----vector

本期我们来学习C中的vector,因为有string的基础,所以我们会讲解的快一点 目录 vector介绍 vector常用接口 构造函数 sort 迭代器 size,max_size,capacity,empty reserve和resize front和back data insert和…

解决appium-doctor报opencv4nodejs cannot be found

一、下载cmake 在CMake官网下载:cmake-3.6.1-win64-x64.msi 二、安装cmake cmake安装过程 在安装时要选择勾选为所有用户添加CMake环境变量 三、检查cmake安装 重新管理员打开dos系统cmd命令提示符,输入cmake -version cmake -version四、安装opencv4no…

pycharm里debug时torch数组显示不全

pycharm里查看torch数组全部值 一、在Pycharm运行torch数组时,通常只能看到数组的一部分二、解决办法1、debug后,鼠标右键想要查看完整的数组,选择Evaluate Expression2、输入np.array(x0.data),x0为想要查看的数组名,…

畅游NLP海洋:HuggingFace的快速入门

目录 前言一、HuggingFace介绍1-1、HuggingFace的介绍1-2、安装 二、Tokenizer分词库:分词工具2-0、加载BertTokenizer:需要传入预训练模型的名字2-1、使用Tokenizer对句子编码:2-2、使用增强Tokenizer对句子编码:2-3、批量编码单…

Apache Kudu 在**医疗科技的生产实践

目录 说明 医疗场景下数据特点 KUDU 的介绍 kudu 架构 kudu 文件组织形式 kudu的生产实践 技术选型 整体的架构 项目遇到的问题 参考资料 说明 本文主要介绍APACHE KUDU 在**医疗科技数据实时分析场景下的实践,内容包括: 医疗场景下数据特点 …

LCD—STM32液晶显示(4.液晶控制代码讲解)

目录 STM32液晶控制代码讲解 液晶接口封装介绍 使用LCD的配置步骤 内存操作要使用volatile进行修饰 图形绘制实现 绘制矩形 重点补充 STM32液晶控制代码讲解 液晶接口封装介绍 指南者液晶接口原理图 左边DB00—DB15表示液晶屏的数据线引脚,分别对应STM32的F…

【无线通信模块】什么是PCB板载天线,PCB板载天线UART/USB接口WiFi模块

基于射频技术的无线模块需要通过天线来发射和接收电磁波信号,市场上常见的天线类型有陶瓷天线、板载天线、棒状天线以及外接天线,外接天线是通过在PCB板上预留IPEX座子,可选天线类型就比较多。本篇SKYLAB小编带大家了解一下板载天线的UART接口…

Ubuntu 考虑采用新的 “统一默认安装 (unified default install)”

导读Ubuntu安装程序中的 “最小化安装” (Minimal installation) 是该发行版多年来最受欢迎的功能之一。 当用户选择 Ubuntu 的 “最小化安装” 选项时,可以在安装更少的预装应用程序情况下,获得完整、功能齐全的 Ubuntu 系统。 但这个功能可能要被砍掉…

MVVM 实现记录文本

1. MVVM 框架说明: Model - 数据层 View - 视图层 ViewModel - 管理模型的视图 2. 资源文件 2.1 启动图标: AppIconhttps://img-blog.csdnimg.cn/8fa1031489f544ef9757b6b3ab0eddbe.png 2.2 Display Name: Do Stuff 2.2 颜色图: 2.3 项目结构图: 3. Model 层实现&a…

设计模式——享元模式

享元模式 定义 享元模式(Flyweight Pattern)是池技术的重要实现方式。 使用共享对象可以有效地支持大量的细粒度对象。 优缺点、应用场景 优点 可以大大减少应用程序创建对象的数量,降低程序内存占用。 缺点 提高了系统的复杂度&…

F#奇妙游(14):F#实现WPF的绑定

WPF中的绑定 绑定在UI开发中是一个非常重要的概念,它可以让我们的UI界面和数据模型之间建立起联系,当数据模型发生变化时,UI界面也会随之变化,反之亦然。这样的好处是显而易见的,我们不需要手动去更新UI界面&#xff…

React native 已有项目升级兼容web

基础 概念 | webpack 中文文档 | webpack 中文文档 | webpack 中文网 深入理解Webpack及Babel的使用 - 掘金 Introduction to React Native for Web // React Native for Web Webpack 是一个现代的 JavaScript 应用程序的静态模块打包工具,它将应用程序所依赖的各…

回归预测 | MATLAB实现GRU(门控循环单元)多输入单输出(不调用工具箱函数)

回归预测 | MATLAB实现GRU(门控循环单元)多输入单输出(不调用工具箱函数) 文章目录 回归预测 | MATLAB实现GRU(门控循环单元)多输入单输出(不调用工具箱函数)预测效果基本介绍程序设计参考资料 预测效果 基本介绍 GRU神经网络是LSTM神经网络的一种变体,LSTM 神经网 …

opencv 基础学习08-图像通道操作

opencv 基础学习08-图像通道操作 什么是图像通道?通道操作:**1 通过索引拆分**2 通过opencv 函数拆分通道合并 什么是图像通道? OpenCV的通道拆分功能可用于将多通道图像拆分成单独的通道,这在图像处理和计算机视觉任务中具有许多…

电子锁语音芯片方案,低功耗声音提示ic,WT588F02B-8S

随着科技的不断发展,电子锁已成为现代社会中,安全性和便利性并存的必备设备。如何为电子锁行业增添智能化、人性化的功能已成为行业内的热门话题。 在这个迅速发展的市场中,深圳唯创知音推出了一款语音交互方案——WT588F02B-8S 低功耗声音提…