iOS 性能iOS 开发系统层知识

iOS Block实现原理

2019-01-03  本文已影响12人  1江春水

系列文章:
iOS Block概念、语法及基本使用
iOS Block __block说明符
iOS Block存储域及循环引用

本章会讲解以下几点:

Block实现原理

要想知道Block的内部实现,需要知道Block编译完后是什么样子,使用clang可看到Block编译完后的代码实现。

Block的语法看着特别,但它实际上是作为极普通的C语言源代码来处理的。通过支持Block的编译器,将含有Block语法的源代码转换为C语言的源代码,并作为极为普通的C语言源代码被编译。

clang -rewrite-objc 文件名

上述命令使用在命令行项目是没问题的,但是使用在single APP项目中会报错,UIKit/UIKit.h’ file not found

在single APP项目中使用以下命令:

模拟器 xcrun -sdk iphonesimulator clang -rewrite-objc xxx.m
真机 xcrun -sdk iphoneos clang -rewrite-objc xxx.m

使用命令后会生成一个 xxx.cpp的文件。

来具体看下Block编译后的代码,先来看命令行项目:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^blk)(void) = ^{
            printf("Block\n");
        };
        blk();
    }
    return 0;
}

上述代码是一个很简单的无返回值无参数的Block,下面是使用clang编译后的源代码,只把有用的部分贴出:

struct __block_impl {
  void *isa;//block isa指针
  int Flags;
  int Reserved;
  void *FuncPtr;//函数指针
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  //初始化
  __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) {

            printf("Block\n");
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;//block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//block表达式转换
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);//block调用
    }
    return 0;
}

如上变换后的源代码,通过Block使用的匿名函数实际上被作为简单的C语言函数来处理,另外Block语法所属的函数名(此处为main,后边会贴上 函数名为 viewDidLoad)和该Block语法在该函数出现的顺序值来给经clang变换的函数命名。

下面具体一步一步来看看上述代码:
__main_block_impl_0结构体的声明:第一个成员变量是impl,第二个成员变量是Desc。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
};

__block_impl 结构体的声明:

struct __block_impl {
  void *isa;//block isa指针
  int Flags;
  int Reserved;
  void *FuncPtr;//函数指针
};

__main_block_desc_0 结构体声明:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;//block大小
}

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

Block表达式生成部分:初始化一个__main_block_impl_0结构体实例并赋值给blk变量。

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

__main_block_func_0结构体声明,__main_block_func_0该函数的参数__cself是 __main_block_impl_0 结构体实例,该实例其实就是转换前的Block实例。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            printf("Block\n");
        }

去掉转换部分:

struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &temp;

上边代码对应的源代码就是以下代码:

void (^blk)(void) = ^{
            printf("Block\n");
        };

再来看看Block调用部分:blk()

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

去掉转换部分:

(*blk->impl.FuncPtr)(blk);

正如刚才看到的编译后的代码,block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中,另外也说明了__main_block_func_0函数的参数__cself指向Block值。在调用该代码的源代码中可以看出Block正是作为参数进行了传递。

Block也是OC对象

impl.isa = &_NSConcreteStackBlock;

只要你理解OC对象内的isa指针,你就能理解这里的isa指针。block的isa指针指向自己所属的Block类。
OC对象都有一个isa指针,指向属于当前的类。以下是<objc/runtime.h>运行时 objc_class 结构体的声明。

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif
}

以下贴出single APP 使用命令编译后的源代码实现,使用命令:

xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
源代码:
- (void)viewDidLoad {
    [super viewDidLoad];

    int (^block)(int,int) = ^ int (int a,int b) {
        return a + b;
    };
    int sum = block(1,2);
    NSLog(@"%d",sum);
}
编译后:
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};


static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int a, int b) {

        return a + b;
    }

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    int (*block)(int,int) = ((int (*)(int, int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
    int sum = ((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_9k_z85dfkt91zd1j387gcxn8xkh0000gn_T_ViewController_b0d5a8_mi_0,sum);
}

截获自动变量值(瞬间值)

编译以下源代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        void (^blk)(void) = ^{
            printf(fmt,val);
        };
        val = 2;
        fmt = "These value were changed. val = %d\n";
        blk();
    }
    return 0;
}

编译后:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {//最终的函数指针调用
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy

            printf(fmt,val);
        }

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;//传入的变量 val
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));//结构体带着参数val初始化并赋值
        val = 2;
        fmt = "These value were changed. val = %d\n";
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);//函数指针调用
    }
    return 0;
}

这样来看就能想明白为什么val、fmt变量改变,而block表达式内部的变量值不改变了。即Block截获的自动变量值是变量的瞬间值,在编译完成后就已经确定了,即便改变Block内部用到的变量的值,Block调用的时候,表达式内部的变量值也不会改变。

总结

Block实质就是: 编译的时候编译器会把Block的表达式及Block变量,编译成一般的C语言结构体、函数,block调用就是函数指针调用。Block表达式中使用局部变量时,编译生成的结构体(__main_block_impl_0)会持有该变量,该结构体会带着默认值初始化结构体实例。

请看下集!

上一篇下一篇

猜你喜欢

热点阅读