当前位置: 首页 > ai >正文

OC学习—Block初探(简易版)

前言

笔者在学习过程中经常遇到使用Block的地方,不太理解,因此来学习了Block的内容

回调

回调是编程中一种延迟执行的机制,允许代码在特定时间发生或任务完成后执行特定的逻辑

回调的本质:控制反转

传统调用流程;

调用方->被调用方->执行完成->返回结果给调用方

回调调用流程:

调用方->被调用方->执行完成->触发回调->执行调用方法的逻辑

核心思想:

将下一步做什么的决策权从被调用方交给调用方

解耦:被调用方无需关心具体的后续逻辑,只负责在特定时机出发回调

四种主要的回调方式

还没学到这里,笔者后面会补充的

Block

Block的定义

  1. 在iOS开发中,Block是一种闭包(Closure)的概念,可以将一段代码作为一个对象进行传递和存储,类似于函数的指针。Block可以捕获其定义时所在范围内的变量,并在需要时执行这段代码块,Block的使用可以方便的实现回调、异步操作、事件处理等功能

  1. Block的本质也是对象,第一段代码块,iOS之所以引入Block,是因为使用Block可以精简代码,减少耦合

Block的用法

  • 作为变量使用

// 返回值类型(^变量名)(参数列表)
int (^multiply)(int, int) = ^(int a, int b) {return a * b; 
}
//调用Block
int result = multiply(3, 5);
  • 作为方法参数

- (void)performOperationWithBlock:(void(^)(void))block {block();
}
[self performOperationWithBlock:^{NSLpg(@"执行Block");
}]
  • 作为属性或者实例变量

@property (nonatomic, copy) void (^completionBlock)(void);
self.completionBlock = ^{NSLog(@"执行操作");
}

Block与外部变量

在iOS开发中,Block可以捕获其定义时所在范围内的外部变量,这种特性称之为变量捕获(Variable Capture),当Block内部引用外部变量时,Block会自动捕获这些变量的值或引用,以便在Block执行时使用,在Block中使用外部变量可以实现数据共享与传递(确保Block在脱离于原始作用域后仍能正确访问外部变量)

变量类型类型捕获到block内部访问方式
局部变量auto可以值传递
局部变量static可以指针传递
全局变量不可以直接访问
  • 局部变量auto(自动变量),平时我们写的局部变量,默认就有auto,离开作用域就销毁

int age = 20; //等价于 auto int age = 20;
  • 局部变量static:修饰局部变量,不会被销毁,由于下面代码可以看得出来Block外部修改static修饰的局部变量,依然可以影响Block内部的值

static int height = 30;
int age = 20 ;
void (^block)(void) = ^{NSLog(@"age us %d height = %d", age, height);
};
age = 25;
height = 35;
block();
//运行后发现是结果为 20  35

解释:

  1. 对于auto类型的局部变量,闭包会拷贝变量的值到闭包内部,形成值捕获(当闭包定义时,会保存这个值的副本)

  1. 对于static修饰的静态变量,闭包会引用静态变量的地址,形成引用捕获(静态变量存储在全局静态区,闭包保存的是他的内存地址,当执行修改变量的值时,是修改静态区的值,闭包通过地址访问到新值35)

  • 全局变量:全局变量不能也无需捕获到Block内部,因为全局变量是存放在全局静态区的区,直接访问即可,缺点就是内存开销大

在Block内部如何修改Block外部变量?

通常需要_block关键字来声明外部变量,以便于在Block内部对外部变量进行修改,使用这个关键字可以使外部变量在Block内部变为可变,允许对其进赋值操作

在使用_block关键字声明外部变量时,需要注意内存管理的问题,特别是在ARC环境下,避免出现循环引用或者内存泄露的情况,要在适当的时候解除对Block的强引用,以避免出现循环引用的问题,实现代码的正确性和稳定性

捕获的底层原理

1.对于auto类型的局部变量,底层会生成一个结构体,包含变量的副本,闭包被定义时age = 20被拷贝到结构体中,后续修改外部age不影响结构体中的值

struct BlockStruct {int age;//保存捕获的age的值void (*invoke)(struct BlockStruct *self);
}

2.对于static修饰的静态变量,底层生成的结构体不会拷贝副本,直接使用全局地址,闭包通过地址height访问全局静态区的值,修改后闭包能开到最新的值

struct BlockStruct {int* height;void (*invoke)(struct BlockStruct* self);
};
  • 如何让局部变量被引用捕获?

如果希望局部变量同样被引用捕获,可以使用_block关键字

_block int age = 20;

Block的分类

根据存储位置分类

  • 栈上的Block:默认情况下,存储在栈上,当Block离开作用域时会被销毁

  • 堆上的Block:通过调用copy方法,可以将栈上的Block复制到堆上,以延长Block的使用周期

栈上的Block

  1. 特点

    • 默认情况下,Block储存在栈上

    • 栈上的Block在定义他的作用域内有效,当作用域结束时,Block会被销毁

    • 栈上的Block不可以跨作用域使用

  1. 内存管理

    • 栈上的Block不需要手动管理内存,由系统自动管理

    • 当Block被复制到堆上时,系统会自动将其从栈上复制到堆上,以延长Block的生命周期

  2. 注意:将栈上的Block传递给异步执行的方法时,需要注意避免在Block执行过程中访问已经释放的栈上变量,可以通过将Block复制到堆上来避免此问题。(只要Block的执行时机可能晚于其定义所在的函数作用域,可就必须将其复制到栈上,避免访问已经释放的Block)

