Block 那些事
概念
Block是iOS4.0+ 和Mac OS X 10.6+ 引進(jìn)的對C語言的擴(kuò)展该贾,用來實(shí)現(xiàn)匿名函數(shù)的特性
閉包是一個能夠訪問其他函數(shù)內(nèi)部變量的函數(shù).
基本語法
^(returnType)(argsList)
body
}
returnType
可以省略握巢,argsList
如果沒有可以省略,最簡單的block語法形式如:
^
body;
}
定義一個無參彻亲,返回值為void
的block堕担。
如果定義一個block變量介褥,則語法形式為:
returnType (^blockVar)(argsList)
body;
}
例如:
int (^blockVar)(NSString* arg)
return @“Hello World”;
}
上述定義一個 返回值為整數(shù)座掘,帶有一個字符串參數(shù)的blockVar
變量。
為了方便使用常常使用typedef
給block類型定義一個別名柔滔,如
typedef int (^returnIntBlock)(NSSstring *arg);
returnIntBlock blockVar;
此時returnIntBlock
就代表反回值為整數(shù)參數(shù)為字符串的block類型溢陪。blockVar 代表 該block類型的一個變量
。
基本用法
-
定義普通變量睛廊。
//定義一個返回值為整型形真,帶有兩個整形參數(shù)的block變量 NSInteger (^returnIntVar)(NSInteger, NSInteger); //給block變量賦值 returnIntVar = ^NSInteger(NSInteger left, NSInteger right){ return left + right; };
-
定義屬性。
@property (nonatomic, copy) NSInteger (^returnIntBlock)(NSInteger, NSInteger);
-
block作為方法參數(shù)超全。
- (void)methodWithBlockArg:(NSInteger (^)(NSInteger, NSInteger))blockArg{ NSInteger sum ; sum = blockArg(3, 4); }
-
block作為方法的返回值咆霜。
- (void (^)(NSString *arg))methodReturnBlock{return ^void(NSString *arg){ NSLog(@"arg: %@", arg); }; }
-
block作為函數(shù)的返回值邓馒。
void (^functionReturnBlock(NSString *arg))(NSString *arg){return ^void(NSString *arg){ NSLog(@"arg: %@", arg); }; }
block注意事項(xiàng)
修改引用的外部變量。
默認(rèn)情況下block內(nèi)部是不能修改應(yīng)用變量的值的蛾坯,若要修改光酣,需在定義外部變量時使用__block
關(guān)鍵字修飾。
__block NSInteger a = 3;
void(^blockOp)() = ^(){
a = 5;
};
blockOp();-
循環(huán)引用脉课。最常見的情況是對象擁有block挂疆,block內(nèi)部又去訪問對象的屬性,這時下翎,在block外面定義一個弱引用。如下
__weak typeof(self) weakSelf = self;
self.returnIntBlock = ^(NSInteger left, NSInteger right){weakSelf.sum = left + right ; return weakSelf.sum; };
block原理
為了研究編譯器是如何實(shí)現(xiàn) block 的宝当,我們需要使用 clang视事。clang 提供一個命令,可以將 Objetive-C 的源碼改寫成 c 語言的庆揩,借此可以研究 block 具體的源碼實(shí)現(xiàn)方式俐东。該命令是:
clang -rewrite-objc block.c
我們先新建一個名為 block1.c 的源文件:
include <stdio.h>
int main()
{
int a = 100;
void (^block2)(void) = ^{
printf("%d\n", a);
};
block2();
return 0;
}
然后在命令行中輸入:
clang -rewrite-objc block1.c
如果成功,會在當(dāng)前目錄下生成一個block.cpp
的源文件订晌。該文件中的關(guān)鍵代碼如下:
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;
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 (*block2)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
return 0;
}
其中:
isa 指針虏辫,所有對象都有該指針,用于實(shí)現(xiàn)對象相關(guān)的功能锈拨。
flags砌庄,用于按 bit 位表示一些 block 的附加信息。
reserved奕枢,保留變量娄昆。
funcPtr,函數(shù)指針缝彬,指向具體的 block 實(shí)現(xiàn)的函數(shù)調(diào)用地址萌焰。
desc, 表示該 block 的附加描述信息谷浅,主要是 size 大小扒俯,以及 copy 和 dispose 函數(shù)的指針。
-
a一疯,capture 過來的變量撼玄,block 能夠訪問它外部的局部變量,就是因?yàn)閷⑦@些變量(或變量的地址)復(fù)制到了結(jié)構(gòu)體中墩邀。
我們修改上面的源碼互纯,在變量前面增加__block
關(guān)鍵字:
include <stdio.h>int main()
{
__block int i = 1024;
void (^block1)(void) = ^{
printf("%d\n", i);
i = 1023;
};
block1();
return 0;
}
生成的關(guān)鍵代碼如下,可以看到磕蒲,差異相當(dāng)大:
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) = 1023;
}
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), 1024};
void (*block1)(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 *)block1)->FuncPtr)((__block_impl *)block1);
return 0;
}
源碼中增加一個名為 __Block_byref_i_0
的結(jié)構(gòu)體留潦,用來保存我們要 capture 并且修改的變量 a只盹。
main_block_impl_0
中引用的是 Block_byref_i_0
的結(jié)構(gòu)體指針,這樣就可以達(dá)到修改外部變量的作用兔院。
對于 block 外的變量引用殖卑,block 默認(rèn)是將其復(fù)制到其數(shù)據(jù)結(jié)構(gòu)中來實(shí)現(xiàn)訪問的。
對于用
__block
修飾的外部變量引用坊萝,block 是復(fù)制其引用地址來實(shí)現(xiàn)訪問的孵稽。