Block

5/19/2021

# Block的结构体

下面为oc的一个简单的block:

void block4() {
    int outA = 8;
    int (^block)(int) = ^(int a) {
        return outA + a;
    };
    outA = 5;
    int result = block(3);
    NSLog(@"block4 -> result = %d", result);
}
1
2
3
4
5
6
7
8
9

对其进行clang编译之后

struct __block4_block_impl_0 {
    struct __block_impl impl;
    struct __block4_block_desc_0* Desc;
    int outA;
    __block4_block_impl_0(void *fp, struct __block4_block_desc_0 *desc, int _outA, int flags=0) : outA(_outA) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static int __block4_block_func_0(struct __block4_block_impl_0 *__cself, int a) {
    int outA = __cself->outA; // bound by copy

    return outA + a;
}

static struct __block4_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __block4_block_desc_0_DATA = { 0, sizeof(struct __block4_block_impl_0)};

void block4() {
    int outA = 8;
    int (*block)(int) = ((int (*)(int))&__block4_block_impl_0((void *)__block4_block_func_0, &__block4_block_desc_0_DATA, outA));
    outA = 5;
    int result = ((int (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_0y_zzzq899s2js3g0f8d71jvbnm0000gp_T_main_2d0f53_mi_3, result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

block结构体包括以下内容:

  • block_impl
  • block_desc
  • 复制的外部变量
  • 构造函数

而真正执行的过程如下:

调用过程void block4, 其中第二句调用__block4_block_impl_0构造函数初始化结构体:__block4_block_impl_0((void *)__block4_block_func_0, &__block4_block_desc_0_DATA, outA)得到__block4_block_impl_0类型变量赋值给block,然后执行block->FuncPtr。事实上,使用clang编译后得到的C代码,发现结构体函数使用_outA初始化结构体中的outA变量,然后再改变outA的值,在真正调用FuncPtr的时候是使用结构体中的outA的值,所以改变outA的值,result仍是11

苹果Block源码地址为:https://opensource.apple.com/source/libclosure/ (opens new window) 选择最新的文件夹查看Block_private.h (opens new window)即可。

主要使用以下几个结构体:

typedef void(*BlockCopyFunction)(void *, const void *);
typedef void(*BlockDisposeFunction)(const void *);
typedef void(*BlockInvokeFunction)(void *, ...);
typedef void(*BlockByrefKeepFunction)(struct Block_byref*, struct Block_byref*);
typedef void(*BlockByrefDestroyFunction)(struct Block_byref *);

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler
#endif

    BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_descriptor_small {
    uint32_t size;

    int32_t signature;
    int32_t layout;

    /* copy & dispose are optional, only access them if
       Block_layout->flags & BLOCK_HAS_COPY_DIPOSE */
    int32_t copy;
    int32_t dispose;
};

struct Block_layout {
    void * __ptrauth_objc_isa_pointer isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};


// Values for Block_byref->flags to describe __block variables
enum {
    // Byref refcount must use the same bits as Block_layout's refcount.
    // BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime

    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler

    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime

    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime
};

struct Block_byref {
    void * __ptrauth_objc_isa_pointer isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

网上找了一张老版本的图:block-struct

# block_impl

也就是Block_layout结构体,包括内容

  • isa 表示block是一个对象,就是_NSConcreteStackBlock_NSConcreteMallocBlock_NSConcreteGlobalBlock这几个
  • Flags 描述block对象,Block_private.h (opens new window)中定义的一个枚举值
// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime  标识栈Block
    BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler
#endif

    BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime     标识堆Block
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler    含有copy_dispose助手
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler    是否为全局Block
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • FuncPtr(invoke) 函数指针block_func,封装block代码的函数
  • Reserved 保留位

# block_desc

记录block的大小、copy和dispose方法

  • reserve
  • size
  • copy
  • dispose

# block_byref

当变量被__block修饰符修饰的时候,编译后会生成,结构体如下:

// Values for Block_byref->flags to describe __block variables
enum {
    // Byref refcount must use the same bits as Block_layout's refcount.
    // BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime

    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler    表示含有layout
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler

    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime

    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler    表示byref中含有copy_dispose函数,在__block捕获的变量为对象时就会生成copy_dispose函数用来管理对象内存
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime     判断是否要释放
};

struct Block_byref {
    void * __ptrauth_objc_isa_pointer isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

结构和Block_layout类似,

  • isa 指针
  • forwarding 在栈中指向自己,Block执行copy后指向堆中的byref。
  • flags 枚举,runtime中会用到
  • size 占用内存大小

# 变量捕捉

所谓的捕获外部变量,就是在block内部创建外部对应的变量。
全局变量整个项目都可以使用,所以block不用担心变量被释放,使用的时候直接访问。
局部变量有作用域,如果block调用的时候已经被释放,则会出现问题,所以需要捕获,在block内部创建对应的变量。

# 全局变量

不捕获,也就是内部不创建对应的变量。可以直接访问改变全局变量

# 局部变量

auto变量: 捕获,block结构体中创建对应的变量,将外部的值赋值给block内部对应的变量,属于值传递,block内部存在的是block内部创建的变量,所以在block内部不能够修改block外部变量的内容。(外部对象变量,传递的是变量是指针的地址,即栈中的地址,所以也是不可以改变的)
静态变量: 捕获,指针传递,因为静态变量的存储区域也在全局存储区,但可能有多个名称相同的静态变量存在,所以需要用地址访问。block内部可以修改静态变量的值

# Block的类型

  1. __NSGlobalBlock__block没有访问auto变量,内存区域为数据段
  2. __NSStackBlock__block访问了auto变量,内存区域为栈
  3. __NSMallocBlock__block调用了copy方法,内存区域为堆

# Block的复制操作

# 类型变化

  • NSGlobalBlock 调用copy后什么都不做
  • NSStackBlock 调用copy后从栈复制到堆,副本存储位置为堆
  • NSMallocBlock 调用copy后引用计数加一,副本存储位置为堆

# 自动copy情况

  • Block作为函数返回时
  • 将block赋值给strong指针时
  • Block作为cocoa api中方法名包含usingBlock的方法参数时,比如数组遍历
  • Block作为GCD api中方法参数时,比如:dispatch_onece, dispatch_after,执行完block后系统才对block执行release

# 为什么block要从栈拷贝到堆?

因为栈中的对象的内存空间为系统管理,可能随时被释放,而堆中的为开发者自己管理,当block在栈中时,可能存在在调用block的时候,block本身已经被释放,从而出现错误。

# __block修饰符

__block修饰符用来使外部auto变量能够在block内部进行修改。__block不能修饰全局变量和静态变量。__block修饰的变量,在block内部会转换成__Block_byref的结构体,我修改的只是对象中的一个属性,而不是修改这个对象:

  • isa
  • __Block_byref: forwarding,存储的是__Block_byref的地址,指向自己
  • flag
  • size
  • 变量

数据结构参考block_byref介绍

block在修改外部变量的时候是通过__Block_byref结构体的forwarding读取和修改的量

# forwarding的作用

为什么要通过forwarding转一下,而不是直接读取?

因为从栈中复制到堆中时,__Block_byref的地址发生改变,而通过forwarding转一下之后栈中__Block_byref的forwarding指向堆中的__Block_byref,堆中的forwarding还指向自己,从而保证访问__Block_byref中变量是同一份

# block的内存管理

  1. 当block在栈上时,不会对指向的对象产生强引用
  2. 当block从栈上复制到堆上时,会调用block内部的copy函数,copy函数会调用__Block_object_assign函数,会根据所指对象的修饰符(__strong,__weak)做出响应的操作,形成强引用或者弱引用。
  3. 当block从堆上移除时,会调用block内部的dispose函数,dispose函数会调用__Block_object_dispose函数,函数会自动释放所指对象

# 循环引用问题

  1. 用__weak解决,这样block就不强持有外部变量了
  2. 用__block解决,block执行时将__block变量设置为nil,这要求block必须被执行。

# UIViewAnimationBlock

UIView动画不需要考虑循环引用问题。

CoreAnimation相关知识:

  • UIView层
  • Layer层
  • data数据层

其中UIView层的block仅仅是提供了类似快照data的变化。当真正执行Animation动画时才会将“原有状态”与“执行完block的状态”做一个差值,来去做动画

# NSNotificationCenterBlock

不会循环引用,但会发生内存泄漏

[[NSNotificationCenter defaultCenter] addObserverForName:@“someNotification” object: nil queue:[NSOperationQueue mainQueue] usingBlock^(NSNotification *notification) {  
    self.someProperty = xyz;
}];
1
2
3

NSNotificationCenter单例生成并持有一个Observation,持有了弱引用self和强持有block, 但block持有self。但self并没有持有block和observation,所以不存在循环引用。由于self未持有方法返回的Observer,所以无法主动移除Observer,单例强持有了block,block持有了self,self不会被释放。

注意:此种方法返回的对象被系统强持有,self需要持有方法返回的对象,用于停止通知移除观察者。如果不持有这个对象,是无法彻底销毁这个通知的。

所以block一定要弱持有self,self要持有返回的observer,在delloc中移除通知

# NSNotificationCenterIVABlock

会循环引用,会发生内存泄漏

_observer =  [[NSNotificationCenter defaultCenter] addObserverForName:@“someNotification” object: nil queue:[NSOperationQueue mainQueue] usingBlock^(NSNotification *notification) {
  self.someProperty = xyz;
}];
1
2
3

NotificationCenter的observer强持有block,block持有self,self持有_observer形成循环引用。

# GCDBlock

不会循环引用,不会发生内存泄漏

dispatch_group_async(self.operationGroup, self.serialQueue, ^{
  [self doSomething];
});
1
2
3

self确实持有了queue,而block也确实持有了self。但并没有证据或者文档表面queue一定会持有block,而且即使queue持有了block,在block执行完毕的时候,由于需要从任务对列中移除,因此完全可以解除queue对block的持有关系,所以实际上这里也不存在循环引用。

# NSOperationQueueBlock

不会循环引用,不会发生内存泄漏

[[NSOperationQueue mainQueue] addOperationWithBlock^{
  self.someProperty = xyz;
}];
1
2
3

mainQueue持有了block,block持有了self,self并没有持有mainQueue,所以不存在循环引用[NSOperationQueue mainQueue] 并非单例,所以并没有内存泄漏

# block可以给NSMutableArray中添加元素吗?需不需使用__block?

答案:可以添加元素,不需要__blcok修饰符

因为block内部仅仅使用了NSMutableArray的内存地址,往其中添加元素时并没有修改NSMutableArray的地址,因此可以添加,且不用__block修饰符

Last Updated: 1/15/2023, 2:48:14 PM