筆記:Objective-C高級(jí)編程 第二章 Blocks

t
1862021-459edd5ef79e8373.jpg

第二章 Blocks

2.1 Blocks 概要

2.1.1 什么是Blocks

Blocks是C語(yǔ)言的擴(kuò)充功能,帶有自動(dòng)變量(局部變量)的匿名函數(shù)。
C語(yǔ)言的函數(shù)中可能使用的變量

  • 自動(dòng)變量(局部變量)
  • 函數(shù)的參數(shù)
  • 靜態(tài)變量(靜態(tài)局部變量)
  • 靜態(tài)全局變量
  • 全局變量
    其中,在函數(shù)的多次調(diào)用之間能夠傳遞值的變量有:
  • 靜態(tài)變量(靜態(tài)局部變量)
  • 靜態(tài)全局變量
  • 全局變量

2.2 Blocks 模式

2.2.1 Blocks語(yǔ)法

完整形式的Block語(yǔ)法與一般的C語(yǔ)言函數(shù)定義相比,僅有二點(diǎn)不同:

  • 沒有函數(shù)名
  • 帶有^(插入記號(hào) caret)

完整形式:
^int (int count){ return count+1;}
省略返回值類型
^(int count){ return count+1;}
省略返回值和參數(shù)類型
^{printf("Block");}

聲明Block類型變量的示例:
int (^blk) (int);
Block類型變量的用途與C語(yǔ)言的變量作用完全相同

Block賦值:
int (^blk) (int) = ^(int count){return count+1;};
int (^blk1) (int) = blk;
int (^blk2) (int);
blk2 = blk1;
在函數(shù)參數(shù)中使用Block類型量可以向函數(shù)傳遞Block:
void func(int (^blk) (int))
{
}
在函數(shù)返回值中指定Block類型,可以將Block作為函數(shù)的返回值返回:

在函數(shù)參數(shù)和返回值中使用Block類型變量時(shí) 記述方式極為復(fù)雜,可以用命typedef來解決問題:
typedef int (^blk_t) (int);

Block的指針類型變量:
typedef int (^blk_t)(int);
blk_t blk = ^(int count) {return count+1;};
blk_t *blkptr = &blk;
(*blkptr)(10);

2.2.3 截獲自動(dòng)變量值

"帶有自動(dòng)變量值"在Blocks中表現(xiàn)為“截獲自動(dòng)變量值”,即保存該自動(dòng)變量的瞬間值。

2.2.4 __block說明符

若想在Block語(yǔ)法的表達(dá)式中將值賦給在Block語(yǔ)法外聲明的自動(dòng)變量,需要在該自動(dòng)變量上附加__block說明符
__block int val = 0; //該變量稱為__block變量

2.2.5 截獲的自動(dòng)變量

int val = 0;
void (^blk) (void) = ^{val = 1;};//會(huì)產(chǎn)生編譯錯(cuò)誤

截獲Objective-C對(duì)象:
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
[array addObject:obj];//不會(huì)報(bào)錯(cuò)
array = [[NSMutableArray alloc] init];//報(bào)錯(cuò)
}
在使用C語(yǔ)言數(shù)組時(shí)必須小心使用其指針:
const char text[] = "hello";//區(qū)別
void (^block)(void) = ^{
printf("%c\n",text[2]);//報(bào)錯(cuò)
};

const char *text = "hello";//區(qū)別
void (^block)(void) = ^{
printf("%c\n",text[2]);//不會(huì)報(bào)錯(cuò)
};

2.3 Block的實(shí)現(xiàn)

2.3.1 Block的實(shí)質(zhì)

所謂Block就是Objective_C對(duì)象
struct __main_block_impl_0 *__cself;
參數(shù)__cself是__main_block_impl_0結(jié)構(gòu)體的指針
objc_object與obj_class結(jié)構(gòu)體相同
objc_object與obj_class結(jié)構(gòu)體歸根結(jié)底是在各個(gè)對(duì)象和類的實(shí)現(xiàn)中使用的最基本的結(jié)構(gòu)體
struct __main_block_impl_0{
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
struct __block_impl{
void *isa;
int Flags;//標(biāo)志
int Reserved;//今后版本升級(jí)所需的區(qū)域以及函數(shù)指針
void *FuncPtr;;//函數(shù)指針
};
struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
};
typedef struct objc_object{
Class isa;
}*id;
typedef struct objc_class *Class;
struct objc_class{
Class isa;
};
struct class_t{
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t date_NEVER_USE;
};
各類的結(jié)構(gòu)體就是基于objc_class結(jié)構(gòu)體的class_t結(jié)構(gòu)體。

^{printf("Block")};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block");
}
根據(jù)Block語(yǔ)法所屬的函數(shù)名(此處為main)和該Block語(yǔ)法在該函數(shù)出現(xiàn)的順序值(此處為0)來給經(jīng)clang變換的函數(shù)換名。

2.3.2 截獲自動(dòng)變量值

Block語(yǔ)法表達(dá)式中使用的自動(dòng)變量被作為成員變量追加到了__main_block_impl_0結(jié)構(gòu)體中
Blocks的自動(dòng)變量截獲只針對(duì)Block中使用的自動(dòng)變量
Block語(yǔ)法表達(dá)式所使用的自動(dòng)變量值被保存到Block的結(jié)構(gòu)體實(shí)例中

2.3.3 __block存儲(chǔ)域類說明符

C語(yǔ)言存儲(chǔ)域類說明符

  • typedef
  • extern
  • static//表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)中
  • auto //表示作為自動(dòng)變量存儲(chǔ)在棧中
  • register
    struct __Block_byref_val_0{
    void *__isa;
    __Block_byref_val_0 *__forwarding;
    int __flags;
    int __size;
    int val;
    }

__block變量也同Block一樣變成_Block_byref_val_0結(jié)構(gòu)體類型的自動(dòng)變量,即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例

在Block中向靜態(tài)變量賦值時(shí) 使用了指向該靜態(tài)變量的指針。而向__block變量賦值要比這個(gè)更為復(fù)雜。Block的__main_block_impl_0結(jié)構(gòu)體實(shí)例持有指向_block變量的_Block_byref_val_0結(jié)構(gòu)體實(shí)例的指針。
_Block_byref_val_0結(jié)構(gòu)體實(shí)例的成員變量_forwarding持有指向該實(shí)例自身的指針,通過成員變量__forwarding訪問成員變量val
__block變量的_Block_byref_val_0結(jié)構(gòu)體并不在Block用__main_block_impl_0結(jié)構(gòu)體中 而是使用__Block_byref_val_0結(jié)構(gòu)體實(shí)例的指針,這樣做是為了在多個(gè)Block中使用__block變量

2.3.4 Block存儲(chǔ)域

Block與__block變量的實(shí)質(zhì)

名稱 實(shí)質(zhì)
Block 棧上Block的結(jié)構(gòu)體實(shí)例
__block變量 棧上__block變量的結(jié)構(gòu)體實(shí)例

Block的類

設(shè)置對(duì)象的存儲(chǔ)域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域(.data區(qū))
_NSConcreteMallocBlock

另還有一個(gè)程序區(qū)域(.text區(qū))

在以下情況下,Block為_NSConcreteGlobalBlock類對(duì)象

  • 記述全局變量的地方有Block語(yǔ)法時(shí)
  • Block語(yǔ)法的表達(dá)式中不使用截獲的自動(dòng)變量
    除此之外的Block語(yǔ)法生成的Block為_NSConcreteStackBlock類對(duì)象 且設(shè)置在棧上。

Block超出變量作用域可存在的原因:
Block提供了將Block和__block變量從棧上復(fù)制到堆上的方法來解決這個(gè)問題。

編譯器不能進(jìn)行判斷 將Block從棧上復(fù)制到堆上, 所以需使用copy方法:

  • 向方法或函數(shù)的參數(shù)中傳遞Block時(shí)

編譯器可以自行判斷 自動(dòng)生成將Block從棧上復(fù)制到堆上的代碼:

  • Block作為函數(shù)返回值時(shí)
  • Cocoa框架的方法且方法名中含有usingBlock等時(shí)
  • Grand Central Dispatch的API
    注:從棧上復(fù)制到堆上是相當(dāng)消耗CPU的

2.3.5 __block變量存儲(chǔ)域

__block變量用結(jié)構(gòu)體成員變量__forwarding存在的原因:
通過Block的復(fù)制__block變量也從棧復(fù)制到堆,此時(shí)可同時(shí)訪問棧上的__block變量和堆上的__block變量。
棧上的__block變量用結(jié)構(gòu)體實(shí)例在__block變量從棧復(fù)制到堆上時(shí),會(huì)將成員變量__forwarding的值替換為復(fù)制目標(biāo)堆上的__block變量用結(jié)構(gòu)體實(shí)例的地址。

2.3.6 截獲對(duì)象

C語(yǔ)言結(jié)構(gòu)體不能含有附有__strong修飾符的變量,因?yàn)榫幾g器不知道應(yīng)何時(shí)進(jìn)行C語(yǔ)言結(jié)構(gòu)體的初始化和廢棄操作 不能很好地管理內(nèi)存。
但是Object_C的運(yùn)行庫(kù)能夠準(zhǔn)確把握Block從棧復(fù)制到堆以及堆上的Block被廢棄的時(shí)機(jī)。
什么時(shí)候棧上的Block會(huì)復(fù)制到堆呢?

  • 調(diào)用Block的copy實(shí)例方法
  • Block作為函數(shù)返回值返回時(shí)
  • 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時(shí)
  • 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時(shí)

Block中使用對(duì)象類型自動(dòng)變量時(shí),除以下情形外,推薦調(diào)用Block的copy實(shí)例方法:

  • Block作為函數(shù)返回值返回時(shí)
  • 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時(shí)
  • 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時(shí)

2.3.7 __block變量和對(duì)象

沒有設(shè)定__autoreleasing修飾符與Block同時(shí)使用的方法:
__block id __autoreleasing obj = [[NSObject alloc] init];//編譯器報(bào)錯(cuò)

2.3.7 循環(huán)引用

避免循環(huán)引用二種方式:

  • 使用__block
  • 使用__weak修飾符及__unsafe_unretained修飾符

使用_block變量的優(yōu)點(diǎn):

  • 通過__block變量可控制對(duì)象的持有期間
  • 在不能使用__weak修飾符的環(huán)境中可以不需使用__unsafe_unretained修飾符即可(不必?fù)?dān)心懸垂指針)
  • 在執(zhí)行Block時(shí)可動(dòng)態(tài)地決定是否將nil或其它對(duì)象賦值在__block變量中

使用__block變量的缺點(diǎn)如下:

  • 為了避免循環(huán)引用必須執(zhí)行Block

存在執(zhí)行了Block語(yǔ)法 卻不執(zhí)行Block的路徑時(shí),無(wú)法避免循環(huán)引用.

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

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

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