iOS block的使用與深入理解

一、block的基本語法與使用

概述
Block是蘋果在iOS4開始引入的對C語言的擴(kuò)展,用來實現(xiàn)匿名函數(shù)的特性,Block是一種特殊的數(shù)據(jù)類型,其可以正常定義變量、作為參數(shù)、作為返回值,特殊地,Block還可以保存一段代碼,在需要的時候調(diào)用,目前Block已經(jīng)廣泛應(yīng)用于iOS開發(fā)中,常用于GCD、動畫、排序及各類回調(diào)。

block的申明,跟C語言中的函數(shù)申明類似

void (^blockName) (int parameter);
//void代表返回值類型,后面一次是block名,參數(shù)

block的賦值,跟C語言中函數(shù)的定義類似

blockName = ^(int a){  //這里參數(shù)要和定義的參數(shù)類型一致
        NSLog(@"parameter=%d",a);
    };

block的調(diào)用

blockName(1);   //輸出  ‘parameter=1’

使用typedef定義Block類型

// 定義一種無返回值無參數(shù)列表的Block類型
typedef void(^SayHello)();

// 我們可以像OC中聲明變量一樣使用Block類型SayHello來聲明變量
SayHello hello = ^(){
    NSLog(@"hello");
};

// 調(diào)用后控制臺輸出"hello"
hello();

block作為函數(shù)的參數(shù)

// 1.使用typedef定義Block類型
typedef int(^MyBlock)(int, int);

// 2.定義一個形參為Block的OC函數(shù)
- (void)useBlockForOC:(MyBlock)aBlock
{
    NSLog(@"result = %d", aBlock(300,200));
}

// 3.聲明并賦值定義一個Block變量
MyBlock addBlock = ^(int x, int y){
    return x+y;
};

// 4.以Block作為函數(shù)參數(shù),把Block像對象一樣傳遞
[self useBlockForOC:addBlock];

// 將第3點和第4點合并一起,以內(nèi)聯(lián)定義的Block作為函數(shù)參數(shù)
[self useBlockForOC:^(int x, int y){
    return x+y;
}];

在block中訪問局部變量的時候注意
在聲明Block之后、調(diào)用Block之前對局部變量進(jìn)行修改,在調(diào)用Block時局部變量值是修改之前的舊值

// 聲明局部變量a
int a = 100;

void(^myBlock)() = ^{
    NSLog(@"a = %d", a);
};
a = 101;
// 調(diào)用后控制臺輸出"a = 100"
myBlock();

在block中不可以修改局部變量
原因后面再詳細(xì)介紹

// 聲明局部變量global
int a = 100;

void(^myBlock)() = ^{
    a ++; // 這句報錯
    NSLog(@"a = %d", a);
};
// 調(diào)用后控制臺輸出"a = 100"
myBlock();

block中可以訪問全局變量

int global = 100; // 聲明全局變量global

void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
global = 101;
// 調(diào)用后控制臺輸出"global = 101"
myBlock();

在實際應(yīng)用中,從一個頁面?zhèn)髦档搅硪粋€頁面的情況比較常見,就簡單介紹一下:
一、在第二個視圖控制器的.h文件中定義聲明Block屬性

typedef void (^ReturnTextBlock)(NSString *showText); //申明一個block并重新定義一個名字
@interface TextFieldViewController : UIViewController
@property (nonatomic, copy) ReturnTextBlock returnTextBlock;  //定義一個block屬性
@end

二、實現(xiàn)第二個視圖控制器的方法

//在需要的方法中調(diào)用
if (self.returnTextBlock != nil) {
    self.returnTextBlock(self.inputTF.text);  //block的調(diào)用
}

三、在第一個視圖中獲得第二個視圖控制器,并且用第二個視圖控制器來調(diào)用定義的屬性

//在控制跳轉(zhuǎn)到第二個頁面的地方調(diào)用
TextFieldViewController *tfVC = [[TextFieldViewController alloc]init];
[tfVC returnText:^(NSString *showText) {
        self.showLabel.text = showText;
}];  //block的定義
 [self.navigationController pushViewController:tfVC animated:YES]; 

可能剛學(xué)iOS的同學(xué)會有點懵,把這里的邏輯梳理一下。首先在第二個頁面定義了一個block變量,然后在需要的地方(比如點擊返回第一個頁面)調(diào)用了這個block,把UITextField中的內(nèi)容傳入了進(jìn)去,但是block的定義是在第一個頁面定義的,并且這里定義的內(nèi)容是把傳過來的內(nèi)容(也就是第二個頁面UITextField的內(nèi)容)顯示在label上面。

二、block的深入理解

塊本身其實就是一個對象,在存放塊對象的內(nèi)存區(qū)域中,首個變量是指向Class對象的指針(isa指針)

首先要說明一下,block在創(chuàng)建的時候默認(rèn)是創(chuàng)建在棧中的,這時候不能保證block的生存周期,所以我們在聲明一個block屬性的時候往往是用copy關(guān)鍵字,使用copy后會吧block拷貝在堆內(nèi)存中,這時候才能保證block的生存周期。

現(xiàn)在我們來看看block的底層實現(xiàn)

  • 常規(guī)block訪問局部變量的情況
// OC代碼如下
void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};

// 轉(zhuǎn)為C++代碼如下
void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, global));

// 將變量類型精簡之后C++代碼如下,我們發(fā)現(xiàn)Block變量實際上就是一個指向結(jié)構(gòu)體__main_block_impl_0的指針,而結(jié)構(gòu)體的第三個元素是局部變量global的值
void(*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, global);

// 我們看一下結(jié)構(gòu)體__main_block_impl_0的代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int global;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// 在OC中調(diào)用Block的方法轉(zhuǎn)為C++代碼如下,實際上是指向結(jié)構(gòu)體的指針myBlock訪問其FuncPtr元素,在定義Block時為FuncPtr元素傳進(jìn)去的__main_block_func_0方法
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

// __main_block_func_0方法代碼如下,由此可見NSLog的global正是定義Block時為結(jié)構(gòu)體傳進(jìn)去的局部變量global的值
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int global = __cself->global; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6y_vkd9wnv13pz6lc_h8phss0jw0000gn_T_main_d5d9eb_mi_0, global);
}

這是block訪問局部變量的時候,由此可知,在Block定義時便是將局部變量的值傳給Block變量所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對局部變量進(jìn)行修改并不會影響B(tài)lock內(nèi)部的值,同時內(nèi)部的值也是不可修改的。

  • 在局部變量前使用下劃線下劃線block修飾
//oc代碼還是不變,只是將global前加一個__block關(guān)鍵字
// 轉(zhuǎn)為C++代碼如下
void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_global_0 *)&global, 570425344));

// 將變量類型精簡之后C++代碼如下,我們發(fā)現(xiàn)Block變量實際上就是一個指向結(jié)構(gòu)體__main_block_impl_0的指針,而結(jié)構(gòu)體的第三個元素是局部變量global的指針
void(*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &global, 570425344);

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容