關(guān)于block--你想了解的幾乎都在這里了

一.block定義
二.block的本質(zhì)
三.block變量捕獲(Capture)
四.block的類型
五.block的copy操作
六.block使用了對(duì)象類型的auto變量的分析
七.__block講解
八.block的循環(huán)引用問題
九.block的一些使用場景

block在我們開發(fā)中隨處可見,比如我們常用的AFN框架和ReactiveCocoa框架等,多線程中的GCD,各種方法的回調(diào)等等,可見block在我們開發(fā)中的重要性,今天我就詳細(xì)的整理下關(guān)于block的一些東西,如果有什么問題也歡迎大家一起討論.

一. block知識(shí)回顧

1. 定義

什么是Block?=》 Block是將函數(shù)及其執(zhí)行上下文封裝起來的對(duì)象。
這里借用網(wǎng)上的一張截圖,感覺還是比較詳細(xì)的

上圖聲明的block類型為:int (^)(int)

注意:block塊中的代碼并沒有執(zhí)行,只有當(dāng)我們明確調(diào)用的時(shí)候才會(huì)具體執(zhí)行

我們可以使用inlineBlock的快捷方式生成一段block代碼

2. 使用typedef重定義

對(duì)于可能需要重復(fù)地聲明多個(gè)相同返回值相同參數(shù)列表的block變量, 如果我們總是重復(fù)地編寫一長串代碼來聲明變量 那么會(huì)非常的繁瑣,所以此時(shí)我們可以使用typedef來對(duì)block進(jìn)行重定義 具體如下:

typedef int(^sumBlock)(int , int);

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    sumBlock myblock = ^(int a,int b)
    {
        return a+b;
    };
    myblock(20,30);
}

二. block的本質(zhì)

block使用起來非常的方便,那么他的底層或者說是本質(zhì)到底是什么呢? 下面我們先通過一個(gè)最簡單的block來分析下它的底層到底是如何實(shí)現(xiàn)的:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void(^block)(void) = ^{
            NSLog(@"Hello World");
        };
        
        block();
    }
    return 0;
}

通過下面的命令將我們的.m文件轉(zhuǎn)化成.cpp文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

生成的C++文件如下圖所示,為了便于理解我去掉了部分強(qiáng)制轉(zhuǎn)換的東西:

通過以上的代碼分析我們我們可以得出一個(gè)結(jié)論:

block本質(zhì)上是一個(gè)OC對(duì)象,它的內(nèi)部也有一個(gè)isa指針

當(dāng)然我們也可以通過下面的代碼來驗(yàn)證我們的結(jié)論:

        void (^block)(void) = ^{
            NSLog(@"Hello World");
        };
        NSLog(@"%@",[block class]);
        NSLog(@"%@",[[block class] superclass]);
        NSLog(@"%@",[[[block class] superclass] superclass]);
        NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);

執(zhí)行結(jié)果:

 block[4176:730867] __NSGlobalBlock__
 block[4176:730867] __NSGlobalBlock
 block[4176:730867] NSBlock
 block[4176:730867] NSObject

重點(diǎn): 可以看到block對(duì)象是繼承自NSObject的一個(gè)OC對(duì)象

四. 變量捕獲(Capture)

1. 局部變量auto

因?yàn)樯厦娑x的block太過簡單,一些本質(zhì)的東西我們無法看到,這里我們定義一個(gè)稍微復(fù)雜一點(diǎn)的block對(duì)象,代碼如下:

int age = 10;//等價(jià)于 auto int age = 10;
void(^block)(void) = ^{
    NSLog(@"age is %d",age);
};
age = 20;
block();

執(zhí)行該block后打印的age的值是多少呢?

相信大部分同學(xué)立馬就可以回答出來結(jié)果 age is 10,但是為什么是這個(gè)結(jié)果呢? 相信大部分同學(xué)應(yīng)該挺迷惑的

不著急我們一步步來為大家分析:

同樣將該段代碼轉(zhuǎn)化為C++代碼 如下圖:


通過以上代碼的簡單分析我們就可以清晰的看出block在內(nèi)部捕獲了age,并且是值傳遞, 所以age在block外部修改并不會(huì)影響到其內(nèi)部的值
該段C++代碼結(jié)構(gòu)其實(shí)可以簡化成下面這幅圖:

