iOS Block
这篇文章是为了讲解ReactiveCocoa做的一个铺垫,由于ReactiveCocoa中大量使用Block,所以这里将对Block进行一个巩固,当然,Block用的很熟悉的朋友,大可不必看这篇文章。
在iOS4.0之后,Block面世,它是一种特殊的数据类型。它的本身是一段代码,特殊在于,它将这段代码当做了变量,然后通过类似block()这种方式回调。
写一个Block总共分几步?
答:三步!
-
声明:
NSString *(^blockName)(NSString *str1, NSString *str2);
从前到后依次为返回值类型、block名字、参数1、参数2; - 定义:
(NSString *)^(NSString *str1, NSString *str2) {
return [NSString stringWithFormat:@"%@+%@",str1, str2];
};
注意:Block是一个语法块,后面我们要带“;”的!
-
调用:
blockName(@"str1",@"str2");
根据传入参数和返回值类型,我们可以认为Block有四种类型:
- 有参有返回值
//声明&定义
NSString *(^block5)(NSString *) = ^(NSString *str){
return [NSString stringWithFormat:@"%@+参数2", str];
};
//调用
NSLog(@"%@", block5(@"参数1"));
- 有参无返回值
//声明&定义
void (^block2)(NSString *) = ^(NSString *str){
NSLog(@"%@", str);
};
//调用
block2(@"参数");
- 无参有返回值
//声明&定义
NSString *(^block4)(void) = ^(void){
return @"返回值";
};
//调用
NSLog(@"%@", block4());
- 无参无返回值
//声明&定义
void (^block1)(void) = ^(void){
NSLog(@"无参数 无返回值!");
};
//调用
block1();
Block的使用
在Block诞生之前,开发者们用的都是delegate来完成回调。在Block面世之后,绝大部分的回调处理被改为了Block。之所以做出这一改变,跟Block的使用简单,灵活等原因是分不开的!
这里我以再次封装AFNetworking为例,来对Block回调进行讲解。
先分析一下网络请求的几个必要要素:
- 域名
- 请求参数
- 网络请求成功回调
- 网络请求失败回调
- 网络异常回调
这里面前两者没什么可分析的,主要说一下后三个Block的机制。
三者我们着重来讲解一个(网络请求成功回调),因为三者只是参数和返回的数据类型不一样,大同小异,只要理解了,没必要多做解释。
- 通过typedef来给block起一个类型名称
typedef void (^ReturnValueBlock) (id returnValue);//成功回调
typedef void (^ErrorCodeBlock) (id errorCode);//失败回调
typedef void (^FailureBlock)();//错误回调
当我们要回调的时候,我们可以根据我们的参数类型、个数,和返回值类型来选择我们用哪种Block。
在网络请求的类中声明这个方法:
#pragma --mark POST请求方式
-(void) NetRequestPOSTWithRequestURL: (NSString *) requestURLString
WithParameter: (NSDictionary *) parameter
WithReturnValeuBlock: (ReturnValueBlock) block
WithErrorCodeBlock: (ErrorCodeBlock) errorBlock
WithFailureBlock: (FailureBlock) failureBlock;
方法中,我们传入的参数有三个Block,他们三个分别是用之前用typedef声明的,分别处理网络请求的时候不同的处理。接下来看看它是怎么实现的
#pragma --mark POST请求方式
-(void) NetRequestPOSTWithRequestURL: (NSString *) requestURLString
WithParameter: (NSDictionary *) parameter
WithReturnValeuBlock: (ReturnValueBlock) block
WithErrorCodeBlock: (ErrorCodeBlock) errorBlock
WithFailureBlock: (FailureBlock) failureBlock
{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:[NSString stringWithFormat:@"%@%@", BaseUrl, requestURLString] parameters:parameter progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"requestUrl: %@\nparameter: %@", requestURLString, parameter);
if ([[responseObject objectForKey:@"status"] integerValue]== 200) {
block(responseObject);
} else if ([[responseObject objectForKey:@"status"] integerValue]== 400) {
errorBlock(responseObject);
}
} failureObjc:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error, id responseObject) {
NSLog(@"%@", responseObject);
failureBlock();
}];
}
主要看Success里面,这里面回调了两个block:”block“、”errorBlock“。不知道大家能不能看这两个block是在什么时候回调的。感觉应该可以看得懂,根据网络请求返回的状态,”200“表示网络请求成功,”400“表示网络请求失败,各家公司的接口应该都差不多吧。接下来就是使用了
由于这段代码是从公司工程中拿出来的,所以接口和数据的处理都被我删掉重新简化的写了下
-(void)getTeacherCommentLis{
NSDictionary *dic = @{@"access_token":[USER_DEFAULT objectForKey:@"access_token"],
@"pageNo":[NSNumber numberWithInteger:1]};
[[ZTAPIClient alloc] NetRequestPOSTWithRequestURL:@"getTeacherCommentLis" WithParameter:dic WithReturnValeuBlock:^(id returnValue) {
网络请求成功,得到返回结果;
可以刷新TableView,存储,更新等一系列操作
} WithErrorCodeBlock:^(id errorCode) {
网络请求失败,往往是填写的参数有问题
} WithFailureBlock:^{
}];
}
现在来想一想,回顾一下,以上的操作我们都是在做什么。我们是在哪里声明的block、又是在哪里定义的、最后又是在哪里调用的?
- 在网络请求类的声明文件中,我们用typedef声明了block;
- 在网络请求的方法中,我们把声明的block当做了参数,传入到了方法中
- 在调用方法的时候,我们定义了这个block需要做的操作
- 在网络请求类实现方法中,我们调用了block
Block相关的几个关键字
copy
block在使用的过程中跟对象一样会引起引用计数变化。同样声明block时,它的内存是分配在栈上,随时可能被回收,所以,我们需要将他拷贝到堆上。通过copy可以把block拷贝到堆上,来保证block不会被随时回收。
@property (nonatomic, copy)void(^block)(NSString *);
__block
在一个block里面,我们对block之外的变量是只读操作,也就是我们只能读取它的值,不可以更改。像这样,Xcode会给你报一个这样的错误。
block内访问外部变量.png
说到__block, 它是用于,我们想在block内来改变某个外部的变量的时候,我们需要在声明这个变量的时候用“__block”来修饰。
__block修饰外部变量
__weak
有时在使用block 的时候,由于self 是被强引用的,在 ARC 下,当编译器自动将代码中的block从栈拷贝到堆时,block 会强引用和持有self,而 self 也强引用和持有了 block,这就造成了循环引用,导致两者都不能释放,内存泄露。__weak解决循环引用的理念就是,将其中一者弱化,编程弱引用,也就是引用计数不会增加。 __weak修饰符修饰变量 self,让 block 不强引用 self,从而破除循环。