美文网首页
—— block详解

—— block详解

作者: 大成小栈 | 来源:发表于2021-07-13 20:16 被阅读0次

新建工程,并实现以下代码:

#import <Foundation/Foundation.h>

void testFunc() {
    int a =10;
    __block int b = 20;
    
    void (^testBlock)(int) = ^(int c){
         int  d = a + b + c;
         NSLog(@"d=%d", d);
     };
 
    //修改值不会影响testBlock内的计算结果
    a = 20;
    //修改值会影响testBlock内的计算结果。
    b = 40;
    
    testBlock(30);
}
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        testFunc();
    }
    return 0;
}

在工程目录启动命令行输入以下命令:
$ clang -rewrite-objc main.m

将生成main.cpp文件拉至最下,可见如下代码:

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};

struct __testFunc_block_impl_0 {
  struct __block_impl impl;
  struct __testFunc_block_desc_0* Desc;
  int a;
  __Block_byref_b_0 *b; // by ref
  __testFunc_block_impl_0(void *fp, struct __testFunc_block_desc_0 *desc, int _a, __Block_byref_b_0 *_b, int flags=0) : a(_a), b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __testFunc_block_func_0(struct __testFunc_block_impl_0 *__cself, int c) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref
  int a = __cself->a; // bound by copy

         int d = a + (b->__forwarding->b) + c;
         NSLog((NSString *)&__NSConstantStringImpl__var_folders_1y_tktqm9p53zz3khyhzvqxtb9m0000gn_T_main_2edacd_mi_0, d);
     }
static void __testFunc_block_copy_0(struct __testFunc_block_impl_0*dst, struct __testFunc_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __testFunc_block_dispose_0(struct __testFunc_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __testFunc_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __testFunc_block_impl_0*, struct __testFunc_block_impl_0*);
  void (*dispose)(struct __testFunc_block_impl_0*);
} __testFunc_block_desc_0_DATA = { 0, sizeof(struct __testFunc_block_impl_0), __testFunc_block_copy_0, __testFunc_block_dispose_0};
void testFunc() {

    int a =10;
    __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};

    void (*testBlock)(int) = ((void (*)(int))&__testFunc_block_impl_0((void *)__testFunc_block_func_0, &__testFunc_block_desc_0_DATA, a, (__Block_byref_b_0 *)&b, 570425344));


    a = 20;

    (b.__forwarding->b) = 40;

    ((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);
}



int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        testFunc();
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

1. block 的底层结构

1.1 __testFunc_block_impl_0
struct __testFunc_block_impl_0 {
  struct __block_impl impl;
  struct __testFunc_block_desc_0* Desc;
  int a;
  __Block_byref_b_0 *b; // by ref
  __testFunc_block_impl_0(void *fp, struct __testFunc_block_desc_0 *desc, int _a, __Block_byref_b_0 *_b, int flags=0) : a(_a), b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__testFunc_block_impl_0是该block以C++实现的一个结构体,且从命名可以看出其为testFunc方法中的第0个block,通常包含两个成员变量__block_impl impl,__testBlock_block_desc_0* Desc和一个构造函数。

  • __block_impl 结构体
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  1. isa指向一个类对象(三种类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock),本例中为_NSConcreteStackBlock;
  2. Flags:block的负载信息(引用计数和类型信息),按位存储;
  3. Reserved:保留变量;
  4. FuncPtr指针指向Block执行时调用的函数(Block需要执行的代码块),本例中为__blockTest_block_func_0函数。
  • __blockTest_block_desc_0 结构体
static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};

其中,__blockTest_block_desc_0_DATA是一个__blockTest_block_desc_0的一个实例。
reserved:Block版本升级所需的预留区空间,在这里为0。
Block_size:Block大小(sizeof(struct __blockTest_block_impl_0))。

1.2 __testFunc_block_func_0
static void __testFunc_block_func_0(struct __testFunc_block_impl_0 *__cself, int c) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref
  int a = __cself->a; // bound by copy

  int d = a + (b->__forwarding->b) + c;
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_1y_tktqm9p53zz3khyhzvqxtb9m0000gn_T_main_2edacd_mi_0, d);
}

__testFunc_block_func_0就是block在执行时调用的函数,参数是一个__testFunc_block_impl_0类型的指针。