2. 局部變量Static

下面我們來看一個(gè)稍微復(fù)雜點(diǎn)的代碼:

        int age = 10;//等價(jià)于 auto int age = 10 ,auto可以省略
        static int height = 10;
        void(^block)(void) = ^{
            NSLog(@"age is %d,height is %d",age,height);
        };
        age = 20;
        height = 20;
        block();

執(zhí)行該block后打印的結(jié)果是多少呢? age is 10,height is 20
老規(guī)矩,同樣將該段代碼轉(zhuǎn)化為C++代碼,如下圖所示:

通過以上代碼我們可以看到block可以同時(shí)捕獲到age和height,但是用Static修飾的height和age是不一樣的,age是一個(gè)值傳遞,而height是一個(gè)引用傳遞,所以在block外部即使height的數(shù)據(jù)變更我們也可以獲取到最新的數(shù)據(jù).

思考

那么不知道大家有沒有想過這樣一個(gè)問題,為什么用static修飾的變量是引用傳遞,而用auto修飾的變量就是值傳遞呢?
下面我們通過一段簡單的代碼來為大家分下一下:


因?yàn)樽饔糜虻膯栴} 當(dāng)代碼執(zhí)行到23行的時(shí)候也就是我們執(zhí)行完test函數(shù)后,此時(shí)age的內(nèi)存已經(jīng)銷毀,如果block不馬上將age的值捕獲到其內(nèi)部,那么執(zhí)行到24行的時(shí)候,訪問到的age此時(shí)就是一個(gè)垃圾數(shù)據(jù)了,而用static修飾的height就不一樣了,所以傳遞的是地址,將來height的數(shù)據(jù)即使更新了,我們也可以獲取到最新的數(shù)據(jù).

3. 全局變量

上面的兩種情況都是局部變量,下面我們?cè)趤砜纯慈肿兞康那闆r:

#import <Foundation/Foundation.h>

int age = 10;
static int height = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void (^block)(void) = ^{
            NSLog(@"age is %d,height is %d",age,height);
        };
        age = 20;
        height = 20;
        block();
        
    }
    return 0;
}

打印結(jié)果:

age is 20,height is 20

轉(zhuǎn)化為C++代碼:


通過上面代碼我們可以看到 __main_block_impl_0內(nèi)部并沒有捕獲age和height 所以打印結(jié)果就是最新的數(shù)據(jù)

通過上面的分析我們應(yīng)該知道因?yàn)閍ge和height都是全局變量,在任何地方都可以訪問到他們,所以block并不用去捕獲他們

4. 總結(jié):

關(guān)于block捕獲變量我們可以總結(jié)成下面一個(gè)表格,清晰明了:


五.block的類型

1. block的三種類型

先說結(jié)論:block對(duì)象有3種類型,分別為:

__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__  ( _NSConcreteStackBlock  )
__NSMallocBlock__ ( _NSConcreteMallocBlock )

他們都是繼承自NSBlock類型.我們都知道應(yīng)用程序在內(nèi)存中分為 代碼段 數(shù)據(jù)段 堆段棧段,其中堆段是我們程序員自己動(dòng)態(tài)分配的,并且需要我們手動(dòng)釋放內(nèi)存(free()或者release()操作),而棧段是非常危險(xiǎn)的,隨時(shí)都有可能會(huì)被系統(tǒng)銷毀.

因?yàn)橄聢D比較經(jīng)典我就直接拿過來用了


那么這三種類型之間如何區(qū)分以及有什么區(qū)別呢?下面我們先來看一幅圖:


2. 結(jié)論一:如果block沒有訪問auto變量則其類型為:__NSGlobalBlock

對(duì)于這種類型的block我們不需要考慮作用域的問題,而且對(duì)他進(jìn)行copy或者retain操作也是無效的.

驗(yàn)證:

#import <Foundation/Foundation.h>
int height = 20;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //沒有訪問任何變量
        void (^block1)(void) = ^{
            NSLog(@"Hello World");
        };
        //訪問全局變量
        void (^block2) (void) =  ^{
            NSLog(@"height is %d",height);
        };
        //訪問static修飾的局部變量
        static int age = 10;
        void (^block3) (void) =  ^{
            NSLog(@"age is %d",age);
        };
        
        NSLog(@"%@--%@---%@",[block1 class],[block2 class],[block3 class]);
        
    }
    return 0;
}

