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

来源:互联网 发布:通甲优博 知乎 编辑:程序博客网 时间:2024/06/12 20:45
上一篇讲了Block的简单实用,这一篇则是讲述Block的最经常也是比较简单的用法,个人认为这是block相对其他页面传值方式比较好的一个用处。也顺便总结下这两天对Block的理解的一些注意点。
对于页面传值这种情况,我们一般在程序里有几种处理的方法。1、设置Delegate2、使用NotificationCenter3、使用Block4、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类型的属性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

0 0