iOS 關(guān)于Block代碼塊的詳解

概述

block

上圖就是一個(gè)block簡(jiǎn)單使用,它包括了block的聲明、賦值實(shí)現(xiàn)、調(diào)用 三個(gè)部分,其中,實(shí)現(xiàn)部分可以看作是一種匿名函數(shù);跟函數(shù)一樣,block也是需要調(diào)用才能執(zhí)行內(nèi)部代碼的;賦值的行為又讓block看起來跟數(shù)據(jù)類型類似

代碼塊Block是在iOS4開始引入的,是對(duì)C語言的擴(kuò)展,用來實(shí)現(xiàn)匿名函數(shù)的特性

Block是一種特殊的數(shù)據(jù)類型,可以像基本數(shù)據(jù)類型一樣定義成變量、作為參數(shù)、返回值來使用

Block還可以保存一段代碼,在需要的時(shí)候調(diào)用

在iOS開發(fā)中,Block被系統(tǒng)應(yīng)在很多地方,例如:GCD、UIView動(dòng)畫、排序等,我們開發(fā)者也可以應(yīng)用在各類回調(diào)、傳值、傳消息等

Block的聲明、賦值實(shí)現(xiàn)、調(diào)用

Block的聲明樣式。

返回類型 (^Block名稱)(參數(shù)列表)

void(^myBlock)(NSString *, NSString *)

Block的返回類型分為有返回類型和無返回類型(void),參數(shù)列表也可有也可以沒有,具體看需求

// 無返回類型無參數(shù)列表
void(^block)();
// 無返回類型有參數(shù)列表
void(^block)(int);
// 有返回類型無參數(shù)列表
int(^block)();
// 有返回列表有參數(shù)列表
int(^block)(int);

Block的簡(jiǎn)單使用:聲明、賦值、調(diào)用

Block變量的賦值格式為:

Block變量 = ^(參數(shù)列表){函數(shù)體}; // 這里的參數(shù)列表一定要和聲明時(shí)的參數(shù)列表一致

// 聲明
void(^block)();
// 賦值
block = ^(){
    NSLog(@"Hello World");
};
// 調(diào)用
block();

也可以在聲明時(shí)完成賦值

// 聲明、賦值
void(^block)() = ^(){
    NSLog(@"Hello World");
};
// 調(diào)用
block();

定義Block類型

前面提到過,Block是一種特殊的數(shù)據(jù)類型,我們可以使用 typedef 來定義 Block 類型,這樣我們就可以使用該類型來聲明很多相同的Block變量了。

typedef 返回類型(^Block名稱)(參數(shù)列表);

示例:

// 聲明一個(gè)Block類型
typedef void(^Block)();

// 使用定義兩個(gè)block變量
Block myBlock,myNewBlock;
// 賦值實(shí)現(xiàn)
myBlock = ^(){
    NSLog(@"Hello World");
};
myNewBlock = ^(){
    NSLog(@"Hello World, I am lolita0164.");
};
// 調(diào)用
myBlock();
myNewBlock();

ARC模式下簡(jiǎn)單應(yīng)用

  • 作為對(duì)象屬性實(shí)現(xiàn)消息傳遞

前面說到,Block保存一段代碼,在需要的時(shí)候調(diào)用。我們可以將使用Block的三個(gè)步驟拆開,實(shí)現(xiàn)消息傳遞、傳值功能。

// 定義Block類型
typedef void(^Block)(NSString *);

@interface Person : NSObject
// 聲明Block變量
@property (nonatomic, copy) Block myBlock;
-(void)sayHello;
@end

@implementation Person
-(void)sayHello{
    // Block調(diào)用
    self.myBlock(@"Hello, I am lolita0164");
}
@end

在定義聲明、調(diào)用之后,還缺少實(shí)現(xiàn)的部分,這一步通常由外部實(shí)現(xiàn)。