打印結(jié)果:

block[4486:803768] __NSGlobalBlock__--__NSGlobalBlock__---__NSGlobalBlock__

3. 結(jié)論二:如果block訪問了auto修飾的變量則其類型為:__NSStackBlock

驗(yàn)證:

int age = 10;
        void (^block) (void) =  ^{
            NSLog(@"age is %d",age);
        };
        NSLog(@"%@",[block class]);

打印結(jié)果:

block[4524:811710] __NSMallocBlock__

恩? 怎么是__NSMallocBlock__?搞錯(cuò)了吧.....

不著急,因?yàn)槲覀兊捻?xiàng)目是ARC環(huán)境,編譯器幫助我們做了一些事情,我們將代碼改成MRC環(huán)境:



再次執(zhí)行:

block[4577:821982] __NSStackBlock__

這才對(duì)嘛

但是因?yàn)?code>__NSStackBlock__的內(nèi)存是分配在棧段的,所以在使用的時(shí)候經(jīng)常會(huì)出現(xiàn)一些問題,比如:

#import <Foundation/Foundation.h>
void (^block) (void);
void test()
{
    int age = 10;
    block = ^{
        NSLog(@"age is %d",age);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();        
    }
    return 0;
}

執(zhí)行結(jié)果:

block[4919:908731] age is -272632360

納尼???!!! 怎么是這么個(gè)亂七八糟的數(shù)據(jù)?

因?yàn)樵揵lock的類型是__NSStackBlock__,所以當(dāng)test()函數(shù)執(zhí)行完畢后,block對(duì)象中成員已經(jīng)被釋放掉了,所以當(dāng)我們?cè)趫?zhí)行block()的時(shí)候拿到的數(shù)據(jù)就是垃圾數(shù)據(jù)了,而不是我們想要的10.

那么該如何解決該問題呢?
我們只需要將block進(jìn)行copy操作即可.

void test()
{
    int age = 10;
    block = [^{
        NSLog(@"age is %d",age);
    } copy];
}

執(zhí)行結(jié)果:

block[5004:929720] age is 10

所以在開發(fā)中,我們經(jīng)常會(huì) 對(duì) block進(jìn)行Copy操作 原因就是因?yàn)檫@個(gè)

4. 所以結(jié)論三 NSStackBlock 調(diào)用了copy后會(huì)變成 NSMallocBlock

每一種類型的block調(diào)用Copy操作后的結(jié)果如下圖所示:


block的copy操作

在上文中我們的環(huán)境是MRC環(huán)境,如果是ARC環(huán)境的時(shí)候 block內(nèi)部使用了auto修飾的局部變量時(shí),該block的類型是NSMallocBlock,為什么會(huì)這樣呢?因?yàn)樵贏RC環(huán)境下亿虽,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上谍夭,比如以下這些情況:

1. block作為函數(shù)返回值時(shí)
2. 將block賦值給__strong指針時(shí)
3. block作為Cocoa API中方法名含有`usingBlock`的方法參數(shù)時(shí)
4. block作為`GCD API`的方法參數(shù)時(shí)
5. ....
.
.
.

基于以上結(jié)論:
MRC下block屬性的建議寫法

@property (copy, nonatomic) void (^block)(void);

ARC下block屬性的建議寫法

@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

但是為了統(tǒng)一,其實(shí)我們更建議不管是ARC環(huán)境還是MRC環(huán)境下block都用copy修飾

block使用了對(duì)象類型的auto變量的分析

因?yàn)樯厦嫖覀兎治龅慕Y(jié)果都是基于基本數(shù)據(jù)類型的,有些問題涉及不到,這里我們分析一種復(fù)雜的情況 , 即 block內(nèi)部使用了對(duì)象類型的變量

首先我們定義一個(gè)Person對(duì)象

@interface Person : NSObject
@property (nonatomic,assign) int age;
@end

在該對(duì)象內(nèi)部調(diào)用dealloc方法

#import "Person.h"
@implementation Person
-(void)dealloc{
    NSLog(@"Person -------dealloc");
}
@end

執(zhí)行程序后 代碼執(zhí)行到20行的時(shí)候 調(diào)用了Person對(duì)象的dealloc方法 , 哥們 你在逗我么 這個(gè)地球人都知道好吧.....