1.3 testFunc()
void testFunc() {
    int a =10;
    __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
    void (*testBlock)(int) = ((void (*)(int))&__testFunc_block_impl_0((void *)__testFunc_block_func_0, &__testFunc_block_desc_0_DATA, a, (__Block_byref_b_0 *)&b, 570425344));

    a = 20;
    (b.__forwarding->b) = 40;
    ((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);
}
  • 定义Block
void (*testBlock)(int) = ((void (*)(int))&__testFunc_block_impl_0((void *)__testFunc_block_func_0, &__testFunc_block_desc_0_DATA, a, (__Block_byref_b_0 *)&b, 570425344));

testBlock实际上是一个指针,指向一个通过构造函数实例化的__testFunc_block_impl_0结构体实例。初始化时需要两个参数:
__testFunc_block_func_0:block块的函数指针;
__testFunc_block_desc_0_DATA:作为静态全局变量初始化__main_block_desc_0的结构体实例指针。

  • 调用Block
((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);

上面方法调用通过testBlock->FuncPtr指针找到__blockTest_block_func_0函数并且转成(void (*)(__block_impl *, int))类型,然后将((__block_impl *) testBlock)作为参数传给这个函数进行调用。

2. 变量的截获(__block)

文章开始处的testFunc()中定义了两个变量:

int a =10;
__block int b = 20;

经过clang命令处理后的.cpp文件的__testFunc_block_func_0中,又看到以下代码:

__Block_byref_b_0 *b = __cself->b; // bound by ref
int a = __cself->a; // bound by copy

在clang后的testFunc()中,__block int b = 20;变成了以下代码:

__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};

其中,还多了针对 b 的结构体 __Block_byref_b_0 的定义:

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};

可见,日常使用block想改变外部变量的值需要加__block修饰,原因就在于:
block截获普通变量时,是值传递;
截获__block修饰的变量时,是地址传递;

另外,还可以试图定义静态局部变量、全局变量等,来观察clang后代码的变化。
静态局部变量也变成指针;全局变量仍然为全局直接作参传递。

3. block循环引用

@interface Person : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void (^block)(void);

- (void)testReferenceSelf;

@end

@implementation Person

- (void)testReferenceSelf {
    self.block = ^ {
        NSLog(@"self.name = %s", self.name.UTF8String);
    };
    self.block();
}

- (void)dealloc {
    NSLog(@"-------dealloc-------");
}

@end


int main(int argc, char * argv[]) {
    Person *person = [[Person alloc] init];
    person.name = @"roy";
    [person testReferenceSelf];
}

打印结果是self.name = roy,Person的析构方法dealloc并没有执行,这是典型的循环引用,下面我们研究研究为啥会循环引用。clang改写后的代码如下:

struct __Person__testReferenceSelf_block_impl_0 {
  struct __block_impl impl;
  struct __Person__testReferenceSelf_block_desc_0* Desc;
  Person *const __strong self;
  __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, self, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}

我们看到本来Person中testReferenceSelf方法是没有参数的,但是转成C++之后多出来两个参数:* self和_cmd,再看看__Person__testReferenceSelf_block_impl_0中多出来一个成员变量Person *const __strong self;,因此我们知道Person中block捕获了self,并且block强引用self,同时self也强引用block,因此形成循环引用。

  • __weak 解除循环引用
@implementation Person

- (void)testReferenceSelf {
    __weak typeof(self) weakself = self;
    self.block = ^ {
        __strong typeof(self) strongself = weakself;
        NSLog(@"self.name = %s", strongself.name.UTF8String);
    };
    self.block();
}

- (void)dealloc {
    NSLog(@"-------dealloc-------");
}

@end

打印结果:
//2019-05-04 19:27:48.274358+0800 BlockTest[37426:17007507] self.name = roy
//2019-05-04 19:27:48.275016+0800 BlockTest[37426:17007507] -------dealloc-------

我们看到Person对象被正常释放了,说明不存在循环引用,为什么呢?clang改写后的代码如下:

struct __Person__testReferenceSelf_block_impl_0 {
  struct __block_impl impl;
  struct __Person__testReferenceSelf_block_desc_0* Desc;
  Person *const __weak weakself;
  __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __weak _weakself, int flags=0) : weakself(_weakself) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
    __attribute__((objc_ownership(weak))) typeof(self) weakself = self;
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, weakself, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}

可以看到__Person__testReferenceSelf_block_impl_0结构体中weakself成员是一个__weak修饰的Person类型对象,也就是说__Person__testReferenceSelf_block_impl_0对Person的依赖是弱依赖。weak修饰变量是在runtime中进行处理的,在Person对象的Dealloc方法中会调用weak引用的处理方法,从weak_table中寻找弱引用的依赖对象,进行清除处理。

  • __strong(block执行过程中的强制持有)

当在block外使用 __weak 修饰对象,虽然可以防止循环引用,但是这个对象在block正在执行的过程中也可能会变成nil,这就可能会带来一些未知问题。这种情况一般在多线程回调过程中比较常见,如子线程处理完任务后回调时,不能保证block中的原对象还未被释放。

TestObj *obj = [[TestObj alloc] init];
__weak TestObj *weakObj = obj;
NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
block = ^(){
    NSLog(@"TestObj对象地址:%@",weakObj);
    dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
        for (int i = 0; i < 1000000; i++) {
            // 模拟一个耗时的任务
        }
        NSLog(@"耗时的任务 结束 TestObj对象地址:%@",weakObj);
    });
};
NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
block();

