iOS Block

作者: 赤子追梦心 | 来源:发表于2017-03-06 11:59 被阅读204次

这篇文章是为了讲解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,从而破除循环。

相关文章

网友评论

    本文标题:iOS Block

    本文链接:https://www.haomeiwen.com/subject/gjbpwttx.html