Person *p = [Person new];
// Block賦值實(shí)現(xiàn)
p.myBlock = ^(NSString *string) {
    NSLog(@"%@",string);
};
[p sayHello];

這樣,我們就可以在Block的賦值實(shí)現(xiàn)部分里拿到 p類里的數(shù)據(jù)了。

  • 作為函數(shù)參數(shù)實(shí)現(xiàn)數(shù)據(jù)回調(diào)

我們將之前的例子稍加改動(dòng)

// 定義Block類型
typedef void(^Block)(NSString *);
@interface Person : NSObject
// 將Block作為參數(shù)
-(void)sayHelloUseBlock:(Block)myBlock;
@end

@implementation Person
-(void)sayHelloUseBlock:(Block)myBlock{
    // Block調(diào)用
    myBlock(@"Hello, I am lolita0164");
}
@end

在外部進(jìn)行實(shí)現(xiàn)。

Person *p = [Person new];
// Block實(shí)現(xiàn)
[p sayHelloUseBlock:^(NSString *string) {
    NSLog(@"%@",string);
}];

作為參數(shù)和作為屬性傳遞消息,在應(yīng)用場(chǎng)景稍稍有些不同。
作為參數(shù)時(shí),通常和當(dāng)前的方法有著緊密的聯(lián)系,函數(shù)體內(nèi)部需要與調(diào)用的外部進(jìn)行交互。例如在請(qǐng)求方法中,經(jīng)常會(huì)使用到block進(jìn)行回調(diào),而這個(gè)block和當(dāng)前的方法關(guān)系緊密,通常是該方法的結(jié)果回調(diào)。又或者是方法執(zhí)行期間需要外部提供一定的信息,從而通過block獲取外部提供的數(shù)據(jù)。
作為屬性時(shí),通常是和當(dāng)前類相關(guān),作為類與類之間的交互代表。

  • 作為返回值實(shí)現(xiàn)鏈?zhǔn)秸Z法

將block作為返回值的經(jīng)典例子就是約束庫(kù) masonry,這個(gè)庫(kù)在做完每次約束設(shè)置之后通過 block 將實(shí)例再次回調(diào),就形成了鏈?zhǔn)秸Z法。

[view makeConstraints:^(MASConstraintMaker *make) {
    make.left.equalTo(view1.mas_right).offset(10); 
    make.top.equalTo(view1).offset(0);
    make.right.equalTo(-10);
    make.size.equalTo(viewWidth);
}];

下面通過創(chuàng)建顏色類來演示 block 作為參數(shù)的使用。

// block 作為返回值
+(UIColor* (^)(CGFloat, CGFloat, CGFloat))rgb{
    // block 的聲明和實(shí)現(xiàn)
    UIColor* (^rgbBlock)(CGFloat, CGFloat, CGFloat) = ^id(CGFloat r, CGFloat g, CGFloat b) {
        return [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1];
    };
    return rgbBlock;
}

解析

返回值是一個(gè)有返回值參數(shù)有三個(gè)的block:UIColor * (^)(CGFloat, CGFloat, CGFloat)。我們?cè)谠摲椒ǖ膬?nèi)部進(jìn)行了block的聲明和具體實(shí)現(xiàn),并且將其作為返回值返回了出去,那么外部在調(diào)用該方法之后接收到的是一個(gè)block,可以使用該值。

那么外部使用情況如下。

// 接收 block 類型
UIColor* (^colorBlock)(CGFloat, CGFloat, CGFloat) = [UIColor rgb];
// 使用 block 獲取到顏色
UIColor* color = colorBlock(10,33,65);
self.view.backgroundColor = color;

在丟棄不需要的部分后,代碼如下。

+(UIColor* (^)(CGFloat, CGFloat, CGFloat))rgb{
    return ^id(CGFloat r, CGFloat g, CGFloat b) {
        return [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1];
    };
}
// 使用block作為參數(shù)的方法
self.view.backgroundColor = UIColor.rgb(10, 33, 65);

