Block的初步理解

前沿

最近在研究RAC,里面用到了很多的Block。然后想起来网络请求中也用到了很多,所以自己参考了很多相关内容,对Block进行了简单的总结和理解。

一.什么是Block

block就是可以截获局部变量的匿名函数。

Block的初步理解_第1张图片
988593-1b047e7026c5f589.jpg

上面为官方的语法说明。

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使用场景

你可能感兴趣的:(Block的初步理解)