//打印结果:
//DemoWeek[19247:6816518] before block retainCount:1
//DemoWeek[19247:6816518] after block retainCount:1
//DemoWeek[19247:6816518] TestObj对象地址:<TestObj: 0x602000006af0>
//DemoWeek[19247:6816518] TestObj 对象已释放
//DemoWeek[19247:6816544] 耗时的任务 结束 TestObj对象地址:(null)

正确的做法是,在block执行的开始检验弱引用的对象是否还存在,如果还存在则使用__strong进行强引用。这样,在block执行的整个过程中,该对象就不会被置为nil,而在block执行完毕后,对象的引用计数就会-1,这样就不会导致对象无法释放。

block = ^(){
    __strong  TestObj *strongObj = weakObj;
    if(! strongObj) return;
    NSLog(@"TestObj对象地址:%@",strongObj);
    dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
        for (int i = 0; i < 1000000; i++) {
            // 模拟一个耗时的任务
        }
        NSLog(@"耗时的任务 结束 TestObj对象地址:%@",strongObj);
    });

//打印结果:
//DemoWeek[19280:6819437] before block retainCount:1
//DemoWeek[19280:6819437] after block retainCount:1
//DemoWeek[19280:6819437] TestObj对象地址:<TestObj: 0x602000006b30>
//DemoWeek[19280:6819464] 耗时的任务 结束 TestObj对象地址:<TestObj: 0x602000006b30>
//DemoWeek[19280:6819464] TestObj 对象已释放

4. block 的存储区域

上面代码中结构体__block_impl中的isa指针指向_NSConcreteStackBlock类型的类对象,其实该类对象总共有三种类型:

_NSConcreteStackBlock // 栈
_NSConcreteMallocBlock // 堆
_NSConcreteGlobalBlock // 数据区

block不同存储区域,实例如下:

void (^globalBlock)(void) = ^{
    NSLog(@"Global Block");
};
 
int main() {
    globalBlock();
    NSLog(@"%@",[globalBlock class]); // 打印:__NSGlobalBlock__
    

    void (^stackBlock)(void) = ^{ // 没有截获自动变量的Block
        NSLog(@"Stack Block");
    };
    stackBlock();
    NSLog(@"%@",[stackBlock class]); // 打印:__NSStackBlock__
    

    int i = 1;
    void (^mallocBlock)(void) = ^{ // 截获自动变量i的Block
        NSLog(@"Malloc Block:%d", i);
    };
    mallocBlock();
    NSLog(@"%@",[mallocBlock class]);//打印:__NSMallocBlock__
}

block 是一个对象,所以 block 理论上是可以 retain/release 的,但是 block 在创建的时候它的内存是默认分配在栈(stack)上,而不是堆(heap)上的,所以它的作用域仅限创建时候的上下文(函数,方法···),当你在该作用域外调用该 block 时,程序就会奔溃.

如果其所属的栈作用域结束,该block就会被废弃,对于超出block作用域仍需使用block的情况,从clang过的block相关代码中可见,其提供了将block从栈上复制到堆上的方法来解决这种问题。即便Block栈作用域已结束,但被拷贝到堆上的block还可以继续存在。

复制到堆上的block实例,会修改其成员变量isa为_NSConcreteMallocBlock类对象:

impl.isa = &_NSConcreteMallocBlock;

在ARC有效时,大多数情况下编译器会进行判断,自动生成将Block从栈上复制到堆上的代码,以下几种情况栈上的block会自动复制到堆上:
调用block的copy方法;
将block作为函数返回值时;
将block赋值给__strong修改的变量时;
向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时。

非ARC情况下向方法的参数中传递block,则需要开发者手动调用copy方法复制。

相关文章

  • iOS Block实例

    iOS之Block详解:Block详解 ViewController.h(ARC) ViewController....

  • Block - block简单的使用

    参考文档 iOS Block详解 一、忘记block格式? 样例一.png 样例二.png 二、Block的定义 ...

  • SDWebImage4.0源码探究(二)具体代码拓展

    代码一 知识点:block参考:iOS中block的详解weakSelf、strongSelf <转自唐巧>Blo...

  • 关于block的理解

    block的类型详解 关于block的知识,在网络上的资料那是相当的多。不过这里还是想来谈谈自己对block的理解...

  • iOS 题目详解 部分三

    主要讲解Block 内部使用strongSelf的理由和用法 iOS 题目详解 部分一iOS 题目详解 部分二...

  • Block详解

    1.Block定义及使用 首先看下Block的定义和使用。 实际使用方法 Block看起来比较复杂,在OC中实际上...

  • Block详解

    1️⃣Block的修饰 ARC情况下( ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Ref...

  • block详解

    __weak typeof(self) weakSelf = self;self.handler = ^{type...

  • Block详解

    __block说明符 Block只能保存局部变量瞬间的值,所以当我们尝试修改截获的自动变量值,就会报错。例如: 该...

  • Block详解

    block的定义,调用等就不介绍了,自行去查资料。 本文介绍内容: 1.block的底层数据结构2.block的类...

网友评论

      本文标题:—— block详解

      本文链接:https://www.haomeiwen.com/subject/fbxeeltx.html