回炉篇之(五)-- OC 中的 Block

iOS May 21, 2015

回炉篇已经是第五篇了,断断续续的写,前面几篇附上一个传送门吧


Objective-C 的 Block 非常神奇,相当于一个类,里面保存的是一段代码(代码块),别的语言可能还没有这个特性,同时,Apple 官方也推荐开发者使用 Block 来写程序。

一个最简单的 Block

Block 跟函数挺像的,不过用法比函数要高大上。举个栗子:

// 函数是这样写
void blockTest() {
    NSLog(@"Hello, World!");
}

// 这样调用
blockTest();

// block 这样写
void (^blockTest)() = ^{
    NSLog(@"Hello, World!");
};

// 这样调用
blockTest();

两个调用是完全一样的,不过 Block 的声明有点难理解,来分析下

void 说明函数没有返回值,跟函数的写法完全一样的,

(^blockTest) 拆成三部分,blockTest 是 Block 的名字,前面加一个 ^ 说明这是个 Block 类型,然后再用括号括住,这是固定写法,

() 说明无参数,

^{} 这个就是 Block 内容的固定写法了,当然前提是没有参数,另一种情况稍后补充慢慢来~

有参数无返回值的 Block

void (^blockTest)(int, NSString *) = ^(int count, NSString *word){
    
    for (int i = 0; i<count; i++) {
        NSLog(@"%@", word);
    }
};

blockTest(5, @"Hello, World!");

大括号内的东西大概都明白,不再细说,上面提到,() 代表无参数,那么这次里面有一个 int, NSString *,说明有两个类型的参数,后面原本是 ^{},现在中间多了个 (),道理同上。

有参数有返回值的 Block

int (^sum)(int, int) = ^(int x, int y){
    
    return x + y;
};

NSLog(@"%d", sum(13, 14));

这个其实我不想多说了,以大家聪明的程度肯定自己能看懂。

Block 中的 typedef

大家都知道 typedef 可以这么用

typedef int BlockInt;

相同的,可以推理到 Block 中

typedef int (^MyBlock)(int);

注意写法,跟普通的不太一样

之后想用的时候,可以直接把 MyBlock 当成一个类型,举个栗子

MyBlock square = ^(int x){
    return x * x;
};

square(6);

Block 到底(比函数)优越在哪里

void getSomeInfo(NSString *info) {
    
    NSLog(@"%@", info);
}

用函数写的话,可以这么玩,传入一个已知类型的变量,由这个函数来决定如何处置变量。

我们写程序讲究低耦合,这个函数关心的东西越少越好,这么做管的太宽了,但是我们有 Block,来看一个神奇的效果:

void getSomeInfo(void(^blockFunc)()) {
    
    NSLog(@"Before Block");
    blockFunc();
    NSLog(@"After Block");
}

main 函数中

getSomeInfo(^{
    
    NSLog(@"%@", @"You can do anything here");
});

结果是

2015-05-21 22:52:23.498 Block-Test[5427:277676] Before Block
2015-05-21 22:52:23.499 Block-Test[5427:277676] You can do anything here
2015-05-21 22:52:23.499 Block-Test[5427:277676] After Block

当然这么搞不太严谨的,如果传进去一个空,程序会报错(坏访问),解决方法也很简单,判断一下是否为空即可,我就不写了 =. =

Block 的进阶使用

修改外部变量的时候,外部变量需要加一个 __block 来修饰

__block int a = 50;

为什么加上 __block 就能修改了

先贴 main.m 的代码

//
//  main.m
//  Block-Test
//
//  Created by JieLee on 15/5/21.
//  Copyright (c) 2015年 PUPBOSS. All rights reserved.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        __block int a = 10;
        
        void (^blockTest)() = ^{
            
            a = 50;
            NSLog(@"%d", a);
        };

        blockTest();
        
    }
    return 0;
}

在 Terminal 中,输入 clang -rewrite-objc main.m,居然转换了 10 万多行 C++ 代码,拉到最下面分析

有一段是这样的

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};

        void (*blockTest)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);

        ((void (*)(__block_impl *))((__block_impl *)blockTest)->FuncPtr)((__block_impl *)blockTest);

    }
    return 0;
}

来分析下,一般括号内的东西是强转,那就好办了,重点看这句

void (*blockTest)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);

删掉各种括号,就变成了

void (*blockTest)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344);

__main_block_impl_0 应该是一个类,或者结构体,我们往上找,还真有一个

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

C++ 的结构体比较有意思,他可以调用自己的 __main_block_impl_0 函数,然后把需要的东西传进去,最后 Desc = desc; 返回一个结构体。

再转回来 &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344); 是什么意思呢,返回一个结构体,然后取地址,所以 block 的本质,就是一个结构体!

至于为什么能改变 a 的值,我贴段代码,大家自己理解吧,我是有点蒙。反正就是各种地址各种传进去,然后就把值给改了。

__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref


            (a->__forwarding->a) = 50;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f__kvyhp79j5wbcb2d7t24v3v_c0000gn_T_main_f979f0_mi_2, (a->__forwarding->a));
        }

文章最后有完整的代码,想研究的可以去看看~

附完整代码

https://gist.github.com/pupboss/0cf42d6114816ef2fdb8

Tags

Jie Li

🚘 On-road / 📉 US Stock / 💻 Full Stack Developer / 🎓 Grad Student / ®️ ENTJ

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.