Block 和 變量

Block訪問局部變量問題

block 內(nèi)部可以訪問局部變量。

int global = 100;
void (^Block)() = ^(){
    NSLog(@"global = %i", global);
};
Block(); // 輸出 "global = 100"

但是 block 會(huì)把變量 復(fù)制 為自己私有的const變量,也就是說block會(huì)捕獲棧上的變量(或指針),將其復(fù)制為自己私有的const變量,當(dāng)變量被修改時(shí),不會(huì)影響到block自己私有的const變量。

int global = 100;
void (^Block)() = ^(){
    NSLog(@"global = %i", global);
};
global = 101;
Block(); // 輸出 "global = 100"

在Block中不可以直接修改局部變量。

int global = 100;
void (^Block)() = ^(){
    global ++; // 這句報(bào)錯(cuò)
    NSLog(@"global = %i", global);
};
Block();

但是可以通過 __block 修飾符修改局部變量。

__block int global = 100;
void (^Block)() = ^(){
    NSLog(@"global = %i", global);
};
global = 101;
Block();   //輸出 "global = 101"
__block int global = 100;
void (^Block)() = ^(){
    global ++; // 這句正確
    NSLog(@"global = %i", global);
};
Block();   //輸出 "global = 101"

原因:在局部變量前使用 __block修飾 ,在Block定義時(shí)便是將局部變量的指針傳給Block變量所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)局部變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是可以修改的。

Block訪問全局變量、靜態(tài)變量問題

可以訪問和修改。

全局變量所占用的內(nèi)存只有一份,供所有函數(shù)共同調(diào)用,在Block定義時(shí)并未將全局變量的值或者指針傳給Block變量所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)全局變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是可以修改的。

在Block定義時(shí)便是將靜態(tài)變量的指針傳給Block變量所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)靜態(tài)變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是可以修改的。

ARC下的內(nèi)存管理

在ARC默認(rèn)情況下,Block的內(nèi)存存儲(chǔ)在堆中,ARC會(huì)自動(dòng)進(jìn)行內(nèi)存管理,我們只需要避免循環(huán)引用即可。

// 當(dāng)Block變量出了作用域,Block的內(nèi)存會(huì)被自動(dòng)釋放
void(^myBlock)() = ^{
    NSLog(@"------");
};
myBlock();

如果在Block中引用了外面的對(duì)象,會(huì)對(duì)所引用的對(duì)象進(jìn)行強(qiáng)引用,但是在Block被釋放時(shí)會(huì)自動(dòng)去掉對(duì)該對(duì)象的強(qiáng)引用,因此比并不會(huì)造成內(nèi)存泄漏問題。

Person *p = [[Person alloc] init];
void(^myBlock)() = ^{
    NSLog(@"------%@", p);
};
myBlock();
// Person對(duì)象在這里可以正常被釋放
// 注:這里的Block只是單方面的強(qiáng)引用,所以不會(huì)產(chǎn)生循環(huán)引用,也不會(huì)內(nèi)存泄漏

如果對(duì)象內(nèi)部引用一個(gè)Block屬性,而在Block內(nèi)部又訪問了該對(duì)象,那么會(huì)造成循環(huán)引用,導(dǎo)致內(nèi)存泄漏。

self.block = ^{
    NSLog(@"------%@", self);
};

解決辦法:使用一個(gè)弱引用的指針指向該對(duì)象,然后在Block內(nèi)部使用該弱引用指針來進(jìn)行操作,這樣就避免了Block對(duì)對(duì)象進(jìn)行強(qiáng)引用。

__weak typeof(self) weakSelf = self;
weakSelf.block = ^{
    NSLog(@"------%@", weakSelf);
};

提示:如果只是Block單方面地對(duì)外部變量進(jìn)行強(qiáng)引用,并不會(huì)造成內(nèi)存泄漏。

補(bǔ)充