?? 不著急,我們一步步來 上面是預(yù)熱

將我們的編譯環(huán)境切換到MRC環(huán)境,執(zhí)行下面的代碼



斷點(diǎn)停留在了25行,此時(shí)block還沒有銷毀,但是打印結(jié)果顯示Person已經(jīng)掛掉了

結(jié)論一:如果block是在棧上,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用
同樣的代碼將編譯環(huán)境切換到ARC,執(zhí)行結(jié)果如下圖:


ARC環(huán)境下,當(dāng)代碼執(zhí)行到24行的時(shí)候person對(duì)象并沒有銷毀,說明block對(duì)象對(duì)person有一個(gè)強(qiáng)引用

如果我們將Person對(duì)象用__weak修飾呢?結(jié)果又是什么樣子呢?如下圖所示:

那么其底層是如何操作的呢?執(zhí)行下面的命令將代碼轉(zhuǎn)化為C++代碼:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

總結(jié):當(dāng)block內(nèi)部訪問了對(duì)象類型的auto變量時(shí)

如果block是__NSStackBlock__類型,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用

如果block被拷貝到堆上,則會(huì)調(diào)用block內(nèi)部的copy函數(shù),copy函數(shù)內(nèi)部會(huì)執(zhí)行_Block_object_assign函數(shù),該函數(shù)內(nèi)部會(huì)根據(jù)auto變量的修飾符__strong 熟丸、__weak而做出相應(yīng)的操作,如果是__strong 則形成強(qiáng)引用,而__weak 則會(huì)形成弱引用關(guān)系.

如果block從堆上移除,則會(huì)調(diào)用block內(nèi)部的dispose函數(shù),dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù),該函數(shù)會(huì)自動(dòng)釋放引用的auto變量,類似于release操作.

練習(xí)題:
仔細(xì)看下面的代碼,Person對(duì)象會(huì)在什么時(shí)候銷毀?


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    Person *person = [[Person alloc] init];
    person.age = 18;
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"age is %d",person.age);
    });
    NSLog(@"Hello World");
}

分析過程:因?yàn)镚CD中的block是在堆段中,所以block會(huì)一直存在直到執(zhí)行完畢之后才會(huì)銷毀,因?yàn)榇颂嶱erson對(duì)象是一個(gè)__Strong,所以block會(huì)對(duì)Person對(duì)象形成一個(gè)強(qiáng)引用,所以Person對(duì)象會(huì)在3秒后Blcok執(zhí)行完畢后釋放
執(zhí)行結(jié)果:

六. __block講解

在具體講解__block之前我們看一段代碼:


我們想在block內(nèi)部去修改age的值,但是此時(shí)系統(tǒng)編譯失敗并且提示我們?nèi)鄙?code>__block修飾符,首先我們來思考下為什么在block內(nèi)部不能去修改auto修飾的局部變量呢?

其實(shí)仔細(xì)看過上面的分析之后我們肯定知道答案了,因?yàn)樽饔糜虻脑?自動(dòng)變量隨時(shí)都有可能被銷毀, 所以在block內(nèi)部是不能去更改auto修飾的自動(dòng)變量的.

除了系統(tǒng)給我們的提示, 添加__block修飾符外,其實(shí)我們還可以通過添加static修飾符,因?yàn)?code>static修飾的局部變量是一個(gè)地址傳遞,所以完全沒有問題.

或者將age放到函數(shù)外部,也就是將age設(shè)置成一個(gè)全局變量,這樣也是可以更改的

但是不管是static修飾還是全局變量,age的性質(zhì)就改變了 所以此處最好還是用__block修飾,那么__block內(nèi)部做了什么事情呢?

老規(guī)矩..


我們通過C++代碼可以看到 編譯器會(huì)將__block變量包裝成一個(gè)對(duì)象,然后將age的地址值傳遞給該對(duì)象,通過修改該對(duì)象內(nèi)部的age來達(dá)到目的

注意:

__block可以用于解決block內(nèi)部無法修改auto變量值的問題,但是不能修飾全局變量老翘、靜態(tài)變量(static)

__block的內(nèi)存管理

當(dāng)block在棧上時(shí),僅僅是使用了__block變量锻离,并沒有對(duì)__block變量產(chǎn)生強(qiáng)引用

