Block語法?
Block可以認為是一個匿名函數。語法聲明如下:
return_type (^block_name)(parameters)?
例如:
double (^multiplyTwoValues)(double, double);?
Block字面值的寫法:
^ (double firstValue, double secondValue) {?
return firstValue *secondValue;?
}?
上面寫法省略了返回值的類型,可以顯示的指出返回值類型。
typedef double (^MultiplyTwoVlaues)(double, double);?
MultiplyTwoVlaues mtv = ^(double firstValue, double secondValue){?
return firstValue * secondValue;?
};?
NSLog(@"%f", mtv(3, 4));?
Block也是一個Objective-C對象,可以用于賦值,當參數傳遞,也可以放在集合中。
數據結構定義?
block的數據結構定義:
block?
對應的結構體定義如下:
struct Block_descriptor {?
unsigned long int reserved;?
unsigned long int size;?
void (*copy)(void *dst, void *src);?
void (*dispose)(void *);?
};?
struct Block_layout {?
void *isa;?
int flags;?
int reserved;?
void (*invoke)(void *, ...);?
struct Block_descriptor *descriptor;?
/* Imported variables. */?
};?
block實際上由6部分組成:
isa指針,所有對象都有該指針,用來指向對象相關實現。?
flags,用于按位表示一些block附加信息。?
reserved, 保留變量。?
invoke, 函數指針,指向具體的block實現的函數調用地址。?
descriptor, 表示該block的附加描述信息,主要是size的大小,以及copy和dispose函數指針。?
variables,capture過來的變量,block能夠訪問它外部的局部變量,就是因為將這些變量復制到了結構體中。?
Block分類?
在Objective-C中,一共有3種類型的Block:
_NSConcreteGlobalBlock全局的靜態block,不會訪問任何外部變量。?
_NSConcreteStackBlock 保存在棧中的block,當函數返回時會被銷毀。?
_NSConcreateMallocBlock 保存在堆中的block,當引用計數為0時會被銷毀。?
Clang?
為了研究編譯器是如何實現block的,需要使用clang,clang命令,可以將Objective-C的源碼改寫成C語言,命令如下:
clang -rewrite-objc xxx.c?
Block實現?
全局的靜態Block實現?
新建一個globalBlock.c的源文件:
#include ?
int main()?
{?
^{?
printf("Hello, World!/n");?
} ();?
return 0;?
}?
在文件所在的文件夾,使用命令:clang -rewrite-objc globalBlock.c,在目錄中得到一個名為globalBlock.c的文件。其中關鍵代碼:
struct __block_impl {?
void *isa;?
int Flags;?
int Reserved;?
void *FuncPtr;?
};?
struct __main_block_impl_0 {?
struct __block_impl impl;?
struct __main_block_desc_0* Desc;?
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {?
impl.isa = &;_NSConcreteStackBlock;?
impl.Flags = flags;?
impl.FuncPtr = fp;?
Desc = desc;?
}?
};?
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {?
printf("Hello, World!/n");?
}?
static struct __main_block_desc_0 {?
size_t reserved;?
size_t Block_size;?
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};?
int main()?
{?
((void (*)())&;__main_block_impl_0((void *)__main_block_func_0, &;__main_block_desc_0_DATA)) ();?
return 0;?
}?
下面具體看一下是如何實現的。__main_block_impl_0就是block的實現,從中可以看出:
一個block實際上就是一個對象,主要由 isa、 impl和 descriptor組成。?
在LLVM實現中,開啟ARC時。block的 isa應該是 _NSConcreteGlobalBlock類型。因為使用clang命令并沒有開啟ARC,所以還是 _NSConreteStackBlock類型。?
impl是實際的函數指針。它指向 __main_block_func_0。其實, impl就是 invoke變量。?
descriptor 是描述當前這個block的附加信息,包括大小,需要capture和dispose的變量列表等。?
靜態Block實現?
新建一個名為 stackBlock.c的文件:
#include ?
int main()?
{?
int a = 100;?
void (^stackBlock)(void) = ^{?
printf("%d/n", a);?
};?
stackBlock();?
return 0;?
}?
使用clang命令:
struct __main_block_impl_0 {?
struct __block_impl impl;?
struct __main_block_desc_0* Desc;?
int a;?
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {?
impl.isa = &;_NSConcreteStackBlock;?
impl.Flags = flags;?
impl.FuncPtr = fp;?
Desc = desc;?
}?
};?
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {?
int a = __cself->a; // bound by copy?
printf("%d/n", a);?
}?
static struct __main_block_desc_0 {?
size_t reserved;?
size_t Block_size;?
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};?
int main()?
{?
int a = 100;?
void (*stackBlock)(void) = ((void (*)())&;__main_block_impl_0((void *)__main_block_func_0, &;__main_block_desc_0_DATA, a));?
((void (*)(__block_impl *))((__block_impl *)stackBlock)->FuncPtr)((__block_impl *)stackBlock);?
return 0;?
}?
本例中,
isa指向_NSConcreteStackBlock,這是分配在棧上的實例。?
__main_block_impl_0中增加了一個變量a, 在block中引用的變量a實際是在聲明block時,被復制到 __main_block_impl_0結構體中的那個變量a。所以,在block內部修改變量a的內容, 不會影響外部變量a。?
__main_block_impl_0增加了變量a, 所有結構體大小變了,該結構體被寫在 __main_block_desc_0中。?
修改上面的代碼,在變量前面添加__block關鍵字:
#include ?
int main()?
{?
__block int i = 100;?
void (^stackBlock)(void) = ^{?
printf("%d/n", i);?
i = 10;?
};?
stackBlock();?
return 0;?
}?
轉換代碼:
struct __Block_byref_i_0 {?
void *__isa;?
__Block_byref_i_0 *__forwarding;?
int __flags;?
int __size;?
int i;?
};?
struct __main_block_impl_0 {?
struct __block_impl impl;?
struct __main_block_desc_0* Desc;?
__Block_byref_i_0 *i; // by ref?
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {?
impl.isa = &;_NSConcreteStackBlock;?
impl.Flags = flags;?
impl.FuncPtr = fp;?
Desc = desc;?
}?
};?
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {?
__Block_byref_i_0 *i = __cself->i; // bound by ref?
printf("%d/n", (i->__forwarding->i));?
(i->__forwarding->i) = 10;?
}?
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&;dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}?
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}?
static struct __main_block_desc_0 {?
size_t reserved;?
size_t Block_size;?
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);?
void (*dispose)(struct __main_block_impl_0*);?
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};?
int main()?
{?
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&;i, 0, sizeof(__Block_byref_i_0), 100};?
void (*stackBlock)(void) = ((void (*)())&;__main_block_impl_0((void *)__main_block_func_0, &;__main_block_desc_0_DATA, (__Block_byref_i_0 *)&;i, 570425344));?
((void (*)(__block_impl *))((__block_impl *)stackBlock)->FuncPtr)((__block_impl *)stackBlock);?
return 0;?
}?
代碼可以看到:
源碼中添加了一個名為 __Block_byref_i_0的結構體,用來保存需要capture并且修改的變量i。?
在 __main_block_impl_0中引用的 __Block_byref_i_0的結構體指針,這樣就可以達到修改外部變量的作用。?
__Block_byref_i_0結構體中帶有isa,說明也是一個對象。?
我們需要負責 __Block_byref_i_0結構體相關的內存管理,所以 __main_block_desc_0中增加了copy和dispose函數指針,對于在調用前后修改相應的引用計數。?
堆Block實現?
NSConcreteMallocBlock類型的block通常不會再源碼中直接出現,默認當block被copy的時候,才會將block復制到堆中。以下是block被copy時的代碼(來自這里),在第8步,目標block類型被修改為_NSConcreteMallocBlock。
static void *_Block_copy_internal(const void *arg, const int flags) {?
struct Block_layout *aBlock;?
const bool wantsOne = (WANTS_ONE &; flags) == WANTS_ONE;?
// 1?
if (!arg) return NULL;?
// 2?
aBlock = (struct Block_layout *)arg;?
// 3?
if (aBlock->flags &; BLOCK_NEEDS_FREE) {?
// latches on high?
latching_incr_int(&;aBlock->flags);?
return aBlock;?
}?
// 4?
else if (aBlock->flags &; BLOCK_IS_GLOBAL) {?
return aBlock;?
}?
// 5?
struct Block_layout *result = malloc(aBlock->descriptor->size);?
if (!result) return (void *)0;?
// 6?
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first?
// 7?
result->flags &;= ~(BLOCK_REFCOUNT_MASK); // XXX not needed?
result->flags |= BLOCK_NEEDS_FREE | 1;?
// 8?
result->isa = _NSConcreteMallocBlock;?
// 9?
if (result->flags &; BLOCK_HAS_COPY_DISPOSE) {?
(*aBlock->descriptor->copy)(result, aBlock); // do fixup?
}?
return result;?
}?
變量的復制?
對于block外部變量的引用,block默認是將其復制到其數據結構中來實現訪問的。?
對于__block修飾的外部變量引用,block是復制其引用地址來實現訪問的。?
另外,可以參考《招聘一個靠譜的iOS (下)13、14題》。
ARC,MRC 對Block類型的影響?
在ARC開啟的情況下,將只會有NSConcreteGlobalBlock和NSConcreteMallocBlock類型的block。原本NSConreteStackBlock被NSConcreteMallocBlock類型替代。
在Block中,如果只使用全局或靜態變量或者不是用外部變量,那么Block塊的代碼會存儲在全局區。
在ARC中
如果使用外部變量,Block塊的代碼會存儲在堆區。?
在MRC中
如果使用外部變量,Block塊的代碼會存儲在棧區。?
Block默認情況下不能修改外部變量,只能讀取外部變量。
在ARC中
外部變量在堆中,這個變量在Block塊內與在Block塊外地址相同。?
外部變量在棧中,這個變量會被copy到為Block代碼塊所分配的堆中。?
在MRC中
外部變量在堆中,這個變量在Block塊內與在Block塊外地址相同。?
外部變量在棧中,這個變量會被copy到為Block代碼塊所分配的棧中。?
如果需要修改外部變量,需要在外部變量前聲明__block。
在ARC中
外部變量存在堆中,這個變量在Block塊內與Block塊外地址相同。?
外部變量存在棧中,這個變量會被轉移到堆中,不是復制。?
在MRC中
外部變量存在堆中,這個變量在Block塊內與Block塊外地址相同。?
外部變量存在棧中,這個變量在Block塊內與Block塊外地址相同。?
關于Block的面試題?
使用block時什么情況會發生引用循環,如何解決??
在block內如何修改block外部變量??
使用系統的某些block api(如UIView的block版本寫動畫時),是否也考慮引用循環問題??