Objective-C runtime 拾遗 (三)——Block冷知识

发布时间:2019-06-19 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Objective-C runtime 拾遗 (三)——Block冷知识脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

动因

上次写代码时需要深入了解Block。发现Block is nothing but a struct。今天又拾一下牙慧,汇总一下资料。顺便记录几个码中的发现

值得读的参考

最好的文档
Clang
中文的话,这篇也够了,讲得比较细:
谈Objective-C block的实现
这篇也讲解得不错:
Block技巧与底层解析

另外跟本文无关的,这个人的blog很不错,很多底层知识。
mikeash

源码:
Block_private.h
runtime.c

参考代码写在前面

enum { // Flags from BlockLiteral
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    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
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#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_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported VARiables
};

Block中的isa

这也是为什么Block能当做id类型的参数进行传递。

但如Clang文档中所述:

The isa field is set to the address of the external _NSconcreteStackBlock, which is a block of uninitialized memory supplied in libSystem, or _NSConcreteGlobalBlock if this is a static or file level Block literal.

首先_NSConcretestackBlock是外部的,是libSystem提供的地址,a block of uninitialized memory,实际情况是(模拟器):

(lldb) p &amp;_NSConcreteStackBlock
(void *(*)[32]) $25 = 0x00000001108dc050

(lldb) p _NSConcreteStackBlock
(void *[32]) $26 = {
  [0] = 0x00000001108dc0d0
  [1] = 0x000000010e2e6000
  [2] = 0x00007f8b88d0d100
  [3] = 0x0000000400000007
  [4] = 0x00007f8b8aa00310
  [5] = 0x0000000000000000
  [6] = 0x0000000000000000
  [7] = 0x0000000000000000
  [8] = 0x0000000000000000
  [9] = 0x0000000000000000
  [10] = 0x0000000000000000
  [11] = 0x0000000000000000
  [12] = 0x0000000000000000
  [13] = 0x0000000000000000
  [14] = 0x0000000000000000
  [15] = 0x0000000000000000
  [16] = 0x000000010de91198
  [17] = 0x000000010e2e5fd8
  [18] = 0x000000010db4CF70
  [19] = 0x0000000000000000
  [20] = 0x00007f8b8aa00350
  [21] = 0x0000000000000000
  [22] = 0x0000000000000000
  [23] = 0x0000000000000000
  [24] = 0x0000000000000000
  [25] = 0x0000000000000000
  [26] = 0x0000000000000000
  [27] = 0x0000000000000000
  [28] = 0x0000000000000000
  [29] = 0x0000000000000000
  [30] = 0x0000000000000000
  [31] = 0x0000000000000000
}

分析之后的结果是这样的:

  • 所有stack Blockisa都指向_NSConcreteStackBlock,runtime的时候已经初始化了。何时初始化未知。

  • _NSConcreteStackBlock是个数组,size为32

  • 水平有限,没看出规律。应该是被平分成两部分,第二部分从_NSConcreteStackBlock[16]开始

  • 元素都是Class(废话),如__NSStackBlock__,__NSStackBlock

  • _NSConcreteGlobalBlock类似

  • 代码也说明了这一点

BLOCK_export void * _NSConcreteMallocBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_ExpORT void * _NSConcreteAutoBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

关于flags

除了标示Block的类型,还用作reference counting:
volatile int32_t flags; // contains ref count
因为最低位被BLOCK_DEALLOCATING使用了,所以ref count每次+2

关于invoke

看代码:

id b = ^(int n, double d, char* s){
    NSLOG(@"%d %lf %s",n, d, s);
};
    
((__bridge struct Block_layout*)(b))->invoke((__bridge void *)(b),1,2.345,"hello");

官方解释:

The invoke function pointer is set to a function t@R_406_2570@ takes the Block structure as its First argument and the rest of the arguments (if any) to the Block and executes the Block compound statement.

Runtime Helper Functions

源码的注释这样说的:

A Block can reference four different kinds of things that require help when the Block is copied to the heap.
1) C++ stack based objects
2) References to Objective-C objects
3) Other Blocks
4) __block variables

In these cases helper functions are synthesized by the compiler for use in Block_copy and Block_release, called the copy and dispose helpers. The copy helper emits a call to the C++ const copy constructor for C++ stack based objects and for the rest calls into the runtime support function _Block_object_assign. The dispose helper has a call to the C++ destructor for case 1 and a call into _Block_object_dispose for the rest.

简单来说,当Block引用了 1) C++ 栈上对象 2)OC对象 3) 其他block对象 4) __block修饰的变量,并被拷贝至堆上时则需要copy/dispose辅助函数。辅助函数由编译器生成。
除了case 1,其他三种case都会分别调用下面的函数:

void _Block_object_assign(void *destAddr, const void *object, const int flags);
void _Block_object_dispose(const void *object, const int flags);

_Block_object_assign(或者_Block_object_dispose)会根据flags的值来决定调用相应类型的copy helper(或者dispose helper)
例如:

switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        _Block_retain_object(object);
        _Block_assign((void *)object, destAddr);
        break;
      ...
}

上述代码中_Block_retain_object _Block_assign以SPI的形式提供给runtime 和 CoreFoundation进行注入。

原作写于segmentfault 链接

脚本宝典总结

以上是脚本宝典为你收集整理的Objective-C runtime 拾遗 (三)——Block冷知识全部内容,希望文章能够帮你解决Objective-C runtime 拾遗 (三)——Block冷知识所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。