iOS的Block详解
Block
是带有自动变量的匿名函数,是C语言的一个扩充功能。Block
本质上也是一个OC对象,内部也有一个isa指针,其内部封装了函数调用以及函数调用环境。
(一)Block的基本使用
1. block声明
//返回值(^block变量名)(参数)
void(^block)();
2. block定义
//三种方式 = ^(参数){};
// 第一种:没返回值,没参数
void(^block1)() = ^{
NSLog(@"调用了block1");
};
// 第二种:没返回值,有参数
//如果没有参数,参数可以隐藏,如果有参数,定义的时候,必须要写参数,而且必须要有参数变量名
void(^block2)(int) = ^(int a){
};
// 第三种 有返回值
//block返回可以省略,不管有没有返回值,都可以省略
int(^block3)() = ^int{
return 3;
};
3. block类型
// block类型:int(^)(NSString *)
int(^block4)(NSString *) = ^(NSString *name){
return 2;
};
4. block调用
block1();
// block快捷方式 inline
// <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
// <#statements#>
// };
(二)Block的基本使用
1. 保存代码到模型中
CellItem.h
#import <Foundation/Foundation.h>
@interface CellItem : NSObject
// 设计模型:控件需要展示什么内容,就定义什么属性
@property (nonatomic, strong) NSString *title;
// 保存每个cell做的事情
@property (nonatomic, strong) void(^block)();
+ (instancetype)itemWithTitle:(NSString *)title;
@end
CellItem.m
#import "CellItem.h"
@implementation CellItem
+ (instancetype)itemWithTitle:(NSString *)title
{
CellItem *item = [[self alloc] init];
item.title = title;
return item;
}
@end
TableViewController.m
#import "TableViewController.h"
#import "CellItem.h"
// 1.tableView展示3个cell,打电话,发短信,发邮件
@interface TableViewController ()
/** 注释 */
@property (nonatomic, strong) NSArray *items;
@end
@implementation TableViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建模型
CellItem *item1 = [CellItem itemWithTitle:@"打电话"];
item1.block = ^{
NSLog(@"打电话");
};
CellItem *item2 = [CellItem itemWithTitle:@"发短信"];
item2.block = ^{
NSLog(@"发短信");
};
CellItem *item3 = [CellItem itemWithTitle:@"发邮件"];
item3.block = ^{
NSLog(@"发邮件");
};
_items = @[item1,item2,item3];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"cell";
// 1.从缓存池取
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
}
CellItem *item = self.items[indexPath.row];
cell.textLabel.text = item.title;
return cell;
}
// 点击cell就会调用
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 把要做的事情(代码)保存到模型
CellItem *item = self.items[indexPath.row];
if (item.block) {
item.block();
}
}
@end
2. block传值
传值:只要能拿到对方就能传值
顺传:给需要传值的对象,直接定义属性就能传值
逆传:用代理,block,就是利用block去代替代理
先用代理进行传值:
ModalViewController.h
#import <UIKit/UIKit.h>
@class ModalViewController;
@protocol ModalViewControllerDelegate <NSObject>
@optional
// 设计方法:想要代理做什么事情
- (void)modalViewController:(ModalViewController *)modalVc sendValue:(NSString *)value;
@end
@interface ModalViewController : UIViewController
@property (nonatomic, weak) id<ModalViewControllerDelegate> delegate;
@end
ModalViewController.m
#import "ModalViewController.h"
@interface ModalViewController ()
@end
@implementation ModalViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 传值给ViewController
if ([_delegate respondsToSelector:@selector(modalViewController:sendValue:)]) {
[_delegate modalViewController:self sendValue:@"123"];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
ViewController.m
#import "ViewController.h"
#import "ModalViewController.h"
/*
传值:1.只要能拿到对方就能传值
顺传:给需要传值的对象,直接定义属性就能传值
逆传:用代理,block,就是利用block去代替代理
*/
@interface ViewController ()<ModalViewControllerDelegate>
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
ModalViewController *modalVc = [[ModalViewController alloc] init];
modalVc.view.backgroundColor = [UIColor brownColor];
modalVc.delegate = self;
// 跳转
[self presentViewController:modalVc animated:YES completion:nil];
}
#pragma mark - ModalViewControllerDelegate
// 传值给ViewController
- (void)modalViewController:(ModalViewController *)modalVc sendValue:(NSString *)value
{
NSLog(@"%@",value);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
在用block进行传值:
ModalViewController.h
#import <UIKit/UIKit.h>
@interface ModalViewController : UIViewController
@property (nonatomic, strong) void(^block)(NSString *value);
@end
ModalViewController.m
#import "ModalViewController.h"
@interface ModalViewController ()
@end
@implementation ModalViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (_block) {
_block(@"123");
}
}
@end
ViewController.m
#import "ViewController.h"
#import "ModalViewController.h"
/*
传值:1.只要能拿到对方就能传值
顺传:给需要传值的对象,直接定义属性就能传值
逆传:用代理,block,就是利用block去代替代理
*/
@interface ViewController ()
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
ModalViewController *modalVc = [[ModalViewController alloc] init];
modalVc.view.backgroundColor = [UIColor brownColor];
modalVc.block = ^(NSString *value) {
NSLog(@"%@",value);
};
// 跳转
[self presentViewController:modalVc animated:YES completion:nil];
}
@end
(三)Block的内存管理
block是不是一个对象?是一个对象
如何判断当前文件是MRC,还是ARC
1.dealloc 能否调用super,只有MRC才能调用super
2.能否使用retain,release.如果能用就是MRC
ARC管理原则:只要一个对象没有被强指针修饰就会被销毁,默认局部变量对象都是强指针,存放到堆里面
MRC了解开发常识:
1.MRC没有strong,weak,局部变量对象就是相当于基本数据类型
2.MRC给成员属性赋值,一定要使用set方法,不能直接访问下划线成员属性赋值
MRC:管理block
- 总结:只要block没有引用外部局部变量,block放在全局区
- 只要Block引用外部局部变量,block放在栈里面.
- block只能使用copy,不能使用retain,使用retain,block还是在栈里面, 当方法结束时, 大括号内的在栈里的block会销毁, 再引用block就会报错。
- MRC环境下,在定义block为属性时,使用copy的原因,是把block从栈区拷贝到堆区(深拷贝),因为栈区中的变量出了作用域之后就会被销毁,无法在全局使用,所以应该把栈区的属性拷贝到堆区中全局共享,这样就不会被销毁了。
- 浅拷贝:浅拷贝并不拷贝对象本身,只是对指向对象的指针进行拷贝
深拷贝:直接拷贝对象到内存中一块区域,然后把新对象的指针指向这块内存
#import "ViewController.h"
/*
block是不是一个对象?是一个对象
如何判断当前文件是MRC,还是ARC
1.dealloc 能否调用super,只有MRC才能调用super
2.能否使用retain,release.如果能用就是MRC
ARC管理原则:只要一个对象没有被强指针修饰就会被销毁,默认局部变量对象都是强指针,存放到堆里面
MRC了解开发常识:1.MRC没有strong,weak,局部变量对象就是相当于基本数据类型
2.MRC给成员属性赋值,一定要使用set方法,不能直接访问下划线成员属性赋值
MRC:管理block
总结:只要block没有引用外部局部变量,block放在全局区
只要Block引用外部局部变量,block放在栈里面.
block只能使用copy,不能使用retain,使用retain,block还是在栈里面
*/
@interface ViewController ()
@property (nonatomic, copy) void(^block)();
@property (nonatomic, retain) NSString *name;
@end
@implementation ViewController
- (void)setName:(NSString *)name
{
if (name != _name) {
[_name release];
_name = [name retain];
}
}
- (void)dealloc
{
[self.name release];
[super dealloc];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 堆 栈 全局区
__block int a = 3;
void(^block)() = ^{
NSLog(@"调用block%d",a);
};
self.block = block;
NSLog(@"%@",self.block);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.block();
}
@end
ARC:管理block
- 总结:只要block没有引用外部局部变量,block放在全局区
- 只要block引用外部局部变量,block放在堆里面
- block使用strong.最好不要使用copy, 使用copy也没问题
- 在MRC下必须使用self.task = block;给属性赋值,在赋值的时候会调用setter方法,会把栈区的block拷贝到堆区,如果使用_task的方式赋值不会去copy,所以在MRC下属性都用copy修饰
- 在ARC下可以使用_task,因为ARC下默认属性就是在堆区, MRC不可以使用_task.
@interface ViewController ()
//可以使用strong和copy
@property (nonatomic, strong) void(^block)();
//@property (nonatomic, copy) void(^block)();
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 堆 栈 全局区
int a = 3;
void(^block)() = ^{
NSLog(@"%d",a);
};
_block = block;
NSLog(@"%@",_block);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
_block();
}
@end
(四)__block的使用
1. 在block的内部,访问外部的变量时,block内部会对外部的变量进行一次拷贝,在block内部操作的是拷贝之后的副本,不会影响外部的变量,这个变量在堆区
2. 在block内部,修改外部变量,是不被允许的
3. 如果非要在block内部修改外部的变量,需要使用__block修饰外部变量
4. 一旦外部的int变量(在栈区)被__block标记了,如果block内部又修改了这个变量,那么这个变量的地址会永久的被修改在堆区
5. 如果外部变量是NSMutableString这样本身就在堆区的,在block内部修改就不会报错
5. 为什么在block的内部不能修改外部的变量? 因为block一般是需要传递给另外一个类里面,block内部的一些变量不能存储在栈区,需要存在堆区,不然数据就容易丢失,这就是使用__block修饰的原因,这样传输数据的时候,数据就不会丢失
(五)Block的循环引用
循环引用:我引用你,你也引用,就会造成循环引用,双方都不会被销毁,导致内存泄露问题
block造成循环利用:Block会对里面所有强指针变量都强引用一次
//会报错 循环引用的错误
_block = ^{
NSLog(@"%@",self);
};
_block();
循环引用原因图解:
解决循环引用方法:
用__weak typeof修饰self
__weak typeof(self) weakSelf = self;
_block = ^{
NSLog(@"%@",weakSelf);
};
_block();
比较复杂的情况,比如block中有使用block,在调用self的时候又有延迟操作:
错误情况:延迟输出的waekSelf已经提前销毁,输出的为null
__weak typeof(self) weakSelf = self;
_block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//输出的weakSelf为null
NSLog(@"%@", weakSelf);
});
};
_block();
正确的修改方式: 在内部在用__strong typeof修饰weakSelf
__weak typeof(self) weakSelf = self;
_block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//此时输出不为null
NSLog(@"%@",strongSelf);
});
};
_block();
正确的图解:
(六)Block的变量传递
如果是局部变量,Block是值传递
int a = 3;
// 如果是局部变量,Block是值传递
void(^block)() = ^{
//此时输出的a为3
NSLog(@"%d",a);
};
a = 5;
block();
如果是静态变量,全局变量,__block修饰的变量,block都是指针传递。
static int a = 3;
//__block int a = 3;
// 如果是局部变量,Block是值传递
// 如果是静态变量,全局变量,__block修饰的变量,block都是指针传递
void(^block)() = ^{
//此时a为5
NSLog(@"%d",a);
};
a = 5;
block();
(七)Block的变开发使用场景
- 怎么区分参数是block,就看有没有^,只要有^.把block当做参数
- 把block当做参数,并不是马上就调用Block,什么时候调用,由方法内部决定
- 什么时候需要把block当做参数去使用:做的事情由外界决定,但是什么时候做由内部决定.
1. 设计一个计算器:
CacultorManager.h
#import <Foundation/Foundation.h>
@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;
// 计算
- (void)cacultor:(NSInteger(^)(NSInteger result))cacultorBlock;
@end
CacultorManager.m
#import "CacultorManager.h"
@implementation CacultorManager
- (void)cacultor:(NSInteger (^)(NSInteger))cacultorBlock
{
if (cacultorBlock) {
_result = cacultorBlock(_result);
}
}
@end
ViewController.m
#import "ViewController.h"
#import "CacultorManager.h"
// 怎么区分参数是block,就看有没有^,只要有^.把block当做参数
// 把block当做参数,并不是马上就调用Block,什么时候调用,由方法内部决定
// 什么时候需要把block当做参数去使用:做的事情由外界决定,但是什么时候做由内部决定.
/*
需求:封装一个计算器,提供一个计算方法,怎么计算由外界决定,什么时候计算由内部决定.
*/
@interface ViewController ()
@end
@implementation ViewController
- (void)test:(int)a
{
}
- (void)viewDidLoad {
[super viewDidLoad];
[self test:3];
// 创建计算器管理者
CacultorManager *mgr = [[CacultorManager alloc] init];
[mgr cacultor:^(NSInteger result){
result += 5;
result += 6;
result *= 2;
return result;
}];
NSLog(@"%ld",mgr.result);
// [UIView animateWithDuration:0 animations:^{
//
// }];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
2. Block做返回值
链式编程思想:把所有的语句用.号连接起来,好处:可读性非常好
CalculatorManager.h
#import <Foundation/Foundation.h>
@interface CalculatorManager : NSObject
@property (nonatomic, assign) int result;
//- (CalculatorManager *)add:(int)value;
- (CalculatorManager *(^)(int))add;
@end
CalculatorManager.m
#import "CalculatorManager.h"
@implementation CalculatorManager
- (CalculatorManager *(^)(int))add
{
return ^(int value){
_result += value;
return self;
};
}
- (CalculatorManager *)add:(int)value
{
_result += value;
return self;
}
@end
ViewController.m
#import "ViewController.h"
#import "CalculatorManager.h"
@interface ViewController ()
@end
@implementation ViewController
/*
链式编程思想:把所有的语句用.号连接起来,好处:可读性非常好
*/
/*
需求:封装一个计算器,提供一个加号方法
*/
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CalculatorManager *mgr = [[CalculatorManager alloc] init];
// [[[[[mgr add:5] add:5] add:5] add:6] add:7];
mgr.add(5).add(5).add(5).add(5);
NSLog(@"%d",mgr.result);
// void(^block)() = ^{
// NSLog(@"调用了block");
// }();//会直接调用block
self.test();
}
- (void(^)())test
{
return ^{
NSLog(@"调用了block");
};
}
@end