Block对象的本质

192 阅读4分钟

block是什么

block底层就是一个OC对象, 封装函数调用及调用环境的OC对象, 内部有isa指针

源代码

^{
    int a = 10;
    a = 20;
};

oc代码转换为c++代码后大体是这样的

// block实现, 有一个isa指针
struct __block_impl {
    // isa指针表明block本质上是一个oc对象
  void *isa;
  int Flags;
  int Reserved;
    
    // 函数地址
  void *FuncPtr;
};

// block实现
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;
  }
};

// 放block块中的代码实现
void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = 10;
}

// 放block基础信息的结构体
struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// Block实现
&__main_block_impl_0(
     // block块内的代码实现放到了__main_block_func_0这个函数中
     __main_block_func_0,

     // __main_block_desc_0类型, 存放一些所占内存大小的信息
     &__main_block_desc_0_DATA)
);

结构如图

block的变量捕获机制

auto修饰的局部变量

捕获到block内部, 值传递, 不可修改捕获的这个变量的值

源代码

// auto默认省略不写
int a = 10;
        
void (^block)(void) = ^{

    NSLog(@"%d", a);
};
block();

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

c++代码

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

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int a = 10;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

从第4行这里可以看出block内部会增加一个跟外部同名的变量, 并把block外部的值赋值给这个变量, 所以会给我们一假象在block内部访问外部的变量

static修饰的局部变量

捕获到block内部, 址传递, 可通过这个地址, 修改对应的内存数据

源代码

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

c++代码

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

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        static int a = 10;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

第四行的int *a和第18行的&a可以看出, block在捕获static修饰的变量时, 通过&a捕获外部变量a的地址值赋值给自己的int *a这个内部变量

全局变量

不捕获, 直接访问

源代码

int a = 10;

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

c++代码

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

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

从c++实现中没有发现任何存储外部变量的信息, 可见block对于访问全局变量, 不会进行捕获, 直接访问

block的种类

要在MRC环境下使用以下代码, ARC环境下会将我们栈上的block拷贝到堆上

__NSGlobalBlock__

// __NSGlobalBlock__: 没有访问auto变量
void (^block)(void) = ^{
    NSLog(@"global block");
};
NSLog(@"%@", [block class]);

__NSStackBlock__

// __NSStackBlock__: 访问了auto变量
int a = 10;
void (^block2)(void) = ^{
    NSLog(@"%d", a);
};
NSLog(@"%@", [block2 class]);

__NSMallocBlock__

// __NSMallocBlock__: __NSStackBlock__调用了copy
int b = 10;
void (^block3)(void) = [^{
	NSLog(@"%d", b);
} copy];
NSLog(@"%@", [block3 class]);

注意: 当我们要持有block对象时, 要使用copy属性, 这样可以保证我们在使这个block时没有被销毁

__block修饰符

__block int a = 10;
void (^block)(void) = ^{
    // 如果想要修改外部变量, 外部变量要用__block修饰
    a = 20;
    NSLog(@"%d", a);
};
block();

当基本数据类型的变量被__block修饰时, block内部会将其捕获为一个对象

c++代码

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __Block_byref_a_0 a = {0, &a, 0, sizeof(__Block_byref_a_0), 10};
        void (*block)(void) = (
           &__main_block_impl_0(
                __main_block_func_0,
                &__main_block_desc_0_DATA,
                &a,
                570425344
            )
        );
        block->FuncPtr(block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_bs_8j14my3x2yxb7f_65thjrd1m0000gn_T_main_9aad1a_mii_1, (a.__forwarding->a));
    }
    return 0;
}

当一个基本类型的变量被block捕获之后, 它就不再是之前单纯的a了, a变得复杂了, 它变成了一个对象类型

__weak修饰符

循环引用的问题, 当代码执行完p对象不会被释放, 造成内存泄露

Person * p = [Person new];
void (^block)(void) = ^{
    NSLog(@"block 内部 ------ %p", p);
};
p.block = block;
block();
NSLog(@"block 外部 ------");

使用__weak解决循环引用的问题

Person * p = [Person new];
__weak typeof(p) weakP = p;

void (^block)(void) = ^{
    NSLog(@"block 内部 ------ %p", weakP);
};
p.block = block;
block();
NSLog(@"block 外部 ------");

p对象正常释放

__strong修饰符

如果在block内部想要在代码未执行完之前一直持有捕获的对象, 用该关键字修饰

Person * p = [Person new];
__weak typeof(p) weakP = p;

void (^block)(void) = ^{
    __strong typeof(weakP) strongP = weakP;
    NSLog(@"block 内部 ------ %p", strongP);
};
p.block = block;
block();
NSLog(@"block 外部 ------");

本篇博客纯粹是自己的学习笔记, 希望对自己的学习有一个总结性的成果, 如有错误的地方, 欢迎大家指正, 小弟不胜感激!