【iOS】—— Block总结

鼠扑  论坛元老 | 2024-7-30 07:40:33 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1068|帖子 1068|积分 3204

1. Block的使用规范

Block完整格式:声明变量+定义
  1. //block声明
  2. void (^blockName) (int a, int b);
  3. //block实现
  4. int ^(int a, int b){
  5. };
复制代码
  1. int (^blockName)(int numA, int numB) = ^int (int a, int b){
  2.       return a+b;
  3.   };
复制代码
Block变量雷同于函数指针。
用途:自动变量(局部变量)


  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量
截获自动变量:带有自动变量的值在Block中体现为“截获自动变量”。
值得注意是 :在如今的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获
Block判空:
在iOS中对一个 Block 举行判空实际上是在查抄这个 Block变量是否有指向创建出来Block对象。
正如下面的例子:
  1. void (^myBlock)(void);
  2.   if (myBlock) {
  3.       NSLog(@"2");
  4.   } else {
  5.       NSLog(@"1");
  6.   }
  7.   NSLog(@"%p", myBlock);
复制代码
运行结果:说明该Block为空

这个是Block实际函数指针的结构体:
  1. struct __block_impl {
  2.   void *isa;//用于保存Block结构体的实例指针
  3.   int Flags;//标志位
  4.   int Reserved;//今后版本升级所需的区域大小
  5.   void *FuncPtr;//函数指针,FuncPtr指针指向Block的主体部分,也就是Block对应OC代码中的^{…}的部分
  6. };
复制代码
而对于判空的底层代码逻辑则要跟更具体一点:
  1. // 判空并执行 Block
  2.   if (myBlock != NULL && myBlock->FuncPtr != NULL) {
  3.       myBlock->FuncPtr();
  4.   } else {
  5.       printf("Block is NULL!\n");
  6.   }
复制代码
因此判断Block是否为空有两个步调:查抄 Block 变量是否为 nil查抄 Block 中的函数指针是否为 NULL
2. __block修饰符

block可以截获变量,但是在block内部不能修改变量的值。
因此使用__block修饰符修饰变量,对必要在block内部赋值的变量,使用修饰符,确保可以对变量举行修饰。
  1. id tyarray = @[@"blk", @"123", @"234"];
  2. id __block arr = [[NSMutableArray alloc] init];
  3. void (^blk) (void) = ^{
  4.     arr = tyarray;
  5.     NSLog(@"%@", arr);
  6. };
复制代码
__block修饰符的底层原理

我们都知道block捕获了带有__block修饰符的变量时可以修改变量的值,但是具体是怎么做到的?
以下面的变量为例:
  1. __block int b = 10;
  2. void (^block2)(void) = ^{
  3.      NSLog(@"%d",b);
  4. };
  5. b = 100;
  6. block2();
复制代码
原理:首先被__block修饰的变量b,声明变为b的__Block_byref_b_0结构体,加上__block修饰符的话捕获到的block内的变量为__Block_byref_b_0类型的结构体。
接下来看一下**__Block_byref_val_0**结构体的底层:
  1. struct __Block_byref_val_0 {
  2. void *__isa;
  3. __Block_byref_val_0 *__forwarding;
  4. int __flags;
  5. int __size;
  6. int val;
  7. };
复制代码


  • __isa指针:Block_byref_age_0中也有isa指针也就是说__Block_byref_b_0本质也一个对象。
  • __forwarding:__forwarding是__Block_byref_b_0**结构体类型的,并且__forwarding存储的值为(__Block_byref_age_0 )&b,即结构体自己的内存地点。
  • __flags :0
  • __size :sizeof(__Block_byref_b_0)即__Block_byref_b_0所占用的内存空间。
  • b :真正存储变量的地方,这里存储局部变量10。
接着将 __Block_byref_b_0结构体b存入__main_block_impl_0结构体中,并赋值给__Block_byref_b_0 *age
之后调用block,首先取出__main_block_impl_0中的b,通过b结构体拿到__forwarding指针,__forwarding中生存的就是__Block_byref_b_0结构体本身,这里也就是b(__Block_byref_b_0),在通过__forwarding拿到结构体中的b(10)变量并修改其值。
总结:block为什么能够修改变量的值?由于block把变量包装成了一个带有指针的对象,然后把b封装在结构体里面,block内部存储的变量为结构体指针,也可以通过指针找到内存地点修改变量的值。
3. Block的类型

Block的存储域

三种类型对应不同的地区:

NSGlobalBlock