當(dāng)block被copy到堆時(shí)
會(huì)調(diào)用block內(nèi)部的copy函數(shù)
copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
_Block_object_assign函數(shù)會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用(僅當(dāng)ARC時(shí)才有效,MRC時(shí)候并不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用)

當(dāng)block從堆中移除時(shí)
會(huì)調(diào)用block內(nèi)部的dispose函數(shù)
dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的__block變量

Block的循環(huán)引用問題

兩個(gè)對(duì)象相互持有铺峭,這樣就會(huì)造成循環(huán)引用,如下圖所示:

圖中汽纠,對(duì)象A持有對(duì)象B卫键,對(duì)象B持有對(duì)象A,相互持有虱朵,最終導(dǎo)致兩個(gè)對(duì)象都不能釋放莉炉。

列舉個(gè)簡單的例子:

Person.h

@interface Person : NSObject
/** 名字 */
@property (nonatomic, copy) NSString *name;

/** block */
@property (nonatomic, copy) void (^dosomethingBlock)();
@end

Person.m

@implementation Person

-(void)dealloc
{
     NSLog(@"%s", __func__);
}
@end

ViewController中去實(shí)例化Person對(duì)象:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *per = [[Person alloc] init];
    per.name = @"Jack";
    per.dosomethingBlock = ^{
        NSLog(@"-----------%@", per.name);
    };
}
@end

運(yùn)行上面的代碼,發(fā)現(xiàn)并沒有調(diào)用 Person 對(duì)象中的 dealloc方法.說明此時(shí)發(fā)生了循環(huán)引用的問題,
通過上文這么長時(shí)間的分析,我相信大家一定知道block內(nèi)部做了什么事情, 因?yàn)?Person 對(duì)象是用__strong修飾的,所以此時(shí) __Block_Object_Assign方法內(nèi)部會(huì)自動(dòng)產(chǎn)生一個(gè)【強(qiáng)引用】指向【對(duì)象Person】

那么此時(shí)我們?cè)撊绾谓鉀Q這個(gè)問題呢? 通常的做法是創(chuàng)建一個(gè) __weak 修飾的弱引用 指向 person對(duì)象,代碼如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *per = [[Person alloc] init];
    
    __weak typeof(Person) *weakPerson = per;
    
    per.name = @"Jack";
    per.dosomethingBlock = ^{
        NSLog(@"-----------%@", weakPerson.name);
    };
}

運(yùn)行上面的代碼,發(fā)現(xiàn) Person對(duì)象調(diào)用了 dealloc方法.

Block的嵌套

還是以上面的例子,我們?cè)?per.dosomethingBlock中延遲三秒后打印 weakPerson.name,仔細(xì)查看下面的代碼,你會(huì)發(fā)現(xiàn)什么問題?


- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *per = [[Person alloc] init];
    __weak typeof(Person) *weakPerson = per;
    per.name = @"Jack";
    per.dosomethingBlock = ^{
         NSLog(@"beign-------");
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(),
           ^{
               NSLog(@"after-----------%@", weakPerson.name);
           });
    
    };
    per.dosomethingBlock();
}

控制臺(tái)輸出結(jié)果:

2017-08-14 17:48:17.441 block[23041:1182337] beign-------
2017-08-14 17:48:17.441 block[23041:1182337] -[Person dealloc]
2017-08-14 17:48:20.729 block[23041:1182337] after-----------(null)

通過打印結(jié)果我們可以看到當(dāng) Block 被執(zhí)行的時(shí)候立馬打印了 beign-------信息,然后緊接著 Person對(duì)象被銷毀, 3秒以后打印了 after-------信息,注意因?yàn)榇藭r(shí) Person對(duì)象已經(jīng)被銷毀了,所以打印出了 Null

所以 在延遲執(zhí)行的Block內(nèi)部 為了保住 Person對(duì)象不被銷毀 我們需要使用一個(gè)強(qiáng)引用來保住 Person對(duì)象的命,稍微更改下代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *per = [[Person alloc] init];
    __weak typeof(Person) *weakPerson = per;
    per.name = @"Jack";
    per.dosomethingBlock = ^{
         NSLog(@"beign-------");
        
        Person *strongPerson = weakPerson;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(),
           ^{
               NSLog(@"after-----------%@", strongPerson.name);
           });
    
    };
    per.dosomethingBlock();
}

