本篇文章的主要内容
-
了解何谓block。
-
了解block的使用方法。
Block 是iOS在4.0版本之后新增的程序语法.在iOS SDK 4.0之后,Block几乎出现在所有新版的API之中,换句话说,如果不了解Block这个概念就无法使用SDK 4.0版本以后的新功能,因此虽然Block本身的语法有点难度,但为了使用iOS的新功能我们还是得硬着头皮去了解这个新的程序概念。
一、看一看什么是Block
我们使用^
运算符来声明一个Block变量,而且在声明完一个Block变量后要像声明普通变量一样,最后要加;
。
- 声明Block变量
接下来,我们通过一个例子来声明一个简单的Block变量.
int (^block)( int ) = NULL;
我们一起来看一下声明Block变量的语法
数据返回值类型 (^变量名) (参数列表) = NULL;
- 赋值Block变量
block = ^(int m){
return m * m;
};
- 使用Block变量
//通过使用block变量,计算整型常量10的平方,并且打印在控制器输出
NSLog(@"10的平方是:%d",block(10));
注意,到目前我们应该有发现block变量的使用步骤,有类似于函数的步骤
- 首先都要声明(声明函数,声明block变量);
- 然后都要进行实现(实现函数,为block变量赋值实现过程);
- 最后都要进行调用才能实现具体功能
二、看一看如何直接使用block参数
- 数组排序
//声明数组变量
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:@"5",@"2",@"3",@"9",@"7", nil];
//直接使用block进行数组升序排序
[mutableArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
//将两个参数转换为字符串的对象
NSString *value1 = (NSString *)obj1;
NSString *value2 = (NSString *)obj2;
//value1与value2两个对象的比较结果直接返回
return [value1 compare:value2];
}];
//打印可变数组变量
NSLog(@"%@",mutableArray);
- 简单的网络异步请求
//声明网络地址对象
NSURL *url = [NSURL URLWithString:@"http://www.qq.com"];
//根据网络地址对象,声明网络请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//直接使用block变量完成链接成功后的数据返回功能
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
//将二进制数据使用utf8编码转换成字符串对象
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//打印链接完成后的结果
NSLog(@"%@",result);
}];
可以看出ios平台中的很多功能都已经集成了Block语法的处理方法.
三、看一看深入理解Block语法
在本节,主要要去介绍的就是使用__block
修饰的变量能够完成的作用。
先来看一个例子。
//声明一个局部整型变量
int intValue = 3;
//声明一个返回值为int,一个int参数的block变量
int (^block)(int) = ^(int m){
return m * intValue;
};
//调用block变量,5作为参数之后的结果
NSLog(@"block(5) = %d",block(5));
在上面的例子中,我们将intValue
变量称为block
执行过程中的外部变量,在block
执行过程中可以直接使用该外部变量。
再看一个例子。
//声明一个局部整型变量
int intValue = 3;
//声明一个返回值为int,一个int参数的block变量
int (^block)(int) = ^(int m){
intValue++;
return m * intValue;
};
//调用block变量,5作为参数之后的结果
NSLog(@"block(5) = %d",block(5));
在上面的例子中,我们编译程序后发现编译器会有红色错误,错误提示为
Variable is not assignable (missing __block type specifier)
为什么会出现不能被赋值的错误提示呢?
-
block
在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝 - 在 block 块内使用该只读拷贝。
那为了避免上述错误,就要使用__block
修饰符来修饰外部变量,用来通知编译器该外部变量intValue
与block
中的intValue
指的是同一块儿内存地址,而不需要内存拷贝。
如下例:
//将intValue局部整型变量使用__block修饰符进行修饰
__block int intValue = 3;
//声明一个返回值为int,一个int参数的block变量
int (^block)(int) = ^(int m){
intValue++;
return m * intValue;
};
//调用block变量,5作为参数之后的结果
NSLog(@"block(5) = %d",block(5));
四、使用Block要注意的内存问题
使用weak–strong dance
技术来避免循环引用
举例如下
//
// ViewController.m
//
// Created by lewis.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
{
id observer;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//添加观察者,观察主题修改消息通知,并且在收到消息通知后,打印视图控制器对象
observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSLog(@"%@",self);
}];
}
//当视图控制器对象销毁时,移除观察者
- (void)dealloc
{
if (observer) {
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}
}
@end
在上面代码中,我们添加向通知中心注册了一个观察者,然后在 dealloc 时解除该注册,一切看起来正常。但这里有两个问题:
在消息通知 block
中引用到了 self
,在这里 self
对象被 block
保留一次,而 observer
又 retain 该 block
的一份拷贝,通知中心又持有 observer
。因此只要 observer
对象还没有被解除注册,block
就会一直被通知中心持有,从而 self
就不会被释放,其 dealloc
就不会被调用。而我们却又期望在 dealloc 中通过 removeObserver
来解除注册以消除通知中心对 observer/block
的 保留次数。
同时,observer
是在 self
所在类中定义赋值,因此是被 self retain
的,这样就形成了循环引用。
解决方式如下.
//
// ViewController.m
//
// Created by lewis.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
{
id observer;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//先声明一个weak弱对象
__weak ViewController *wSelf = self;
//添加观察者,观察主题修改消息通知,并且在收到消息通知后,打印视图控制器对象
observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"kThemeChangeNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note){
//在block的执行过程中,使用强对象对弱对象进行引用
ViewController *bSelf = wSelf;
if (bSelf) {
NSLog(@"%@",bSelf);
}
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//当视图控制器对象销毁时,移除观察者
- (void)dealloc
{
if (observer) {
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}
}
@end
- 在
block
之前定义对self
的一个弱引用wSelf
,因为是弱引用,所以当self
被释放时wSelf
会变为nil
; - 在
block
中引用该弱应用,考虑到多线程情况,通过使用强引用bSelf
来引用该弱引用,这时如果self
不为nil
就会retain self
,以防止在后面的使用过程中 self 被释放; - 在之后的
block
块中使用该强引用bself
,注意在使用前要对bSelf
进行了nil
检测,因为多线程环境下在用弱引用wSelf
对强引用bSelf
赋值时,弱引用wSelf
可能已经为nil
了。
通过这种weak-strong
手法,block
就不会持有 sel
f 的引用,从而打破了循环引用。
网友评论
ViewController *bSelf = wSelf;
请问 if (bSelf) {
NSLog(@"%@",bSelf);
}
为什么要加上判断,strong 不是会再block执行完后才释放么?
你理解的情况是在执行block时,wSelf还没有变为nil,那么确实在block执行完后才会导致self的释放。
可以把判断放到block开始处,这样好理解一些。
其实加不加__strong修饰逻辑上都说得通
```
^(MASConstraintMaker *make) {
make.left.top.mas_equalTo(0);
make.width.equalTo(self.canvasScrollView.mas_width);
make.height.mas_equalTo(scrollviewDefaultContentHeight);
}
```
并没有被任何对象持有,Block变量指针指向的栈区地址,变量被调用后就会释放掉,所以不存在循环持有的问题。
但是如果有以下代码
```
typedef void(^CanvasConstraintBlock)(MASConstraintMaker *make);
@property (nonatomic,string) CanvasConstraintBlock ccb;
self.ccb = ^(MASConstraintMaker *make) {
make.left.top.mas_equalTo(0);
make.width.equalTo(self.canvasScrollView.mas_width);
make.height.mas_equalTo(scrollviewDefaultContentHeight);
}];
```
上述代码出现的问题就是,循环持有的问题了。
赋值时,需如下:
```
__weak VC wSelf = self;
elf.ccb = ^(MASConstraintMaker *make) {
//预防线程bug
VC bSelf = wSelf;
make.left.top.mas_equalTo(0);
make.width.equalTo(bSelf.canvasScrollView.mas_width);
make.height.mas_equalTo(scrollviewDefaultContentHeight);
}];
```
[self.canvasContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.mas_equalTo(0);
make.width.equalTo(self.canvasScrollView.mas_width);
make.height.mas_equalTo(scrollviewDefaultContentHeight);
}];
在masonry的约束block中 使用了self 这时候需要使用__block 修饰吗?
我一般是没有进行修饰的 运行起来 也没啥问题 delloc 也能执行到
不理解的是 masonry 约束block中的self 是masonry 帮我们做了weak修饰 还是本身就不需要weak 修饰
具体分析类似'四、使用Block要注意的内存问题'中的举例