基本語法:
^返回值類型 參數列表 表達式
其中返回值類型和參數列表均可省略!
使用Block語法將Block 賦值為 Block類型變量
int (^blk)(int) = ^(int count){ return count +1;};
由"^"開始的Block語法生成的Block被賦值給亦是blk中,因為與通常的變量相同,當然可以進行變量賦值
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;
使用 typedef 簡化block代碼
typedef int (^blk_t)(int); //定義一個blk_t類型變量
//作用可作為參數
void func(blk_t blk){
}
//作為參數返回值
blk_t func(){
}
_block說明符 -- 在賦值的時候需要加上__block說明符
__block int val = 0; void (^blk)(void) = ^{val = 1;}; //只有加上__block,才可以在Block中修改值!
下面的代碼不是賦值,看起來只是使用數組,好像沒有總是,但運行會報錯!
const char text[] = "hello";
void ()(void) = ^{
printf("%c\n",text[2]);
};
這是因為在現在的Block中,並沒有實現對C語言數組的截獲,使用指針可以解決該問題
`const char *text = "hello";
Block的實質
通過 clang -rewrite-objc 源代碼文件名 可將Block轉換成C++源代碼
int main()
{
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
通過轉換變成差不多有10萬行代碼,以下形式
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
...
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; //由於轉換後源代碼中加入其構造函數,其實結構體就上面兩個變量
//初始化__main_block_impl_0結構體的構造函數
__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) {//__csself就是指的Block本身
printf("Block\n");} //就是源碼中的^{printf("Block\n");};
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};//分配Block的空間
int main(int argc, const char * argv[]) {//主函數
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//這行為將棧上生成的__main_block_impl_0結構體實例的指針賦值給指針變量blk
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);//去掉轉換部分(*blk->imp1.FuncPtr)(blk);這行為調用 blk();
return 0;
}
- __main_block_func_0 名稱的由來是因為block在main函數中且是第一次出現所以後面是0
- 主函數main中構造函數的調用可簡化為->
struct __main_block_imp1_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_imp1_0 *blk = &tmp;
- isa = &_NSConcreteStackBlock的理解
typedef struct objc_object {
Class isa;
} *id;
struct objc_class {
Class isa;
};
假設通過NSObject生成的一個結構體實例中會通過變量isa保持該類的結構體實例指針,如下圖所示
所以_NSConcreteStackBlock就相當於class_t結構體實例,因為Block為OC的對象,關於該類的信息放置於_NSConcreteStackBlock中,於是將其賦值給isa指針!
Block的實質-在Block修改外面的值
__block int val = 7;
void (^blk)(void) = ^{val = 1;};
轉換後的代碼如下
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1; //通過__forwarding拿到__Block_byref_val_0實例的指針
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 7};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
- 加了__block的變量變成了__Block_byref_val_0結構體實例,該實例的成員變量__forwarding指向該實例的指針,可通過此指針
所以上面賦值是這樣的形式--> (val->__forwarding->val) = 1
Block 存儲域
Block類型分以下幾種
- _NSConcreteStackBlock 設置在棧上的Block
- _NSConcreteGlobalBlock 設置在數據區(.data區)
- _NSConcreteMallocBlock malloc函數分配的內存塊(堆區)
以下源代碼生成並持有NSMutableArray類的對象,由於附有__strong修飾符的賦值變量作用域立即結束,因此對象被立即釋放並廢棄
{
id array = [[NSMutableArray alloc]init];
}
我們來看一下Block語法中使用該變量array的代碼
int main(int argc, const char * argv[]) {
typedef void (^blk_t)(id);
blk_t blk;
{
id array = [[NSMutableArray alloc]init];//如果用__block id __weak array2 = array;然後下面用array2的話,會因為走出大括號後,array的強引用消失,對象不存在,將array2指針變為Nil,會導致大括號後的打印全是0
blk = [^(id obj){
[array addObject:obj];
NSLog(@"array count = %ld",[array count]);
}copy];
}
blk([[NSObject alloc]init]);
blk([[NSObject alloc]init]);
blk([[NSObject alloc]init]);
return 0;
}
轉換後代碼如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x0_xwklvzm967x54vp3bvrp8b2m0000gn_T_main_ea6c83_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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*); //retain函數的實現在上面
void (*dispose)(struct __main_block_impl_0*);//release函數的實現在上面
}
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
typedef int (*blk_t)(id);
blk_t blk;
{
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
}
((int (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((int (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
((int (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
return 0;
}
- _Block_object_assign 相當於retain實例方法的函數
-
_Block_object_dispose 相當於release實例方法的函數
但是上面的兩個函數只是賦值給了結構體__main_block_desc_0的成員變量中,並沒有調用,原因如下
本以為在去掉copy後,因為變量的作用域結束會導致NSMutableArray對象也廢棄,但是通過運行後通過導出C++發現除了沒有調用copy,其他的都步驟都有做,所以才沒有導致崩潰!這是和書上不同的地方!!!
還是將書上的說明貼出來,
block循環引用
示例一:
如果在Block中使用附有__strong修飾符的對象類型自動變量,那麼當Block從棧複製到堆時,該對象為Block所持有,這樣容易引起循環引用
typedef void (^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
-(id)init{
self = [super init];
blk_ = ^{NSLog(@"self = %@",self);}; //self為帶有__strong修飾符的對象類型自動變量
return self;
}
-(void)dealloc{
NSLog(@"dealloc");
}
@end
int main(int argc, const char * argv[]) {
id o = [[MyObject alloc]init];
NSLog(@"%@",o);
return 0;
}
循環救命如下圖
為避免循環使用,可聲明附有__weak修飾符的變量,將self賦值使用
-(id)init{
self = [super init];
// blk_ = ^{NSLog(@"self = %@",self);};
id __weak tmp = self;
blk_ = ^{NSLog(@"self = %@",tmp);};
return self;
}//這樣就會調用MyObject的dealloc釋放函數了
示例二:
typedef void (^blk_t)(void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
-(id)init{
self = [super init];
__block id tmp = self;
blk_ = ^{NSLog(@"self = %@",tmp);
// tmp = nil;
};
return self;
}
-(void)execBlock{
blk_();
}
-(void)dealloc{
NSLog(@"dealloc");
}
@end
int main(int argc, const char * argv[]) {
id o = [[MyObject alloc]init];
[o execBlock];
return 0;
}
將tmp = nil
這句代碼屏蔽便會引起循環引用,因為
- MyObject 類對象持有Block
- Block持有__block對象
- __block 變量持有MyObject類對象
將tmp=nil
加上便可以解循環,如下圖所示