OC底层原理:block的本质(一)

什么是block?
带有自动变了的匿名函数。

  1. block其实也是一个oc对象,内部有一个isa指针。

block本质

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        void(^testBlock)(void) = ^{
            NSLog(@"a=%d",a);
        };
        testBlock();
    }
    return 0;
}

先通过命令行编译一下

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;
  }
};
//// 封装了block内部执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5v_mzhg9jss4vdc16_hqrk9pfp00000gn_T_main_51326f_mi_0,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 argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        // 定义block变量
        void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
       
        //调用
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);;
    }
    return 0;
}
先看main方法,里面有定义的block
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

这里其实就是调用了__main_block_impl_0函数并且传入了__main_block_func_0、__main_block_desc_0_DATA 参数。

__main_block_impl_0结构如下:
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;
  }
};
这里有一个结构体impl、一个结构体指针Desc和自身的构造函数,自身构造函数我们先忽略。先看下__block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

这里看到有一些标志和指针,我们也看到了isa。

再看下__main_block_desc_0
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)};

//这里reserved 是0 , Block_size是__main_block_impl_0的空间大小。

__main_block_func_0,封装了block内部执行逻辑的函数。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5v_mzhg9jss4vdc16_hqrk9pfp00000gn_T_main_b3d2a9_mi_0);
        }
最后看下__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;
  }

这里可以看到将一部分信息保存到了impl结构体中,desc保存在了Desc中。

1. 首先调用了__main_block_impl_0构造函数,参数(block执行逻辑函数__main_block_func_0,和block描述信息__main_block_desc_0)
2.执行逻辑函数指针fp存放在了impl.FuncPtr中
3.desc存放在了Desc中

再看下调用代码

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

去掉各种类型转换

testBlock->FuncPtr(testBlock);

testBlock是__main_block_impl_0,impl是__main_block_impl_0内部的第一个成员,所以testBlock的地址也是impl的地址,被强转成__block_impl类型,然后取到内部的FuncPtr,调用函数,参数就是自己。

大概总结一张结构关系图:
image.png

block捕获变量

int c = 30;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        static b = 20;
        void(^testBlock)(void) = ^{
            NSLog(@"a is %d, b is %d, c is %d");
        };
        testBlock();;
    }
    return 0;
}

将代码编译之后,我们看下有什么变化。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *b;
};

我们可以看见这里多了2个成员,一个int a, 一个int *b,没有c。而且a是获取了值,b是获取了地址。

再看下构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

这里a(_a), b(_b),其实就是把传进来的_a赋值给a, _b赋值给b。

这里我们看三种不同变量的差异:

  1. a是auto变量,其实是这样auto int a = 10,自动变量,是执行完函数之后,自动释放的局部变量,因为可能会释放,但是block还可以继续保留执行,如果继续访问a,可能会发生坏内存访问,所以需要捕获一份变量值。正是因为这样,外部修改a的值,并不会影响block内部的值,而且block内部也不能修改a的值,因为这里的a是指的block外面的局部变量a, block只是捕获了a的值,并不能修改a,只是名字相同。
  2. static 静态变量,不会被销毁。所以可以使用地址来继续访问。由于是指针传递,所以如果在外部修改值,block内部使用的值也会发生变化。block内部修改值,外部也会变化。
  3. c 全局变量,在任何地方都可以访问,所以不需要捕获。
总结一张图:
image.png

block的类型

block是OC对象,可以通过isa看一下

void (^block)(void) = ^{
            NSLog(@"block");
        };
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
NSLog(@"%@",[[[[[block class] superclass] superclass] superclass] superclass]);

输出

NSGlobalBlock
__NSGlobalBlock
NSBlock
NSObject
null

block是继承自NSBlock,NSBlock又继承自NSObject。block是OC对象。

block有三种类型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )

看下三种类型:

int age = 1;
void (^block1)(void) = ^{
    NSLog(@"block1");
};
void (^block2)(void) = ^{
    NSLog(@"block2:%d",age);
};
NSLog(@"%@/%@/%@",[block1 class],[block2 class],[^{
    NSLog(@"block3:%d",age);
} class]);
image.png

不同类型的block的存储域:


image.png

block是怎么区分类型和存储域的?
验证需要在MRC环境下进行。(先略过,以后再说)
结论:

  1. 没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段
  2. 访问了auto变量的block是__NSStackBlock __
  3. [__NSStackBlock __ copy]操作就变成了__NSMallocBlock __

在ARC环境下,编译器会根据情况自动将栈上的block进行一次copy操作,将block复制到堆上。
以下几种情况:

  1. block作为函数返回值时
  2. 将block赋值给__strong指针时
  3. block作为Cocoa API中方法名含有usingBlock的方法参数时
  4. block作为GCD API的方法参数时

由于时间关系,最近写的越来越仓促了。

你可能感兴趣的:(OC底层原理:block的本质(一))