控制臺(tái)輸出結(jié)果:

2017-08-14 17:50:04.947 block[23068:1184458] beign-------
2017-08-14 17:50:08.234 block[23068:1184458] after-----------Jack
2017-08-14 17:50:08.234 block[23068:1184458] -[Person dealloc]

通過上面的結(jié)果我們可以看到 在 dispatch_after中使用了 一個(gè)強(qiáng)引用 strongPerson來保住了 Person對(duì)象的命,所以此時(shí)才是我們想要的結(jié)果.

block的一些使用場景

將block作為對(duì)象屬性,恰當(dāng)時(shí)機(jī)的時(shí)候才去調(diào)用

場景一
比如我們從A控制器Modal到B控制器,在B控制器中點(diǎn)擊了某個(gè)按鈕后需要給A控制器傳遞一些數(shù)據(jù),當(dāng)然這種也可以使用代理來實(shí)現(xiàn),但是我感覺 使用block更為簡潔:

B控制器:

@interface BViewController : UIViewController

@property (nonatomic, strong) void(^buttonClickBlock)(NSString *param);

@end
-(void)buttonClick{
    if (_buttonClickBlock) {
        _buttonClickBlock(@"需要傳遞的參數(shù)");
    }
}

A控制器:

    BViewController *modalVc = [[BViewController alloc] init];
    modalVc.view.backgroundColor = [UIColor brownColor];
    
    modalVc.buttonClickBlock = ^(NSString *param) {
      
        NSLog(@"傳遞過來的參數(shù):%@",param);
    };
    // 跳轉(zhuǎn)
    [self presentViewController:modalVc animated:YES completion:nil];

場景二:
有一個(gè)UITableView,此時(shí)點(diǎn)擊每一行都會(huì)做不同的事情,如果不用block 你是不是想著在點(diǎn)擊的時(shí)候去判斷行號(hào),然后根據(jù)行號(hào)在去做對(duì)應(yīng)的事情? 然而通過block我們可以簡化這個(gè)流程:

在對(duì)應(yīng)的實(shí)體模型中,我們只需要定義一個(gè)具體做事的block,例如:

@interface CellItem : NSObject

@property (nonatomic, strong) NSString *title;

// 保存每個(gè)cell做的事情
@property (nonatomic, strong) void(^doSomethingBlock)();

@end

然后在tableView中:點(diǎn)擊的時(shí)候執(zhí)行對(duì)應(yīng)的代碼塊就可以了,是不是一下輕松了許多

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 創(chuàng)建模型
    CellItem *item1 = [CellItem itemWithTitle:@"打電話"];
    
    // 把要做的事情(代碼)保存到模型
    item1.doSomethingBlock = ^{
        NSLog(@"打電話");
    };
    CellItem *item2 = [CellItem itemWithTitle:@"發(fā)短信"];
    item2.doSomethingBlock = ^{
        NSLog(@"發(fā)短信");
    };
    CellItem *item3 = [CellItem itemWithTitle:@"發(fā)郵件"];
    item3.doSomethingBlock = ^{
        NSLog(@"發(fā)郵件");
    };
    _items = @[item1,item2,item3];
}
// 點(diǎn)擊cell的時(shí)候執(zhí)行對(duì)應(yīng)的block代碼塊就可以了
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    CellItem *item = self.items[indexPath.row];
    if (item.doSomethingBlock) {
        item.doSomethingBlock();
    }
}

將block當(dāng)作函數(shù)參數(shù)來傳遞

當(dāng)我們封裝一個(gè)方法的時(shí)候,而這個(gè)方法具體要做什么事情不是方法內(nèi)部能夠決定的,但什么時(shí)候做是由內(nèi)部決定的,(即內(nèi)部決定執(zhí)行時(shí)間,而外部傳入具體做些什么)——這個(gè)時(shí)候就可以使用block來作為函數(shù)參數(shù)

列舉一個(gè)經(jīng)常使用的例子:

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion

對(duì)于系統(tǒng)提供的這個(gè)動(dòng)畫方法,要執(zhí)行什么樣的動(dòng)畫,執(zhí)行完動(dòng)畫要做什么事情,該方法內(nèi)部是不知道的,需要交給用戶自己去實(shí)現(xiàn)

