前沿
最近在研究RAC,里面用到了很多的Block。然后想起来网络请求中也用到了很多,所以自己参考了很多相关内容,对Block进行了简单的总结和理解。
一.什么是Block
block就是可以截获局部变量的匿名函数。
上面为官方的语法说明。
Block变量的声明、赋值与调用
Block变量的声明
Block变量的声明格式为: 返回值类型(^Block名字)(参数列表);
// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);
// 形参变量名称可以省略,只留有变量类型即可void(^aBlock)(NSString *, NSString *);
Block变量的赋值
Block变量的赋值格式为: Block变量 = ^返回值类型(参数列表){函数体};
但是一般我们可以将返回值类型省略:Block变量 = ^(参数列表){函数体};
aBlock = ^(NSString *x, NSString *y){
NSLog(@"%@ love %@", x, y);
};
声明Block变量的同时进行赋值
int(^myBlock)(int) = ^(int num){
return num * 7;
};
// 如果没有参数列表,在赋值时参数列表可以省略
void(^aVoidBlock)() = ^{
NSLog(@"I am a aVoidBlock");
};
Block变量的调用
// 调用后控制台输出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei");
// 调用后控制台输出"result = 63"
NSLog(@"result = %d", myBlock(9));
// 调用后控制台输出"I am a aVoidBlock"
aVoidBlock();
注: Block的声明与赋值只是保存了一段代码段,必须调用才能执行内部代码
通常Block代码较长,所以为了方便我们可以使用Typedef来进行重命名。
typedef int (^MyBlock)(int, int);
MyBlock * addBlock=^(int x, int y){ return x + y; };
二.Block一般作为一下几个方法使用
- 作为类的属性出现
- 作为方法的参数出现
- 作为方法的返回值出现
- 作为全局变量出现
- 作为局部变量出现
结下来我写一下主要的用法
作为方法的参数出现(该用法比较常用)
// 1.定义一个形参为Block的OC函数
- (void)useBlockForOC:(int(^)(int, int))aBlock
{
NSLog(@"result = %d", aBlock(300,200));
}
// 2.声明并赋值定义一个Block变量
int(^addBlock)(int, int) = ^(int x, int y){
return x+y;
};
// 3.以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock];
// 将第2点和第3点合并一起,以内联定义的Block作为函数参数
[self useBlockForOC:^(int x, int y){
return x+y;
}];
三.在Block中访问外部变量(使用__block修饰符)
- (void)viewDidLoad {
[super viewDidLoad];
//创建一个firstName字符串,然后运行getFull的Block函数
NSString * firstName=@"Li";
NSString *(^getFullName)(NSString*)=^(NSString * lastname){
return [firstName stringByAppendingString:lastname];
};
NSString * fullName=getFullName(@"Guo");
NSLog(@"第一次:%@",fullName);
// 修改firstName的内容,然后重新运行getFull的Block函数
firstName=@"Wang";
fullName=getFullName(@"Hong");
NSLog(@"第二次:%@",fullName);
}
如果按照我们平时的理解,第二次运行应该结果应该为WangHong才对。
但是打印结果如下:
第一次:LiGuo
第二次:LiHong
这是因为外部变量firstName在block内部默认是只读的,不可改变。而且在Block中访问的外部变量会被直接复制进来,所以在Block中访问的是外部变量的副本。
如果我们想改变传入的外部变量值,我们可以在前面加一个__block
__block NSString * firstName=@"Li";
//这时打印结果为
第一次:LiGuo
第二次:LiHong
四.Block的内存分配
OC中Block是作为对象处理的,在ARC的管理之下。
一般来说,OC对象都是在堆中分配空间,但是Block除外。
Block可以在堆,栈,全局区分配空间。
刚刚定义好的Block分配在栈区。如果需要延长生命周期再次使用Block,就需要跑到堆区。在ARC模式下,栈中的Block会自动跑到堆区。在MRC下,我们需要手动的进行拷贝才能放入堆中。
如果将Block变量定义成全局变量并赋值一个Block时,此时Block在全局区分配空间。
Demo如下
static int(^maxInBlock)(int,int)=^(int x, int y){
return x>y?x:x;
};
//该Block在全局区分配空间
-(void)test{
int i=100;
int j=1;
void(^blk)(void);
void(^blkInHeap)(void);
blk=^(void){printf("%d,%d",i,j);};
// blk指向的Block在栈区
blkInHeap =[blk copy];
// blkInHeap指向的Block在堆区
面试题中可能会遇到这个问题。
如果我们对Block 进行copy调用,会发生什么。
1.对全局区的Block进行copy,会返回原指针,不做任何事情。
2.对栈中的Block进行copy,会将Block拷贝到堆中,并返回堆中的指针。同时_Block会被复制一份到堆中一份,多次copy只会拷贝一份。
3.对堆中copy,只会增加Block的引用计数器。
五.Block使用self
在我们使用Block的时候,当前对象会对Block进行强引用,当BLock内部使用self的时候,会导致Block会对当前对象强引用。最终导致内存引用循环,内存泄漏。
六.具体Block的Demo
下面是一个具体的Demo方便理解。
该demo为block反向传值。
我们在第一个界面设置一个能跳转到第二个界面的button,和显示label,第二个界面设置一个textfield。当我们在textfiedl输入内容后,第一个界面的label能显示出来相应内容。
第一个界面代码为
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
InputViewController* inputVc=segue.destinationViewController;
MyBlock block=^(NSString *showText){
self.showLable.text=showText; /*4*/
};
[inputVc returnText:block]; /*1*/
}
第二个界面代码为
#import
//将block属性进行重新命名
typedef void(^ReturnTextBlock)(NSString*showText);
@interface InputViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *inputTx;
//定义一个block属性
@property(nonatomic,copy)ReturnTextBlock returnTextBlock;
//声明一个block参数的方法
-(void)returnText:(ReturnTextBlock)block;
@end
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)returnText:(ReturnTextBlock)block{
self.returnTextBlock=block; /*2*/
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
self.returnTextBlock(self.inputTx.text); /*3*/
}
上面我依次写了代码的执行顺序1,2,3,4.
1.当跳转界面的时候执行以block为参数returnText方法。
2.将自己的 self.returnTextBlock属性以指针形式指向传入的参数block,这时候他们指向同一个block对象。
(我打印了下2个对象的地址,显示如下)
2016-09-09 17:24:29.852 a[33819:1426202] <__NSMallocBlock__: 0x7c185680>
2016-09-09 17:24:29.853 a[33819:1426202] <__NSMallocBlock__: 0x7c185680>
3.当第二个界面消失时候,调用self.returnTextBlock(self.inputTx.text)。
4.因为在步骤3的时候self.self.returnTextBlock进行了调用,那么此时4中代码部分就会以self.inputTx.text为参数进行执行。
该demo为常用的AFNetWorking框架
1、在写网络请求的时候我们经常会使用如下的代码:
[[AFHTTPSessionManager manager]POST:@"http://www.baidu.com" parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
//返回响应成功后执行的代码块1
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
//返回响应失败后执行的代码块2
}
];
2、 而AFN框架对于该函数的实现如下
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
3、 上述函数继续调用内部函数,把success和failure名字的block往下传递,直到如下函数,才执行这两个block:
(void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
dispatch_async(http_request_operation_processing_queue(), ^{
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
id responseObject = self.responseObject;
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
success(self, responseObject);
});
}
};
}
然后等待网络请求的回应失败或者成功就调用相应的block,然后执行代码块1或者代码块2,如下所示
上述代码中的如下两行代码,实现block的调用,并传入相应的函数
failure(self, self.error);
success(self, responseObject);
该demo为Block计算器(链式编程,Block作为属性)
首先定义一个计算器类,具体如下.
#import
@class Calculator;
typedef Calculator *(^CalculatorBlock)(double num);
@interface Calculator : NSObject
+(instancetype)operationNum:(double) num;
@property(assign,nonatomic)double operationNum;
@property(copy,nonatomic) CalculatorBlock add;
@property(copy,nonatomic) CalculatorBlock reduce;
@property(copy,nonatomic) CalculatorBlock mutiply;
@property(copy,nonatomic) CalculatorBlock divide;
@end
#import "Calculator.h"
@implementation Calculator
-(instancetype)init{
self=[super init];
if (self) {
[self buildBlock];
}
return self;
}
+(instancetype)operationNum:(double)num{
Calculator* calu=[[Calculator alloc]init];
calu.operationNum=num;
return calu;
}
-(void)buildBlock{
__weak typeof(self) weakSelf=self;
_add =^Calculator*(double num){
weakSelf.operationNum+=num;
return weakSelf;
};
_reduce =^Calculator*(double num){
weakSelf.operationNum-=num;
return weakSelf;
};
_mutiply =^Calculator*(double num){
weakSelf.operationNum*=num;
return weakSelf;
};
_divide =^Calculator*(double num){
weakSelf.operationNum= _operationNum / (num == 0 ? 1 : num);
return weakSelf;
};
}
然后我们就可以调用该类进行计算
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Calculator* cal=[Calculator operationNum:20];
cal.add(2).mutiply(5);
NSLog(@"%f",cal.operationNum);
}
要实现链式调用的一个关键点:就是每次调用add方法必须返回自身,然后才可以继续调用,如此一致循环下去,实现这一切都是block的功劳。
七总结
通过上面的例子我们看到,block作为参数时,先在block内部实现一个代码块,因为block是一个OC对象,所以可以被当做参数传递到合适的地方,然后在合适的时候调用该block并传入参数,就可以实现对该代码块的调用,达到回调的目的。
楼主是个初学者,可能暂时总结并不完全,以后会不断完善。
参考文献
一篇文章看懂iOS代码块Block
Block使用场景