一个Block没有访问外部的局部变量,或者访问的是全局变量,或者是静态局部变量。此时的Block是全局Block,数据存储在全局区。
  1.     //block1没有引用到局部变量
  2.     int a = 10;
  3.     void (^block1)(void) = ^{
  4.          NSLog(@"hello world");
  5.     };
  6.     NSLog(@"block1:%@", block1);
  7.     //    block2中引入的是静态变量
  8.     static int a1 = 20;
  9.     void (^block2)(void) = ^{
  10.         NSLog(@"hello - %d",a1);
  11.     };
  12.     NSLog(@"block2:%@", block2);
复制代码
运行结果如下:

NSStackBlock

在捕获了局部变量之后,Block就会变成NSStackBlock,数据存储在栈中。
  1. int a1 = 20;
  2. void (^block2)(void) = ^{
  3.      NSLog(@"hello - %d",a1);
  4. };
  5. NSLog(@"\nblock2:%@", block2);
复制代码
运行结果如下:但是刚开始打印的时间,并不是NSStackBlock,缘故原由是:**在ARC环境下体系会自动将block举行拷贝操作。**只要换成MRC就行了。

NSMallocBlock

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


  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block 赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或 Grand Central Dispatch的 API 中传递 Block 时。
  1.     int a = 10;
  2.     void (^block1)(void) = ^{
  3.         NSLog(@"%d",a);
  4.     };
  5.     NSLog(@"block1:%@", [block1 copy]);
  6.     __block int b = 10;
  7.     void (^block2)(void) = ^{
  8.         NSLog(@"%d",b);
  9.     };
  10.     NSLog(@"block2:%@", [block2 copy]);
复制代码
运行结果如下:

简单来说,没有捕获自动变量的就是数据区,捕获了自动变量但是没有举行copy操作就是栈区,copy之后就变成了堆区。
4. Block的实现及本质

初始化部门

初始化部门就是Block结构体
  1. //Block结构体
  2. struct __main_block_impl_0 {
  3.   struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
  4.   struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
  5.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
  6.     impl.isa = &_NSConcreteStackBlock;  
  7.     impl.Flags = flags;
  8.     impl.FuncPtr = fp;
  9.     Desc = desc;
  10.   }
  11. };
复制代码
__main_block_impl_0结构体也就是Block结构体包含了三个部门:


  • 成员变量impl;
  • 成员变量Desc指针;
  • __main_block_impl_0构造函数;
struct __block_impl结构:包含Block实际函数指针的结构体
  1. struct __block_impl {
  2.   void *isa;//用于保存Block结构体的实例指针
  3.   int Flags;//标志位
  4.   int Reserved;//今后版本升级所需的区域大小
  5.   void *FuncPtr;//函数指针
  6. };
复制代码


  • _block_impl包含了Block实际函数指针FuncPtr,FuncPtr指针指向Block的主体部门,也就是Block对应OC代码中的^{…}的部门
  • 还包含了标志位Flags,在实现block的内部操作时可能会用到。
  • 今后版本升级所需的地区大小Reserved。
  • __block_impl结构体的实例指针isa。
struct __main_block_desc_0结构:
  1. static struct __main_block_desc_0 {
  2.   size_t reserved;//今后版本升级所需区域大小
  3.   size_t Block_size;//Block大小
  4. } __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)的成员变量
  1.    //可以看到里面都是一些赋值操作
  2.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
  3.     impl.isa = &_NSConcreteStackBlock;
  4.     impl.Flags = flags;
  5.     impl.FuncPtr = fp;
  6.     Desc = desc;
  7.   }
复制代码
调用部门

函数原型 ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
徐徐分析这段代码:


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


  • 用一句话来说,Block是个对象(其内部第一个成员为isa指针)
  • 在初始化函数里面:
  1. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
  2.     impl.isa = &_NSConcreteStackBlock;  
  3.     impl.Flags = flags;
  4.     impl.FuncPtr = fp;
  5.     Desc = desc;
复制代码
impl.isa = &_NSConcreteStackBlock;
_NSConcreteStackBlock相称于该block实例的父类.将Block作为OC对象调用时,关于该类的信息放置于_NSConcretestackBlock中,这也证明了 block出生就是在栈上。
5. 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被废弃时会被调用。
内存管理

捕获外部变量引用计数的变革
  1. NSObject *objc = [NSObject new];
  2. NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
  3. void(^strongBlock)(void) = ^{
  4.      NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
  5. };
  6. strongBlock();
  7. void(^__weak weakBlock)(void) = ^{
  8.      NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
  9. };
  10. weakBlock();
  11. void(^mallocBlock)(void) = [weakBlock copy];
  12. mallocBlock();
