阅读 180

iOS-block

block实现原理

block本质是一个OC对象,它内部也有个isa指针,它封装了函数调用已经函数调用环境。

首先声明一个block:

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

    void (^block)(void) = ^ {
        NSLog(@"hello block");
    };

    block();

    return 0;
}
复制代码

然后使用clang将代码转换成C++代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

struct __block_impl {
  void *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) {

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_417806_mi_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)};

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

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;
}
复制代码

从上面的代码可以看出block的本质是一个OC对象,它的结构如下:

block的实现是一个__main_block_impl_0结构体,结构体包含两个重要的成员,一个是block对象__block_impl,另一个是block描述__main_block_desc_0

__block_impl对象中存储着block的函数实现;__main_block_desc_0记录着block占用的内存空间

block的声明和调用简化类型转换后如下:

// 首先创建一个__main_block_impl_0结构体,
// 并且将函数地址__main_block_func_0和__main_block_desc_0_DATA地址传入
void (*block)(void) = &__main_block_impl_0(
                                           __main_block_func_0,
                                           &__main_block_desc_0_DATA)
);

// 再调用__block_impl对象中的FuncPtr函数(实际就是上一步传入的__main_block_func_0函数)
(block)->FuncPtr)(block);
复制代码

block类型捕获

局部变量捕获

int main(int argc, const char * argv[]) {
    // auto 自动变量,离开作用域立刻销毁
    // 定义一个局部变量a和一个静态变量b
    auto int a = 10;
    static int b = 10;

    void (^block)(void) = ^ {
        NSLog(@"a = %d", a);
        NSLog(@"b = %d", b);
    };

    block();

    return 0;
}
复制代码

转换成C++代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *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;
  }
};

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

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_47e51c_mi_0, a);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_47e51c_mi_1, (*b));
}
复制代码

总结:

  • auto类型的局部变量,会被捕获到__main_block_impl_0结构体内成为成员变量,并且在__main_block_func_0函数中使用的是__main_block_impl_0结构体的成员变量

  • 其中auto类型的变量是值传递

  • static类型的变量为指针传递

全局变量

int m = 10;
static int n = 10;

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

    void (^block)(void) = ^ {
        NSLog(@"m = %d", m);
        NSLog(@"n = %d", n);
    };

    block();

    return 0;
}
复制代码

转换成C++代码:

int m = 10;
static int n = 10;

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) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_859eed_mi_0, m);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_859eed_mi_1, n);
}
复制代码

总结:

全局变量不会被捕获成为__main_block_impl_0的成员变量,并且__main_block_func_0函数内是直接访问全局变量

block三种类型

代码如下:

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

    int age = 10;

    //__NSGlobalBlock__
    void (^block1)(void) = ^ {

    };
    NSLog(@"%@", [block1 class]);

    // __NSMallocBlock__
    void (^block2)(void) = ^ {
        NSLog(@"n = %d", age);
    };

    NSLog(@"%@", [block2 class]);

    // __NSStackBlock__
    NSLog(@"%@", [^{
        NSLog(@"%d", age);
    } class]);

    return 0;
}
复制代码

block访问外部auto变量时,会将变量捕获成为__main_block_impl_0结构体的成员变量

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

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

__block

  • __block可以用于解决block内部无法修改auto变量值的问题

  • __block不能修饰全局变量、静态变量

  • 编译器会将__block变量包装成一个对象

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

    // 定义一个__block变量
    __block int age = 10;
    
    void (^block)(void) = ^ {
        // 在block内部修改age的值
        age = 20;
        NSLog(@"block age = %d", age);
    };
    
    block();
    
    // 在block结束之后修改age的值
    age = 30;
    NSLog(@"age = %d", age);
    
    return 0;
    复制代码

    }

将上面代码转换成C++代码:

// 将__block变量包装成__Block_byref_age_0对象
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

// block结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// block实现函数,内部访问的是__Block_byref_age_0包装对象的age成员变量static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

    (age->__forwarding->age) = 20;

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_0117fb_mi_0, (age->__forwarding->age));
}

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

    // 创建一个__Block_byref_age_0类型的结构体,并将结构体里的age成员变量赋值为10
    __Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};

    void (*block)(void) = (&__main_block_impl_0(
                                                __main_block_func_0,
                                                &__main_block_desc_0_DATA,
                                                &age,
                                                570425344)
                           );

    ((block)->FuncPtr)(block);

    // block外部的age修改实际修改的是__Block_byref_age_0结构体内部的age成员变量
    (age.__forwarding->age) = 30;

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_0117fb_mi_1, (age.__forwarding->age));

    return 0;
}
复制代码

__block修饰了block外部的auto变量时,

age会被包装成一个__Block_byref_age_0对象,并且被block捕获到__main_block_impl_0结构体内成为成员变量

block实现函数内部使用的正是这个__Block_byref_age_0对象的age成员变量

并且在block后面再次访问age变量时,同样等同于访问__Block_byref_age_0对象中的age成员变量,如下👇

__Block_byref_age_0 结构体访问age变量时,使用__forwarding指针的原因:

block在栈上时,__Block_byref_age_0对象中的__forwarding指针指向__Block_byref_age_0对象本身

block拷贝到堆上时,栈上的__Block_byref_age_0对象中的__forwarding指向堆上的__Block_byref_age_0对象

这样能保证此时栈上和堆上的__forwarding指针都指向堆上的__Block_byref_age_0对象

__block的内存管理

block在栈上时,并不会对__block变量产生强引用

`block``copy`到堆上时
  • 会调用block内部的copy函数
  • copy函数内部会调用_Block_object_assign函数
  • _Block_object_assign函数会对__block变量形成强引用

block从堆中移除时 

会调用block内部的dispose函数 

dispose函数内部会调用_Block_object_dispose函数 

_Block_object_dispose函数会自动释放引用的__block变量

文章分类
iOS
文章标签