堆上的Block

  1. 特点

    • 通过调用copy方法,可以将栈上的Block复制到堆上

    • 堆上Block的生命周期可以延长,不会受到作用域的限制

    • 堆上的Block可以在不同的作用域中传递和使用,不会因为作用域结束而失效

  2. 内存管理

    • 堆上的Block需要手动管理内存,需要在不使用Block时手动释放

    • 在ARC环境下,系统会管理Block的内存,无需手动调用copy和release

  3. 当Block捕获外部变量时,如果Block存储在堆上,需要注意循环引用问题,可以使用_weak修饰符来避免循环引用

总的来说,栈上的Block在作用域内有效,适合用于临时的、局部的逻辑处理;而堆上的Block可以延长生命周期,适合在不同作用域中传递和复用。

Block的三种类型(底层)

  1. NSGlobalBlock:存储在全局区,Block不捕获任何外部变量时自动生成,无需copy,生命周期与程序一致

  1. NSStackBlock:存储在栈区(随函数返回自动销毁),Block捕获外部变量时默认生产

  2. NSMallocBlock:存储在堆区(需要手动管理内存或依赖ARC),通过copy将NSStackBlock复制到堆上

区分栈上和堆上的Block

  1. 使用NSLog打印Block的地址

  1. 使用_block变量

    • 栈上的block捕获block变量时,变量的值会被Block捕获,但是在作用域结束之后,block的值会被更新为Block执行时的值

    • 堆上的Block捕获_block时,变量的值会复制到堆上的Block中,作用域结束后 _block的值不会被更新

    _block的双重存储:当 _block被堆上的block捕获时,变量的初始值会被复制到堆上的Block中(就是一个副本)

  1. 使用Block_copy函数:

    • 栈:对于栈上的Block使用Block_copy函数会将其复制到堆上,返回一个新的堆上

    • 堆:仍会返回一个新的堆上的Block,地址不同

  2. 使用_weak修饰符

    • 栈上的Block捕获到_weak变量时,变量会被自动置为nil,因为栈上的block不会强因为用 _weak变量

    • 堆上的Block捕获到-weak变量时,变量会正常工作,因为堆上的Block会强引用_weak变量

Block的循环引用

循环引用是指两个或者多个对象之间相互持有对方的强引用,导致他们无法被释放从而造成内存泄漏,特别是在Block中捕获了外部的对象并且外部的对象又持有了block

示例:

@interface ViewController : UIViewController
@property (nonatomic, copy) void(^myBlock)(void);
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.myBlock = ^ {//Block中捕获了self,导致循环引用[self doSomething];}
}
​
- (void)doSomething {NSLog(@"ppppp");
}

在上面代码中,ViewController的实例持有了一个Block,并且在Block中捕获了self,导致了循环引用

解决办法:

1.使用_weak修饰符

_weak typedef(self) weakSelf = self;
self.myblock = ^{[weakSelf doSomthing];
}

2.使用_block修饰符,可以让Block持有一个弱引用指针

什么是异步执行?

异步执行方法是一种程序执行模式,指方法或函数的调用不会阻塞当前线程的后续操作,而是在另一个线程或队列中独立执行。

其实笔者还没学到多线程的内容等后面学了讲解

http://www.xdnf.cn/news/13465.html

相关文章:

  • 【实战指南】前端项目Nginx配置全流程:从打包部署到解决跨域/路由循环问题
  • 在C# 中使用建造者模式
  • 算法题(167):FBI树
  • Oracle日志体系和遇到问题后日志排查路径
  • 行为模式-责任链模式
  • 进行性核上性麻痹健康护理指南:全方位照护之道
  • Pytorch 的编程技巧
  • Java八股文——Spring「Spring 篇」
  • 5.4.2树、森林与二叉树的转换
  • 今日行情明日机会——20250611
  • Android GreenDAO 通过 Key 查询数据库数据慢问题优化
  • 13.自治系统路由计算题
  • Node.js:开启现代服务器端编程的新篇章
  • h5fortran 简介与使用指南
  • 新能源知识库(36)什么是BMU
  • 51LA数据分析遇瓶颈?免费统计工具——悟空统计
  • 大话软工笔记—工程分解
  • GlusterFS分布式文件系统
  • 【Keepalived】Keepalived-2.3.4恢复对RHEL7的支持
  • 第七章: SEO与渲染方式 三
  • (十一)优化算法(Optimization):深度学习训练中的收敛性分析与泛化理论
  • 鹰盾视频加密器Windows播放器AI溯源水印技术方案解析
  • ros2--Sophus
  • “新液冷”破题“智算热”,数字经济低碳化发展新解
  • 【Linux】Linux 操作系统 - 22 , 软硬链接详解 !
  • 104.解决在流式回答功能实现之后上传附件功能失效bug之前端处理
  • DAY 28 类的定义和方法
  • 三代社保卡全字段识别-社保卡识别软件-社保卡识别接口集成
  • 结合redis实现文件分片秒传断点续传
  • Linuxkernel学习-deepseek-2