Block从简单到高级的使用,以及项目中经常使用的场景
一、Block的简单使用
使用Block的三个步骤:1.定义Block变量;2.定义Block(即创建block代码块)3.调用block匿名函数;
1>以下是使用的两个简单的例子:
2>定义Block变量的时候一般大家都是用typedef重定义,应用起来既方便看起来又很直观,例如上面两个例子可以如下书写:
3>Block变量在ARC和MRC环境下的生命周期
分析原因如下:
//1.分析,ARC如果在块对象中使用了__block指定的变量,那么这个变量将会被copy到堆内存中,并且原变量也会指向这个堆内存中的空间
//2.如果有两个块对象引用了同一个__block指定的变量,那么他们共享这个变量,共享同一个内存
MRC环境下打印结果如下:
//1.函数内被__block修饰的变量可以被块对象读写,多个块对象之间可以共享__block变量的值
//2.__block变量不是静态变量,在块句法每次用到这种变量的时候会去相应的内存空间获取值,就是说不同块对象分享的__block变量的值是执行时动态生成
//3.访问__block变量的块对象被复制后,新生成的块对象也能共享__block变量的值
//4.多个块对象访问同一个__block变量的时候,只要有一个块对象存在,__block变量就会存在;如果访问__block变量的对象都不存在了,__block对象随之消失
//总结:
//注意:因为__block变量的内存位置可能会发生变化,所以,写程序时候不要写用指针访问__block变量的代码,那样可能会得不到预期的结果(这是在项目中尤为重要的一点)
二、iOS开发中,项目中使用Block
1.Block作为属性
- @interface ViewController ()
- {
- ///名字按钮
- UIButton *_nameBtn;
- }
- @end
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- _nameBtn = [UIFactory buttonWithTitle:@"跑男" withRect:CGRectMake(50, 100, 100, 50) withBackgroundColor:[UIColor redColor]];
- [_nameBtn addTarget:self action:@selector(run:) forControlEvents:UIControlEventTouchUpInside];
- [self.view addSubview:_nameBtn];
- }
- -(void)run:(UIButton *)sender
- {
- [self pushNextVCWithBlock:^(NSString *name) {
- [_nameBtn setTitle:name forState:UIControlStateNormal];
- }];
- NSLog(@"第一次");
- }
- -(void)pushNextVCWithBlock:(testBlock)testBlock
- {
- RunViewController *runVC = [[RunViewController alloc]init];
- //block作为属性传给下一个界面
- runVC.testBlock = testBlock;
- [self.navigationController pushViewController:runVC animated:YES];
- }
- @interface RunViewController : UIViewController
- ///Block作为属性<pre name="code" class="objc">@property(nonatomic,strong)testBlock testBlock;
- </pre><pre name="code" class="objc"><pre name="code" class="objc">@end
- #import "RunViewController.h"
- @interface RunViewController ()
- {
- ///返回按钮
- UIButton * _backBtn;
- }
- @end
- @implementation RunViewController
- -(void)viewDidLoad
- {
- [super viewDidLoad];
- _backBtn = [UIFactory buttonWithTitle:@"美女" withRect:CGRectMake(50, 100, 100, 50) withBackgroundColor:[UIColor redColor]];
- [_backBtn addTarget:self action:@selector(backToRun:) forControlEvents:UIControlEventTouchUpInside];
- [self.view addSubview:_backBtn];
- }
- -(void)backToRun:(UIButton *)sender
- {
- self.testBlock(_backBtn.titleLabel.text);
- [self.navigationController popViewControllerAnimated:YES];
- }
最后得到的效果就是:第一个界面的按钮的title改变成了第二个界面的title的内容。。。。(也就是页面传值,在实际的项目中例如:有一个菜单是选择很多人的信息,我在这个菜单中选择了三个人,在点击保存的时候是需要将这三个人的数据返回到上个界面,此时应用Block操作简单快捷,例如这样的需求很多很多)
2.Block作为方法参数
在Block作为属性那个知识点中,仍然使用那两个controller去解释这块内容。
在ViewController中具体代码如下:
3.Block代替代理
同时使用Block和代理的情景如下:(请求框架中的内部实现)
之前一个项目中使用第一个版本请求使用的是代理模式,版本更新之后使用的是Block
这个书写方法如果页面请求过多,在ARC环境下,会造成delegate野指针崩溃的问题,所以在下个版本改进的时候使用block代替协议
- __weak __typeof(self)weakSelf = self;
- AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
- __strong __typeof(weakSelf)strongSelf = weakSelf;
- strongSelf.networkReachabilityStatus = status;
- if (strongSelf.networkReachabilityStatusBlock) {
- strongSelf.networkReachabilityStatusBlock(status);
- }
- };
Review一下上面这段代码,里面玄机不少。
第一行:__weak __typeof(self)weakSelf = self;
如之前第四条所说,为防止callback内部对self强引用,weak一下。
其中用到了__typeof(self),这里涉及几个知识点:
a. __typeof、__typeof__、typeof的区别
恩~~他们没有区别,但是这牵扯一段往事,在早期C语言中没有typeof这个关键字,__typeof、__typeof__是在C语言的扩展关键字的时候出现的。
typeof是现代GNU C++的关键字,从Objective-C的根源说,他其实来自于C语言,所以AFNetworking使用了继承自C的关键字。
b.对于老的LLVM编译器上面这句话会编译报错,所以在很早的ARC使用者中流行__typeof(&*self)这种写法,原因如下
大致说法是老LLVM编译器会将__typeof转义为 XXX类名 *const __strong的__strong和前面的__weak关键字对指针的修饰又冲突了,所以加上&*对指针的修饰。
第三行:__strong __typeof(weakSelf)strongSelf = weakSelf;
按照之前第五条的说法给转回strong了,这里__typeof()里面写的是weakSelf,里面写self也没有问题,因为typeof是编译时确定变量类型,所以这里写self 不会被循环引用。
第四、五、六行,如果不转成strongSelf而使用weakSelf,后面几句话中,有可能在第四句执行之后self的对象可能被析构掉,然后后面的StausBlock没有执行,导致逻辑错误。
最后第五行,使用前对block判空。
声明一个block变量一般是
- @property(nonatomic,strong)testBlock testBlock;
使用注意事项:
1)在块内改变外部变量的值时候,在外部变量前加__block,否则该值在block块内部是只读的。
2)在引用某个实例变量或者所在控制器本身时候,在ARC下,要再前面加__weak如:__weak (typeof(self) weak self = self), 在mrc下用__block, 这样做是为了避免内存泄露和循环引用。
3)在使用block前需要对block指针做判空处理,如果是MRC的编译环境下,要先release掉block对象。
4)在MRC的编译环境下,block如果作为成员参数要copy一下将栈上的block拷贝到堆上(因为block默认是在栈上创建的,如果在定义block的作用于外部使用block那么需要使用copy将block放到堆上)//MRC下:_sucBlock = [callbackBlock copy]; 不copy block会在栈上被回收。
5)将block赋值为空,是解掉循环引用的重要方法。
6)还有一种改法,在block接口设计时,将可能需要的变量作为形参传到block中,从设计上解决循环引用的问题。
7)在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。