您的位置:首页 > 其它

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

2015-07-27 18:02 141 查看
上一篇讲了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类型的属性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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: