本文內(nèi)容:
- 什么是Blocks
- Block類型變量
- Block的實質(zhì)(帶有(截獲)自動變量值的匿名函數(shù))
- Block的存儲域
什么是Blocks
Blocks是C語言的擴充功能,可以用一句話表示這個功能:帶有自動變量(局部變量)的匿名函數(shù)
編譯后:就是在文件中的一個函數(shù)
# 在C中可能使用的變量
- 自動變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
其中,在后三種,在文件中的任何地方都能訪問到。
研究Blocks的重點就是:在Blocks中,怎么訪問、存儲、改變前兩種數(shù)據(jù)
“帶有自動變量值的匿名函數(shù)”這一概念并不僅指Blocks,還存在與其他編程語言,也被稱為:
Block: C+ Blocks、Smalltalk、Ruby
閉包(Closure):swift
lambda計算(λ計算,lambda calculus等):LISP、Python、C++ 11
Anonymous function(匿名函數(shù)):JavaScript
Block類型變量
完整形式的Block類型變量定義語法 與 C語言函數(shù)定義 僅有兩點不同:
- 沒有函數(shù)名:因為是匿名函數(shù)
- 帶有 ^ :返回值類型錢帶有'^'(插入記號,caret)記號,因為OS X、iOS中大量使用Block,便于查找
不完整形式的Block類型變量:可以省略返回值類型、參數(shù)列表
# C 函數(shù)指針 類型變量
int func (int count)
{
return count + 1;
}
int (*funcptr) (int) = &func;
# OC Block 類型變量
在Block語法下,可將Block語法賦值給 Block類型的變量。
在Blocks中的文檔中,“Block”既指源代碼中的`Block語法`,也指由Block語法`生成的值`
int (^blk) (int);
比較:
- 相比C而言,僅僅是將* 改成了 ^
- 調(diào)用起來沒有區(qū)別
Block的實質(zhì)
# 代碼反編譯看Block
clang(LLVM 編譯器)具有轉(zhuǎn)換為我們可讀源代碼的功能,我們可以通過-rewrite-objc將含有Block語法的源代碼變換為C++的源代碼,本質(zhì)上是C語言源代碼
clang -rewrite-objc 源代碼文件名
代碼轉(zhuǎn)換:
int main(){
void(^blk)(void)=^{ printf("Block\n");};
blk();
return 0;
}
源代碼通過clang可變換為以下形式:
//從其名稱可以聯(lián)想到某些標志、今后版本所需的區(qū)域以及函數(shù)指針
struct __block_impl {
void *isa;
int Flags;
int Reserved;
vold *Funcptr;
}
// Block類型變量對應(yīng)的結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl imp1;
struct __main_block_desc_0* Desc;
//默認構(gòu)造函數(shù),C++中的語法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
imp1.isa = &_NSConcreteStackBlock;
imp1.Flags = flags //不傳默認=0,Reserved默認也是0
imp1.FuncPtr = fp;
Desc = desc:
}
};
//該函數(shù)的參數(shù)__cself相當于C++實例方法中指向?qū)嵗陨淼淖兞縯his,或是Objective-C實例方法中指向?qū)ο笞陨淼淖兞縮elf
//即參數(shù)__cself為`指向Block值的變量`
static void __main_block_func_0(struct __main_block_impl_0* cself)
{
printf("Block\n");
}
//今后版本升級所需的區(qū)域和Block的大小
static struct __main_block_desc_0{
unsigned long reserved;
unsigned long Block_size;
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0) //block對應(yīng)結(jié)構(gòu)體的實例大小
};
int main()
{
//創(chuàng)建 構(gòu)造函數(shù)
/*
相當于(《OC 高級編程》中寫的):
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 * blk = &tmp;
相當于源碼:void(^blk)(void) = ^{printf("Block\n");};
@param __main_block_func_0 函數(shù)
@param &__main_block_desc_0_DATA 結(jié)構(gòu)體指針 "靜態(tài)全局變量"
*/
void (*blk)(void) = (void(*)(void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
//調(diào)用
/*
問題:blk明明是__main_block_impl_0結(jié)構(gòu)體類型指針的,為什么變成__block_impl結(jié)構(gòu)體類型指針了?
答:因為__block_impl作為__main_block_impl_0的首成員,所以兩個結(jié)構(gòu)體的首地址都是相同的,所以完全可以強轉(zhuǎn),只不過使用__block_impl就訪問不了__main_block_impl_0結(jié)構(gòu)體中其他的幾個成員變量,只能訪問__block_impl自己的。
*/
( (void (*)(struct __block_impl *)) ((struct __block_impl *)blk)->FuncPtr )((struct __block_impl *)blk);
return 0;
}
# &_NSConcreteStackBlock是什么
在講&_NSConcreteStackBlock與isa之前,先了解一下class、id這兩個關(guān)鍵字的定義:
struct objc_class {
Class isa;
}
typedef struct objc_class * class;
struct objc_object{
Class isa;
}
typedef struct objc_object * id;
isa:是一個Class 類型的指針.
1. 每個實例對象有個isa的指針,他指向?qū)ο蟮念怌lass
2. Class里也有個isa的指針, 指向meteClass(元類)。
3. 元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).
4. 根元類的isa指針指向本身,這樣形成了一個封閉的內(nèi)循環(huán)。
元類保存了類方法的列表。當類方法被調(diào)用時,先會從本身查找類方法的實現(xiàn),如果沒有,元類會向他父類查找該方法。
綜上:
- 每個對象、類本質(zhì)上都是結(jié)構(gòu)體,都有isa指針
- 每個結(jié)構(gòu)體都持有對象、類的屬性、方法的名稱、方法的實現(xiàn)(函數(shù)指針)、以及父類的指針
-
&_NSConcreteStackBlock相當于Block isa指向的類,在將Block作為OC對象處理時,關(guān)于該類的信息放置于_NSConcreteStackBlock中.
總結(jié):Block即為Objective-C的對象,C中的結(jié)構(gòu)體,底層實現(xiàn)是C語言中的函數(shù)(下一篇Objective-C Block篇(二) : Block捕獲自動變量機制中將通過講解Block的捕獲機制,詳細證明Block就是函數(shù))
Block的存儲域
Block存在三種不同作用域的對象:
- _NSConcreteStackBlock設(shè)置在棧上
- _NSConcreteGlobalBlock設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))
- _NSConcreteMallocBlock設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(即堆)中
# _NSConcreteGlobalBlock
- 像聲明全局變量一樣聲明Block變量時
- 捕獲全局變量、靜態(tài)自動變量、靜態(tài)全局變量時(
此時,如果同時滿足_NSConcreteMallocBlock的條件,那_NSConcreteMallocBlock優(yōu)先) - 當不捕獲任何自動變量時
# _NSConcreteMallocBlock
- 調(diào)用Block的copy實例方法時
- Block作為函數(shù)返回值返回時
- 將Block賦值給附有__strong修飾符id類型的類或Block類型變量時
- 在方法名含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API傳遞Block時
調(diào)用objc_retainBlock()方法,實際上也就是Block_copy函數(shù)
- 對棧上的Block執(zhí)行copy方法,會從棧復制到堆
- 對堆上的Block執(zhí)行copy方法,引用計數(shù)增加
- 對全局的Block執(zhí)行copy方法,什么也不會發(fā)生
所以,不管Block配置在何處,用copy方法都不會引起任何問題,不確定存儲域的時候可以直接copy
# _NSConcreteStackBlock
除了以上講述的情況,其他創(chuàng)建的Block存儲于都是在棧上
ARC 無效時,一般需要我們手動將Block從棧復制到堆,然后手動釋放。
- -retain
- -copy/Block_copy(block)
- -release/Block_release(block)
對于棧上的block調(diào)用retain是無效的,只有先copy到堆上,再copy才會有效果