場景二:
要求封裝一個(gè)計(jì)算器類CacultorManager,該類提供一個(gè)計(jì)算方法:怎么計(jì)算是由外界決定的,什么時(shí)候計(jì)算由內(nèi)部決定.

CacultorManager.h

@interface CacultorManager : NSObject

/***保存計(jì)算的結(jié)果**/
@property (nonatomic, assign) NSInteger result;

- (void)cacultor:(NSInteger(^)(NSInteger result))cacultorBlock;

@end

CacultorManager.m

@implementation CacultorManager

- (void)cacultor:(NSInteger (^)(NSInteger))cacultorBlock
{
    
    if (cacultorBlock) {
      _result =  cacultorBlock(_result);
    }
}

@end

具體使用該類:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 創(chuàng)建計(jì)算器管理者
    CacultorManager *mgr = [[CacultorManager alloc] init];
    [mgr cacultor:^(NSInteger result){
        result += 5;
        result -= 6;
        return result;
    }];
    
    NSLog(@"%ld",mgr.result);
}
@end
將block當(dāng)作函數(shù)返回值來傳遞

相信大家之前都寫過類似這樣的代碼

[View mas_makeConstraints:^(MASConstraintMaker *make) {
      make.top.equalTo(anotherView);
      make.left.equalTo(anotherView);
      make.width.mas_equalTo(@60);
      make.height.mas_equalTo(@60);
}];

類似于 Masonry這種可以連續(xù).top.equalTo(anotherView); 的寫法我們稱為鏈?zhǔn)骄幊?/code>,而實(shí)現(xiàn)這種思路的重點(diǎn)在于方法的返回值必須是block,而block必須返回本身,block的參數(shù)就是我們需要操作的數(shù)據(jù)

下面我們就來寫一個(gè)類似這樣的小功能:還是拿上面的計(jì)算器類CalculatorManager來舉例子:

CalculatorManager.h

@interface CalculatorManager : NSObject

/***保存計(jì)算的結(jié)果**/
@property (nonatomic, assign) int result;

- (CalculatorManager *(^)(int))add;
- (CalculatorManager *(^)(int))sub;

@end

CalculatorManager.m

@implementation CalculatorManager

- (CalculatorManager *(^)(int))add
{
    return ^(int value){
        _result += value;
        
        return self;
    };
}

- (CalculatorManager *(^)(int))sub
{
    return ^(int value){
        _result -= value;
        
        return self;
    };
}

@end

在實(shí)現(xiàn)類中:

- (void)viewDidLoad {
    [super viewDidLoad];

    CalculatorManager *mgr = [[CalculatorManager alloc] init];
   
    mgr.add(5).add(5).sub(6).add(5);
    
    NSLog(@"%d",mgr.result);

}
@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市碴犬,隨后出現(xiàn)的幾起案子絮宁,更是在濱河造成了極大的恐慌,老刑警劉巖翅敌,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羞福,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚯涮,警方通過查閱死者的電腦和手機(jī)治专,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遭顶,“玉大人张峰,你說我怎么就攤上這事“羝欤” “怎么了喘批?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵撩荣,是天一觀的道長。 經(jīng)常有香客問我饶深,道長餐曹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任敌厘,我火速辦了婚禮台猴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俱两。我一直安慰自己饱狂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布宪彩。 她就那樣靜靜地躺著休讳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尿孔。 梳的紋絲不亂的頭發(fā)上俊柔,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音纳猫,去河邊找鬼婆咸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛芜辕,可吹牛的內(nèi)容都是我干的尚骄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼侵续,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼倔丈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起状蜗,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤需五,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后轧坎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宏邮,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年缸血,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜜氨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捎泻,死狀恐怖飒炎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笆豁,我是刑警寧澤郎汪,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布赤赊,位于F島的核電站,受9級(jí)特大地震影響煞赢,放射性物質(zhì)發(fā)生泄漏抛计。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一耕驰、第九天 我趴在偏房一處隱蔽的房頂上張望爷辱。 院中可真熱鬧,春花似錦朦肘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咏花,卻和暖如春趴生,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昏翰。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工苍匆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棚菊。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓浸踩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親统求。 傳聞我的和親對(duì)象是個(gè)殘疾皇子检碗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容