block & 链式编程

722 阅读6分钟

0 第一性原理

  1. 函数引用
  2. 匿名函数,能自动捕获外部变量。
  3. 赋值操作,会把栈上的block copy到堆上。
  4. 可用于异步/同步,用于同步时,有些像模板模式。(自己的理解)
  5. 函数调用中,不要把,作为实参的block的block体,和本次函数调用,搞混了。

1 用法理解

1.1 作为属性,可以传递代码。

往往也是1.2用法中,函数接收方中的用法。

1.2 作为函数入参,可以将业务逻辑写在一起,代码结构紧凑。

AFN中response的代码写在block中,可以把业务逻辑写在一起。

响应式编程,AFN中,发送请求具体实现是AFN做的,什么时候返回也不确定。 这里用通知、delegate都会比block复杂。

1.3 作为返回值,可用于链式编程(函数式中常见、Masonry)

oc中链式的本质

  1. 借助了Getter的方式
  2. 通过.拿到对象的方法(human.run),该方法会返回一个block
  3. 通过()执行block,该block有参数和返回值,而返回值正是对象本身。
  4. block执行后的返回值是对象 (human.run(10))
  5. 这样从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到堆上。

  1. 局部/全局静态变量、全局变量,不在栈上,所以无需捕获。(这里局部指当前方法,全局指.m文件)
  2. 对于成员变量,会对self做强引用,也无需捕获。
  3. 对于局部变量。 基本类型变量,直接copy值到堆上。 对象类型变量,copy对象引用的值(地址)到堆上。 所以,这个值不能被修改。(但,对象属性的值,如human.age,可以修改)

4. __block则让局部变量create在堆上,block捕获时直接强引用。

3 block内存分配

  1. 没有外部变量的时候,就是一个GlobalBlock
  2. 自动捕获外部变量的时候, 因为外部变量是在栈上的,所以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 定义(也是容易看晕的地方)

  1. 等号右边,^返回类型(java形式的入参){block代码块}
  2. 把^和返回值换位置,同时去掉block名,并把^左移(以便编译器编译?)
  3. 定义时,不能用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体,和本次函数调用,搞混了。

  1. block是传过去的,对方想什么时候调用,就什么时候调用,调用的时候,他会按照block的参数设定去传值.

  2. 大多数情况下,可以通过函数名,看出来,对方大概会在什么时候调用这个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底层实现原理

  1. block就是指向结构体的指针
  2. 编译器会将block的内部代码生成对应的函数
  3. block的结构体内,有指向这个函数的指针
  4. 捕获,见第2节
  5. 源码,简单来说,

__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;
}