复制代码
运行结果:



  • 第一个为1,就是简单创建引用计数+1
  • 第二个为3,strongBlock是在堆区的block,这里捕获objc时会对外部变量+1,这里将栈区的objc拷贝进堆区时,又举行了+1,所以为3。
  • 第三个为4,weakBlock是栈区的block,捕获objc没有举行拷贝就直接+1,所以为4。
  • 第四个为5,[weakBlock copy]举行了拷贝,因此引用计数再+1,所以为5。
6. 循环引用

什么是循环引用

对象持有导致对象不能实时的正常释放,容易造成内存泄漏。

  1. #import "ViewController.h"
  2. typedef void (^TBlock)(void);
  3. @interface ViewController ()
  4. @property (nonatomic, strong) NSString *name;
  5. @property (nonatomic, strong) TBlock block;
  6. @end
  7. @implementation ViewController
  8. - (void)viewDidLoad {
  9.     [super viewDidLoad];
  10.   
  11.     self.name = @"View";
  12.     self.block = ^() {
  13.        NSLog(@"%@", self.name);
  14.     };
  15.     self.block();
  16.    
  17. }
  18. @end
复制代码
self持有了block,block持有了self,导致循环引用。 编译器也会提示:Capturing 'self' strongly in this block is likely to lead to a retain cycle
如果片面取消一方的持有即可取消循环。
  1.   // 不会引起循环引用
  2.     void(^blk1)(void);
  3.     blk1 = ^() {
  4.         NSLog(@"%@", self.name);
  5.     };
  6.     blk1();
复制代码
这个案例就没有出现循环引用是由于当前self,也就是ViewController并没有对block举行强持有,block的生命周期只在viewDidLoad方法内,viewDidLoad方法实行完,block就会释放。
循环引用办理方法

之前提到过weak,可以办理循环引用题目。
weak的使用
  1.   __weak typeof(self) weakSelf = self;
复制代码
typeof(self):typeof 是一个运算符,用于获取表达式的类型。在这种环境下,表达式是 self,它代表当前对象的引用。
  1.   __weak typeof(self) weakSelf = self;
  2.   self.block = ^(){      NSLog(@"%@", weakSelf.name);  } ;  self.block();
复制代码
此时self持有block,block弱引用self,弱引用会自动变为nil,强持有中断,所以不会引起循环引用。
之前学习GCD的时间将其他线程麻烦的操作实行完之后回到主线程,如果在实行其他线程的时ViewController被烧毁,就会导致内部的函数来不及打印,导致想打印的数值为空。
  1.   。。。。。。。
  2.         __weak typeof(self) weakSelf = self;
  3.     self.block = ^(){
  4.     // 延迟2秒钟
  5.         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  6.             NSLog(@"%@", weakSelf.name);
  7.         });
  8.     };
  9.     self.block();
  10.     [self fakeDealloc];
  11. }
  12. - (void)fakeDealloc {
  13.     NSLog(@"调用了dealloc 模拟ViewController模拟销毁");
  14.     // 模拟viewController被销毁
  15.     self.name = nil;
  16. }
复制代码
运行结果:

1. 强弱共舞

为了办理上面的题目,由此引出了强弱共舞
  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @property (nonatomic, strong) NSString *name;
  4. @property (nonatomic, strong) void (^block)(void);
  5. @end
  6. @implementation ViewController
  7. - (void)viewDidLoad {
  8.    [super viewDidLoad];
  9.    // Do any additional setup after loading the view.
  10.    
  11.    self.name = @"View";
  12.    __weak typeof (self) weakself = self;
  13.    self.block = ^{
  14.        __strong __typeof(weakself) strongself = weakself;
  15.        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  16.                NSLog(@"Block executed after 2 seconds. Name: %@", strongself.name);
  17.        });
  18.    };
  19.    self.block();
  20.    [self dealloc1];
  21. }
  22. - (void)dealloc1 {
  23.    NSLog(@"Object is deallocating.");
  24. }
  25. @end
复制代码
则统统就会正常打印。
由于__weak会自动置为nil,所以这里使用__strong(strong-weak-dance)临时延伸 self的生命周期,使得可以正常打印。
为什么强弱共舞能够制止循环引用,不是也调用了self? 由于这里strongself是一个临时的变量,出了作用域也跟着释放了,所以不会出现循环引用

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

鼠扑

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表