Block的使用--页面传值方法及探究

时间:2022-02-02 18:19:34
上一篇讲了Block的简单实用,这一篇则是讲述Block的最经常也是比较简单的用法,个人认为这是block相对其他页面传值方式比较好的一个用处。也顺便总结下这两天对Block的理解的一些注意点。
对于页面传值这种情况,我们一般在程序里有几种处理的方法。
1、设置Delegate
2、使用NotificationCenter
3、使用Block
4、KVO页面传值
5、NSUserDefault等文件存储机制做中间介质传递(但比较浪费,用在页面传值上几率很低)
这一篇博客我只讲一下Block的一般页面传值的使用方法还有探究下其原理。话不多说直接上代码:

方法以及使用

///第一个页面中:
#import "ViewController.h"
#import "NextVC.h"

@interface ViewController ()<NextVCDelegate>{
UILabel *_useBlockText;//显示文字
UIButton *_toNextVC_Block;//进入下个页面
}
@end

///将按钮与显示label放到view上,这里省略。具体可到末尾看我Demo里的源码

/**
* @brief 按钮事件:进入下个页面,指定Block回调函数
*/

- (void)toNext_Block {

NextVC *next = [[NextVC alloc]init];
// 指定回调函数
next.NextVCBlock = ^(NSString *text){
_useBlockText.text = [NSString stringWithFormat:@"I'm from block : %@", text];
};

[self.navigationController pushViewController:next animated:YES];
}
//第二个页面
//.h头文件
@interface NextVC : UIViewController
@property (nonatomic, copy) void(^NextVCBlock)(NSString *text);
@end

//.m实现文件
#import "NextVC.h"

@interface NextVC ()
{
UIButton *_useBlock;
UITextField *_text;
}

///将按钮与显示label放到view上,这里省略。具体可到末尾看我Demo里的源码

/**
* @brief 使用Block进行页面传值
*/

- (void)tranferUseBlock {
//传值部分
if (self.NextVCBlock)
self.NextVCBlock(_text.text);

[self.navigationController popViewControllerAnimated:YES];
}
@end

这里效果大致也就如下:
Block的使用--页面传值方法及探究

传递流程探究

在一个类中定义一个Block,在另一个类怎么得到消息改变的呢。

流程分析:通过打断点以及尝试总算明白了block在页面间传值的一个流程。
我们在第二个页面中声明了一个block类型的属性NextVCBlock:
@property (nonatomic, copy) void(^NextVCBlock)(NSString *text);

而我们在第一个页面实例化了NextVC的对象:
NextVC *next = [[NextVC alloc]init];
并且实现了这个NextVCBlock:
next.NextVCBlock = ^(NSString *text){
_useBlockText.text = ...;
};

当我们在第二个页面中调用了该Block:
self.NextVCBlock(_text.text);
这会自动把Block复制到堆上,然后触发了next.NextVCBlock内部实现代码。

外部变量的使用权限问题

为了优化存储,Block变量与Block本身一开始都是存储于栈上的,跟局部变量类似。在函数体中定义的局部变量通常都是存储在栈内,全局变量/静态变量一般放在堆上。block在引用外部变量时可以是readonly。只能读不能改,如果你想修改局部变量,则应该使用__block对该变量进行修饰。比如 __block int sum = 10;

原因分析:
这是因为block事实上是指向一个结构体的指针,其内部的代码会被声称一个函数。
内部代码在使用外部的变量sum时,传递的只是一个值,而当使用了__block修饰后,传递过来的则是变脸sum的地址。

循环引用的问题(Memory recycle)

比如我们刚刚上面的- (void)toNext_Block 这个函数中,如果我们加入一句NSLog(@"%@",next.description);,也就是:

    NextVC *next = [[NextVC alloc]init];

next.NextVCBlock = ^(NSString *text){
_useBlockText.text = [NSString stringWithFormat:@"I'm from block : %@", text];
NSLog(@"%@",next.description);
};

这种情况就会产生循环引用

原因分析:
因为当这个函数生命周期结束的时候,系统想释放next,但释放next首先要释放NextVCBlock这个变量.
而我们在NextVCBlock中又强引用了next这个对象。
主要原因是所有局部变量在默认情况下的修饰符都为__strong。

解决方法:
你以为这里只需要在NextVC前加__weak即可解决吗?事实却不是如此,因为如果我们使用:
__weak NextVC *next = [[NextVC alloc]init];
next一声明马上就被系统自动回收了,所以后面的使用是不执行的。最好的解决方法应该是:
NextVC *next = [[NextVC alloc]init];
__weak typeof(NextVC) *weakNext = next;
然后使用weakNext代替next:
weakNext.NextVCBlock = ^(NSString *text){
_useBlockText.text = [NSString stringWithFormat:@"I'm from block : %@", text];
NSLog(@"I'm weakNext == %@",weakNext.description);
};

在ARC环境下,以下几种情况Block会自动从栈复制到堆上:

(来源于网上一个帖子所说)
1、被执行copy方法
2、作为方法的返回值
3、将Block赋值给由__strong修饰符的id对象或者Block类型的成员变量
4、在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中传递的时候(这一点我也不是很理解)

这里有个知识点可以扩展下:
栈的优点是创建速度快、管理简单。有严格的生命周期。
因为是先进后出队列,所以效率高也不会有碎片问题。由系统释放。

堆是动态分配的,可由程序员自行控制释放。
但是是根据指针指向一串不连续的内存,空间比较大但频繁的new/release肯定会造成内存空间的更加不连续,即内存碎片化。

效率上:
堆:char *s1 = "Hellow Word";是在编译时就确定的;
栈:char s1[] = "Hellow Word"; 是在运行时赋值的;
再比如NSString与NSMutableString的对象,前者是放在栈中,后者是放在堆上。创建方法也不同,比如
NSString *str = @"hello";
NSMutableString *mutableStr = [[NSMutableString alloc] initWithString:@"hello"];
前者是直接在栈上创建,后者是需要分配空间再进行初始化。

Demo地址:https://github.com/ChenNan-FRAM/TransferDataDemo