【面试题】block 相关

block 的本质

block 的本质是封装了函数调用及函数调用环境的 OC 对象。

可以执行如下代码验证:

#import <Foundation/Foundation.h>

/*
 终端执行:
 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
 获取的 block 相关结构体
*/

// impl 结构体
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// block 描述信息
struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
};

// block 结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
};

//__main_block_impl_0 结构体初始化
//((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a))();

// 函数定义(需传入 __main_block_impl_0 结构体指针)
//static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//  int a = __cself->a; // bound by copy
//  NSLog((NSString *)&__NSConstantStringImpl__var_folders_vz_ktjs0_fn5dg59rp_kggzsch80000gn_T_main_995593_mi_0, a);
//}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 声明变量供 block 捕获
        int a = 10;
        
        // 声明 block
        void (^block)(void) = ^{
            NSLog(@"%s:a: %d", __func__, a);
        };
        
        // 强转为 __main_block_impl_0 结构体指针
        struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
        
        // 打印变量,证明变量是否捕获成功
        NSLog(@"__main_block_impl_0->a: %d", blockImpl->a);
        
        // 获取函数指针
        void (*func)(struct __main_block_impl_0 *) = blockImpl->impl.FuncPtr;
        
        // 直接通过函数指针调用,证明是否执行 block 内部的代码
        func(blockImpl);
    }
    return 0;
}

打印输出:

外部变量的访问

变量类型 是否捕获到 block 内部 访问方式
局部 auto 变量 捕获 值传递
局部 static 变量 捕获 指针传递
全局变量 不捕获 直接访问
#import <Foundation/Foundation.h>

int c = 30;
static int d = 40;

void blockCapture(void) {
    int a = 10;
    static int b = 20;
    void (^block)(void) = ^{
        NSLog(@"%d---%d---%d---%d", a, b, c, d);
    };
    
    a = 100;
    b = 200;
    c = 300;
    d = 400;
    block();
}
int c = 30;
static int d = 40;

struct __blockCapture_block_impl_0 {
  struct __block_impl impl;
  struct __blockCapture_block_desc_0* Desc;
  int a;
  int *b;
};

static void __blockCapture_block_func_0(struct __blockCapture_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  int *b = __cself->b; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_vz_ktjs0_fn5dg59rp_kggzsch80000gn_T_main_426782_mi_2, a, (*b), c, d);
}

block 的类型

block 的类型 环境 副本源的配置存储域 复制效果
__NSGlobalBlock__(_NSConcreteGlobalBlock) 没有访问 auto 变量 程序的数据区域(.data 区) 什么也不做
__NSStackBlock__(_NSConcreteStackBlock) 访问了 auto 变量 从栈复制到堆
__NSMallocBlock__(_NSConcreteMallocBlock) __NSStackBlock__ 调用了 copy 引用计数增加

block 的 copy

在 ARC 环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下情况

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

MRC 下 block 属性的建议写法

  • @property (copy, nonatomic) void (^block)(void);

ARC 下 block 属性的建议写法

  • @property (strong, nonatomic) void (^block)(void);

  • @property (copy, nonatomic) void (^block)(void);

block 访问对象类型的 auto 变量

当 block 内部访问了对象类型的 auto 变量时

  • 如果 block 是在栈上,将不会对 auto 变量产生强引用
  • 如果 block 被拷贝到堆上
    • 会调用 block 内部的 copy 函数
    • copy 函数内部会调用 _Block_object_assign 函数
    • _Block_object_assign 函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  • 如果 block 从堆上移除
    • 会调用 block 内部的 dispose 函数
    • dispose 函数内部会调用 _Block_object_dispose 函数
    • _Block_object_dispose 函数会自动释放引用的 auto 变量 (release)
#import <Foundation/Foundation.h>

@interface JQPerson : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation JQPerson

- (void)dealloc {
    NSLog(@"%@: dealloc", self.name);
}

@end

typedef void(^JQBlock)(void);

void blockCopy(void) {
    JQBlock block;
    {
        JQPerson *strongPerson = [[JQPerson alloc] init];
        strongPerson.name = @"strong person";
        
        JQPerson *person = [[JQPerson alloc] init];
        person.name = @"weak person";
        __weak JQPerson *weakPerson = person;
        
        block = ^{
            NSLog(@"-->%@", strongPerson.name);
            NSLog(@"-->%@", weakPerson.name);
        };
    }
    block();
    NSLog(@"-------");
}
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
struct __blockCopy_block_impl_0 {
  struct __block_impl impl;
  struct __blockCopy_block_desc_0* Desc;
  JQPerson *__strong strongPerson;
  JQPerson *__weak weakPerson;
  __blockCopy_block_impl_0(void *fp, struct __blockCopy_block_desc_0 *desc, JQPerson *__strong _strongPerson, JQPerson *__weak _weakPerson, int flags=0) : strongPerson(_strongPerson), weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockCopy_block_copy_0(struct __blockCopy_block_impl_0*dst, struct __blockCopy_block_impl_0*src) {
  _Block_object_assign((void*)&dst->strongPerson, (void*)src->strongPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
  _Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __blockCopy_block_dispose_0(struct __blockCopy_block_impl_0*src) {
  _Block_object_dispose((void*)src->strongPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
  _Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __blockCopy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockCopy_block_impl_0*, struct __blockCopy_block_impl_0*);
  void (*dispose)(struct __blockCopy_block_impl_0*);
} __blockCopy_block_desc_0_DATA = { 0, sizeof(struct __blockCopy_block_impl_0), __blockCopy_block_copy_0, __blockCopy_block_dispose_0};

__block 修饰符

  • __block 可以用于解决 block 内部无法修改 auto 变量值的问题
  • __block 不能修饰全局变量、静态变量 (static)
  • 编译器会将 __block 修饰的变量包装成一个对象
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a = 10;
        void (^block)(void) = ^{
            a = 20;
        };
        block();
    }
    return 0;
}
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) = 20;
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
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 (*block)(void) = ((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 *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

__block 的内存管理

  • 当 block 在栈上时,并不会对 __block 变量产生强引用
  • 当 block 被 copy 到堆时
    • 会调用 block 内部的 copy 函数
    • copy 函数内部会调用 _Block_object_assign 函数
    • _Block_object_assign 函数会对 __block 变量形成强引用(retain)
  • 当block从堆中移除时
    • 会调用 block 内部的 dispose 函数
    • dispose 函数内部会调用 _Block_object_dispose 函数
    • _Block_object_dispose 函数会自动释放引用的 __block 变量(release)

__block 的 __forwarding 指针

block 的循环引用

  • __weak
__weak typeof(self) weakSelf = self;
self.block = ^{
		NSLog(@"%@", weakSelf);
};
  • __block 修饰,并在 block 内手动去除引用(必须要调用 block)
__block id blockSelf = self;
self.block = ^{
  	NSLog(@"%@", blockSelf);
  	blockSelf = nil;
};
self.block();