0 第一性原理
- 函数引用
- 匿名函数,能自动捕获外部变量。
- 赋值操作,会把栈上的block copy到堆上。
- 可用于异步/同步,用于同步时,有些像模板模式。(自己的理解)
- 函数调用中,不要把,作为实参的block的block体,和本次函数调用,搞混了。
1 用法理解
1.1 作为属性,可以传递代码。
往往也是1.2用法中,函数接收方中的用法。
1.2 作为函数入参,可以将业务逻辑写在一起,代码结构紧凑。
AFN中response的代码写在block中,可以把业务逻辑写在一起。
响应式编程
,AFN中,发送请求具体实现是AFN做的,什么时候返回也不确定。 这里用通知、delegate都会比block复杂。
1.3 作为返回值,可用于链式编程(函数式中常见、Masonry)
oc中链式的本质
:
- 借助了Getter的方式
- 通过
.
拿到对象的方法(human.run),该方法会返回一个block - 通过
()
执行block,该block有参数和返回值,而返回值正是对象本身。 - block执行后的返回值是对象 (human.run(10))
- 这样从4 -> 2 -> 3 -> 4,就可以一直链下去
//Human.h
- (Human *(^)(int))run;
//Human.m
- (Human *(^)(int))run {
return ^Human *(int m) {
NSLog(@"%d", m);
return self;
};
}
//invoke somewhere
Human *human = [Human new];
Human *(^block)(int) = human.run;
block(10);
//合起来
human.run(10);
2 捕获变量的原理和__block
所谓捕获,就是指,把栈上变量的值
copy到堆上。
- 局部/全局静态变量、全局变量,不在栈上,所以无需捕获。(这里局部指当前方法,全局指.m文件)
- 对于成员变量,会对self做强引用,也无需捕获。
- 对于局部变量。 基本类型变量,直接copy值到堆上。 对象类型变量,copy对象引用的值(地址)到堆上。 所以,这个值不能被修改。(但,对象属性的值,如human.age,可以修改)
局部变量create在堆上
,block捕获时直接强引用。
3 block内存分配
- 没有外部变量的时候,就是一个GlobalBlock
- 自动捕获外部变量的时候,
因为外部变量是在栈上的,所以block变成了栈上的block
如果进行了
=赋值
,会执行一个copy操作,变成了堆上的block
ARC下写copy还是strong,都会copy,但最好还是写copy提醒自己和别人。
//全局block
NSLog(@"globalBlock2, %@", ^{
NSLog(@"globalBlock");
});
//全局block,赋值操作,不会copy全局block
void (^globalBlock2) (void) = NSLog(@"globalBlock, %@", ^{
NSLog(@"globalBlock");
});
//栈block, 使用了外部变量 -> 栈block
int i = 2;
NSLog(@"stackBlcok, %@", ^{
NSLog(@"stackBlcok, %d", i);
});
//堆block, 栈block,进行了赋值操作 -> 堆block
void (^heapBlock) (void) = ^{
NSLog(@"heapBlock, %d", i);
};
NSLog(@"heapBlock, %@", heapBlock);
heapBlock();
3.1 深入理解block 与 copy
//对于已经在堆上的block,赋值操作不会再进行copy,[block copy]后,仍然是原地址
//也就是说,block的copy已经非常的自动化,显示的写 [block copy],已经完全不必要了
void (^heapBlock) (void) = ^{
NSLog(@"heapBlock, %d", i);
};
NSLog(@"heapBlock, %@", heapBlock);
heapBlock();
self.anotherBlock = heapBlock;
NSLog(@"self.anotherBlock, %@", self.anotherBlock);
self.anotherBlock= [heapBlock copy];
NSLog(@"self.anotherBlock, %@", self.anotherBlock);
4 声明和定义
三种方式中,保持一致的地方:入参的格式,返回类型都不带括号。 语法比较丑,多写几次才能练到手熟,也可以用下面的快捷方式。
//Xcode中,输入inline,会有提示的block定义出来
4.1 typed & 属性
//1. 返回类型不带括号
//2. block名、参数集带括号
//3. 参数集类似Java
typedef void (^TrueFalseCallback)(BOOL success);
typedef NSString * _Nonnull (^ABlock)(UIImage * _Nullable image);
@property (nonatomic, copy) void (^block)(void);
4.2 形参
//1. 形似typed声明的整体,放在一个括号内,作为类型
//2. block名右移作为形参
//FMDatabaseQueue.m
- (void)inDatabase:(void (^)(FMDatabase *db))block {
//block是参数,这里是函数体,不是block的定义。
FMDatabase *db = [self database];
block(db);
//...
}
4.3 定义(也是容易看晕的地方)
- 等号右边,
^返回类型(java形式的入参){block代码块}
- 把^和返回值换位置,同时去掉block名,并把^左移(以便编译器编译?)
- 定义时,不能用typedof定义的类型名,因为参数看不到。
/**
RAC中的一个定义
1. 参数是一个 返回值为RACSignal,参数为id的block
2. block内容没有,直接返回了一个RACSignal
3. RACSignal的参数是一个 返回值为RACDisposable,参数为id<RACSubscriber>的block
4. 这个block 是返回了一个nil
*/
[[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[self shuwen:input subscriber:subscriber];
return nil;
}];
}];
5-1中,有两个定义的实例
5 糊涂过的地方
5-1 第三方框架中,部分block的入参是谁提供的?
简单说,
函数调用中,不要把,作为实参的block
的block体,和本次函数调用,搞混了。
-
block是传过去的,对方想什么时候调用,就什么时候调用,调用的时候,他会按照
block的参数设定
去传值. -
大多数情况下,可以通过函数名,看出来,对方大概会在
什么时候调用
这个block。
先看这个
//入参中的db,由databaseQueue提供
[[HHBaseSharedDBPersistence databaseQueue] inDatabase:^(FMDatabase *db) {
FMResultSet *resultSet = [db executeQuery:self.sqlString];
if ([resultSet next]) {
dbVersion = [resultSet intForColumn:TABLE_NAME_META_COLUMN];
}
[resultSet close];
}];
再看看网络调用中:
AFURLSessionManager *session = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request
progress:^(NSProgress * _Nonnull downloadProgress) {
if (progress) {
dispatch_async(dispatch_get_main_queue(), ^{
progress(downloadProgress.fractionCompleted);
});
}
}
destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {}
completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {}
];
最后看Masonry中的使用,对照与1.3节中提到的链式编程。
- (MASConstraint * (^)(id))mas_equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
5.2 block与循环引用
www.jianshu.com/p/ce1f1ee52…的3.4节
6 注意事项
嵌套层次太深不要使用 -> 不利于代码调用、直观性较差。
7 block底层实现原理
- block就是指向结构体的指针
- 编译器会将block的内部代码生成对应的函数
- block的结构体内,有指向这个函数的指针
- 捕获,见第2节
- 源码,简单来说,
__block_impl是block的结构体,相当于block的元类型
__xx_block_impl_0是当前block的结构体
__xx_block_func_0是当前block体对应的函数
//voidBlock1.m
void (^globalBlock1) (void) = ^{
int i = 0;
NSLog(@"globalBlock1, %d", i);
};
//heapBlock.m
int main(int argc, char * argv[]) {
int i = 0;
int (^heapBlock1) (void) = ^{
NSLog(@"heapBlock1, %d", i);
return i;
};
heapBlock1();
return 0;
}
//通过clang编译成c++
clang -rewrite-objc xx.m
//block结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved; //保留参数
void *FuncPtr; //与block的是否有返回值无关
};
//block要执行的函数,两个block略有不同
static void __globalBlock1_block_func_0(struct __globalBlock1_block_impl_0 *__cself) {
int i = 0;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5x_6b7t3_991dl3tmjhjdktq_7w0000gn_T_voidBlock1_410ac6_mi_0, i);
}
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy, 对局部变量的引用
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5x_6b7t3_991dl3tmjhjdktq_7w0000gn_T_heapBlock_8bb134_mi_0, i);
return i;
}
//block描述信息的结构体,两个block只有struct名不同
static struct __globalBlock1_block_desc_0 {
size_t reserved;
size_t Block_size;
}
//该block实现的结构体,两个block不同
struct __globalBlock1_block_impl_0 {
struct __block_impl impl;
struct __globalBlock1_block_desc_0* Desc;
__globalBlock1_block_impl_0(void *fp, struct __globalBlock1_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i; //引用的局部变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//heapblock的初始化
int main(int argc, char * argv[]) {
int i = 0;
int (*heapBlock1) (void) = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
((int (*)(__block_impl *))((__block_impl *)heapBlock1)->FuncPtr)((__block_impl *)heapBlock1);
return 0;
}