Block的初步理解

前沿

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

一.什么是Block

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

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 <UIKit/UIKit.h>

//将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 <Foundation/Foundation.h>
@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使用场景

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • Block使用场景,可以在两个界面的传值,也可以对代码封装作为参数的传递等。用过GCD就知道Block的精妙之处。...
    Coder_JMicheal阅读 724评论 2 1
  • 转自李峰峰博客 一、概述 闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」...
    Joshua520阅读 987评论 0 0
  • 1: 什么是block?1.0: Block的语法1.1: block编译转换结构1.2: block实际结构 2...
    iYeso阅读 838评论 0 5
  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,811评论 10 69
  • iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,B...
    smile刺客阅读 2,347评论 2 26