Block 原理分析

448 阅读2分钟

一.Block类型

block是Block_layout类型

Block_layout结构如下:

struct Block_layout {

    void *isa;

    volatile int32_t flags; // contains ref count

    int32_t reserved;

    BlockInvokeFunction invoke;

    struct Block_descriptor_1 *descriptor; //

    // imported variables

};

Block_descriptor结构如下:

#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

};

Block_layout中的flag枚举:

Values for Block_layout->flags to describe block objects
enum {

    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

};

block在编译时就已经对进行了拷贝,无论你是否调用。

Block的三层拷贝:

一.block为啥能捕获变量:

1.在创建block时,block自身被当作型参数传入了block内部,只是在OC层面是隐形参数。

Block的拷贝顺序:

1.block第一层拷贝是调用_Block_copy对自身进行拷贝。

// 栈 -> 堆 研究拷贝

void *_Block_copy(const void *arg) {

    struct Block_layout *aBlock;

\


    if (!arg) return NULL;

    

    // The following would be better done as a switch statement

    aBlock = (struct Block_layout *)arg;

    if (aBlock->flags & BLOCK_NEEDS_FREE) {

        // latches on high

        latching_incr_int(&aBlock->flags);

        return aBlock;

    }

    else if (aBlock->flags & BLOCK_IS_GLOBAL) {

        return aBlock; // 不需要

    }

    else { // 栈

        // Its a stack block.  Make a copy.

        struct Block_layout *result =

            (struct Block_layout *)malloc(aBlock->descriptor->size);

        if (!result) return NULL;

        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first

#if __has_feature(ptrauth_calls)

        // Resign the invoke pointer as it uses address authentication.

        result->invoke = aBlock->invoke;

#endif

        // reset refcount

        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed

        result->flags |= BLOCK_NEEDS_FREE | 2// logical refcount 1

        _Block_call_copy_helper(result, aBlock);

        // Set isa last so memory analysis tools see a fully-initialized object.

        result->isa = _NSConcreteMallocBlock;

        return result;

    }

}

2.block如果捕获了外界外界变量时,会对变量进行一次拷贝,保存变量和block在同一片内存。(捕获一般变量,如:int,float)

// __block 变量

void _Block_object_assign(void *destArg, const void *object, const int flags) {

    const void **dest = (const void **)destArg;

   

    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {

     

        case BLOCK_FIELD_IS_OBJECT:

        /*******

        id object = ...;

        [^{ object; } copy];

        ********/

        // objc 指针地址 weakSelf (self)

            // arc

        _Block_retain_object(object);

            // 持有

        *dest = object;

        break;


      case BLOCK_FIELD_IS_BLOCK:

        /*******

        void (^object)(void) = ...;

        [^{ object; } copy];

        ********/

            

            // block 被一个 block 捕获




        *dest = _Block_copy(object);

        break;

    

      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:

      case BLOCK_FIELD_IS_BYREF:

        /*******

         // copy the onstack __block container to the heap

         // Note this __weak is old GC-weak/MRC-unretained.

         // ARC-style __weak is handled by the copy helper directly.

         __block ... x;

         __weak __block ... x;

         [^{ x; } copy];

         ********/

            

        *dest = _Block_byref_copy(object);

        break;

        

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:

        /*******

         // copy the actual field held in the __block container

         // Note this is MRC unretained __block only. 

         // ARC retained __block is handled by the copy helper directly.

         __block id object;

         __block void (^object)(void);

         [^{ object; } copy];

         ********/


        *dest = object;

        break;




      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:

        /*******

         // copy the actual field held in the __block container

         // Note this __weak is old GC-weak/MRC-unretained.

         // ARC-style __weak is handled by the copy helper directly.

         __weak __block id object;

         __weak __block void (^object)(void);

         [^{ object; } copy];

         ********/




        *dest = object;

        break;


      default:

        break;

    }

}

3.block捕获__block修饰的外界变量时,有如下情况:

struct Block_byref_2 {

    // requires BLOCK_BYREF_HAS_COPY_DISPOSE

    BlockByrefKeepFunction byref_keep; // 结构体 __block  对象

    BlockByrefDestroyFunction byref_destroy;

};

__block修饰的变量就会多出 Block_byref_2

如何查看block底层原理:

1.Object-C的编译前端是LLVM,所以只需要使用LLVM的子集clang进行如下操作:

clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件 UIKit报错问题
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.0.sdk main.m

2.Xcode对clang进行了封装,所以使用xcrun编译更方便,一般不用考虑SDK的版本,所以相对好用些。如下:

`xcode`安装的时候顺带安装了`xcrun`命令,`xcrun`命令在`clang`的基础上进行了 一些封装,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc ViewController.m -o ViewController-arm64.cpp (模拟器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o ViewController-arm64.cpp (手机)


操作如下,cd到文件目录,进行上面的命令就可以,如下图:

Snip20210729_1.png

Snip20210729_2.png

剩下就自己查看.cpp文件了。 如果是swift的话,这个命令是无效的,因为swift的编译前端是swift,后续在更新swift是如何编译的。