1、聲明block屬性的時(shí)候?yàn)槭裁从胏opy呢?

在說明為什么要用copy前,先思考下block是存儲(chǔ)在棧區(qū)還是堆區(qū)呢?其實(shí)block有3種類型:

  1. 全局塊(_NSConcreteGlobalBlock)
  2. 棧塊(_NSConcreteStackBlock)
  3. 堆塊(_NSConcreteMallocBlock)

全局塊存儲(chǔ)在靜態(tài)區(qū)(也叫全局區(qū)),相當(dāng)于OC中的單例;棧塊存儲(chǔ)在棧區(qū),超出作用域則馬上被銷毀。堆塊存儲(chǔ)在堆區(qū)中,是一個(gè)帶引用計(jì)數(shù)的對(duì)象,需要自行管理其內(nèi)存。

關(guān)于內(nèi)存分配,請(qǐng)看這篇:C語言內(nèi)存分配。

怎么判斷一個(gè)block所在的存儲(chǔ)位置呢?

  • block不訪問外界變量(包括棧中和堆中的變量)

block既不在棧中也不在堆中,此時(shí)就為全局塊,ARC和MRC下都是如此。

  • block訪問外面變量

MRC環(huán)境下:默認(rèn)存儲(chǔ)在棧區(qū)
ARC環(huán)境下:默認(rèn)存儲(chǔ)在堆中,實(shí)際上是先放在棧區(qū),在ARC情況下自動(dòng)又拷貝到堆區(qū),自動(dòng)釋放

因此,使用 copy 修飾符的作用就是將block從棧區(qū)拷貝到堆區(qū)

為什么要這么做呢?官方給出的答案是:

復(fù)制到堆區(qū)的主要目的就是 保存 block 的狀態(tài),延長(zhǎng)其聲明周期。因?yàn)閎lock如果在棧上的話,其所屬的變量作用域結(jié)束,該block就被釋放掉了,block中的 __block 變量也同時(shí)被釋放掉了,為了解決超出作用域就被釋放的問題,我們就需要把block復(fù)制到堆中。

總結(jié)

OC 中的 block 是對(duì) C 語言的匿名函數(shù)的一種特性是實(shí)現(xiàn)。block 具有函數(shù)特性,同時(shí)也可以作為變量使用。block 可以作為屬性、參數(shù)、返回值使用。想要在 Block 內(nèi)部修改外部變量時(shí),需要使用 __Block 將變量指針傳遞給 block。在使用 Block 時(shí)需要特別注意內(nèi)存泄漏的問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • iOS代碼塊Block 概述 代碼塊Block是蘋果在iOS4開始引入的對(duì)C語言的擴(kuò)展,用來實(shí)現(xiàn)匿名函數(shù)的特性,B...
    smile刺客閱讀 2,375評(píng)論 2 26
  • 《Objective-C高級(jí)編程》這本書就講了三個(gè)東西:自動(dòng)引用計(jì)數(shù)、block、GCD,偏向于從原理上對(duì)這些內(nèi)容...
    WeiHing閱讀 9,907評(píng)論 10 69
  • iOS代碼塊Block 概述 代碼塊Block是蘋果在iOS4開始引入的對(duì)C語言的擴(kuò)展,用來實(shí)現(xiàn)匿名函數(shù)的特性,B...
    蚊香醬閱讀 59,693評(píng)論 61 439
  • 一: iOS Block的基本概念 1.1 概述 代碼塊Block是蘋果在iOS4開始引入的對(duì)C語言的擴(kuò)展,用來實(shí)...
    iYeso閱讀 250評(píng)論 0 0
  • 我家樓上的鄰居養(yǎng)狗, 每天晚上回家, 老公都要汪汪幾聲, 然后等到樓上的小狗聽到聲音后再回應(yīng)的汪汪幾聲, 老公才美...
    張娜0922閱讀 134評(píng)論 0 0