This is the 24th day of my participation in the August Text Challenge.More challenges in August

Write in front: iOS underlying principle exploration is my usual development and learning in the accumulation of a section of advanced road. Record my continuous exploration of the journey, I hope to be helpful to all readers.Copy the code

The directory is as follows:

  1. IOS underlying principles of alloc exploration
  2. The underlying principles of iOS are explored
  3. The underlying principles of iOS explore the nature of objects & isa’s underlying implementation
  4. Isa-basic Principles of iOS (Part 1)
  5. Isa-basic Principles of iOS (Middle)
  6. Isa-class Basic Principles of iOS Exploration (2)
  7. IOS fundamentals explore the nature of Runtime Runtime & methods
  8. Objc_msgSend: Exploring the underlying principles of iOS
  9. Slow lookups in iOS Runtime
  10. A dynamic approach to iOS fundamentals
  11. The underlying principles of iOS explore the message forwarding process
  12. Dyld (part 1)
  13. IOS Basic Principles of application loading principle dyld (ii)
  14. IOS basic principles explore the loading of classes
  15. The underlying principles of iOS explore the loading of categories
  16. IOS underlying principles to explore the associated object
  17. IOS underlying principle of the wizard KVC exploration
  18. Exploring the underlying principles of iOS: KVO Principles | More challenges in August
  19. Exploring the underlying principles of iOS: Rewritten KVO | More challenges in August
  20. The underlying principles of iOS: Multi-threading | More challenges in August
  21. GCD functions and queues in iOS
  22. GCD principles of iOS (Part 1)
  23. IOS Low-level – What do you know about deadlocks?
  24. IOS Low-level – Singleton destruction is possible?
  25. IOS Low-level – Dispatch Source
  26. IOS bottom – a fence letter blocks the number
  27. IOS low-level – Be there or be Square semaphore
  28. IOS underlying GCD – In and out into a scheduling group
  29. Basic principles of iOS – Basic use of locks
  30. IOS underlying – @synchronized Flow analysis
  31. IOS low-level – The principle of lock exploration
  32. IOS Low-level – allows you to implement a read/write lock
  33. Implementation of Objective-C Block
  34. Implementation of Objective-C Block

Summary of the above column

  • Summary of iOS underlying principles of exploration

Sort out the details

  • Summary of iOS development details

preface

Today, we will start with the underlying structure, remove the veil of the Block, and begin with the process to explain the question from the interview question in the last article.

Block the underlying

To explore the underlying Block, it is necessary to compile it.

Let’s define a block:

Compile xcrun into a CPP file and look at its underlying implementation and structure:

static void _I_BlockViewController_viewDidLoad(BlockViewController * self, SEL _cmd){((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("BlockViewController"))}, sel_registerName("viewDidLoad"));


    int superman = 100;

    // the __BlockViewController__viewDidLoad_block_impl_0 function is called
    void(*myBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0((void *)__BlockViewController__viewDidLoad_block_func_0,
        &__BlockViewController__viewDidLoad_block_desc_0_DATA,
        superman));
    
    
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    Void (*myBlock)(void) = __BlockViewController__viewDidLoad_block_impl_0( __BlockViewController__viewDidLoad_block_func_0, __BlockViewController__viewDidLoad_block_desc_0_DATA, superman ); myBlock->FuncPtr(myBlock); * /

}
Copy the code

block_impl

MyBlock = __BlockViewController__viewDidLoad_block_impl_0

struct __BlockViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __BlockViewController__viewDidLoad_block_desc_0* Desc;
  int superman;
  __BlockViewController__viewDidLoad_block_impl_0(void *fp,
      struct __BlockViewController__viewDidLoad_block_desc_0 *desc, 
      int _superman, int flags=0) : superman(_superman){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

It’s a structure, and the structure is equal to the constructor of the structure; The constructor passes three parameters, the last of which is Superman and one of the members of the structure is Superman (superman is assigned to the member of the structure in the constructor as superman), which is strange because it is the same as our customized variable name.

Could this be a member that the Block automatically generates (and does the assignment in the constructor) from the captured external variable?

Let’s test this hypothesis:

Change myBlock to something like this:

After xrun:

There is a missing parameter, and the structure does not have superman member:

  void(*myBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0(
  (void *)__BlockViewController__viewDidLoad_block_func_0, 
  &__BlockViewController__viewDidLoad_block_desc_0_DATA));

    ((void(*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); . struct __BlockViewController__viewDidLoad_block_impl_0 { struct __block_impl impl; struct __BlockViewController__viewDidLoad_block_desc_0* Desc;__BlockViewController__viewDidLoad_block_impl_0(void *fp, struct __BlockViewController__viewDidLoad_block_desc_0 *desc, int flags=0){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

Let’s do a further test to prove our conjecture is correct:

The same xcrun looks like this:

    AppDelegate *app = ((AppDelegate *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("alloc"));
    void(*myBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0((void *)__BlockViewController__viewDidLoad_block_func_0, &__BlockViewController__viewDidLoad_block_desc_0_DATA, app, 570425344));

    ((void(*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); . struct __BlockViewController__viewDidLoad_block_impl_0 { struct __block_impl impl; struct __BlockViewController__viewDidLoad_block_desc_0* Desc; AppDelegate *app; __BlockViewController__viewDidLoad_block_impl_0(void *fp, struct __BlockViewController__viewDidLoad_block_desc_0 *desc, AppDelegate *_app, int flags=0) : app(_app){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

isa

Isa = &_nsConcretestackBlock; ; However, in our previous two summaries of blocks, a Block that captures an external variable and is not a weak reference in this case is a heap Block. In other words,

It gets compiled into a stack Block at compile time and then becomes a heap Block at run time,

So how does it become a heap Block? And that’s what we’re going to explore;

FuncPtr

In the constructor, the first fp we pass is assigned to FuncPtr; __BlockViewController__viewDidLoad_block_func_0 is a function:

static void __BlockViewController__viewDidLoad_block_func_0(struct __BlockViewController__viewDidLoad_block_impl_0 *__cself) {
  int superman = __cself->superman; // bound by copy


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_w3_ptw7ymvs5pjf98xqvjnp86wc0000gn_T_BlockViewController_385088_mi_0, superman);
    }
Copy the code

Then, on the next line, the FuncPtr call is executed.

The FuncPtr member of the Block saves what is to be executed in the constructor; if it is not called, then method execution is not called. So this is why the call must be executed

Methods the internal
  int superman = __cself->superman; // bound by copy
Copy the code

__cself is __BlockViewController__viewDidLoad_block_impl_0 (because myBlock->FuncPtr(myBlock);).

So, int superman here is the same as superman inside the structure (same value, different address);

What does __block do

Here we add __block to try:

  • Inside the Block:

Before is:

int superman;

After adding __block:

__Block_byref_superman_0 *superman; // by ref
Copy the code
  • inviewDidLoadIn a function internal call:

Before is:

int superman = 100;

After adding __block:


// Struct initialization
// The compiler omitted a line of code: int superman = 100;
__Block_byref_superman_0 superman = {
    (void*)0,
    (__Block_byref_superman_0 *)&superman, 
    0,
    sizeof(__Block_byref_superman_0),
    100
    };

//__attribute__((__blocks__(byref))) __Block_byref_superman_0 superman = {(void*)0,(__Block_byref_superman_0 *)&superman, 0, sizeof(__Block_byref_superman_0), 100};
Copy the code

Let’s look at the structure __Block_byref_superman_0:

struct __Block_byref_superman_0 {
  void *__isa;
__Block_byref_superman_0 *__forwarding;
 int __flags;
 int __size;
 int superman;
};
Copy the code

You can see it’s going to be&supermanAssigned to__Block_byref_superman_0The structure of the bodysuperman__forwardingMembers.

*__forwarding = &superman (100)

In the constructor inside the Block structure, _superman->__forwarding is assigned to the member variable superman inside the Block.

struct __BlockViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __BlockViewController__viewDidLoad_block_desc_0* Desc;
  __Block_byref_superman_0 *superman; // by ref
  __BlockViewController__viewDidLoad_block_impl_0(void *fp, struct __BlockViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_superman_0 *_superman, int flags=0) : superman(_superman->__forwarding){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

__block finishing

The above flow looks like this, after adding __block:

  • The original variable type changed from int to __Block_byref_superman_0; (The address we define as 100 is stored in the structure member variable *__forwarding is stored in the pointer — *__forwarding points to superman’s address)

  • Instead of assigning superman to block member superman, the _superman->__forwarding pointer is assigned to block member *superman pointer.

  • Finally, the FuncPtr call is __BlockViewController__viewDidLoad_block_func_0 :(inside the method __cself is the Block itself)

The original is:

// Superman is not the same as superman in Block
// Same value, different address - copy value
int superman = __cself->superman; // bound by copy
Copy the code

After adding __block:

    // Superman is the same as superman in our external __block
    // Here is the address passed in, which is a pointer assignment.
    // Superman can be modified
    __Block_byref_superman_0 *superman = __cself->superman; // bound by ref
    (superman->__forwarding->superman)++;
Copy the code
  • __block does the following:

You have a structure__Block_byref_superman_0And pass toBlockIs pointer address, to modify the same piece of memory space effect.

conclusion

  • Block automatically generates member variables from captured external variables (and assigns them to member variables in constructors)
  • After the __block modifier is added to the external variable, the underlying structure __Block_byref_superman_0 is generated, and the pointer address is passed to the Block, so as to modify the same piece of memory space effect.

Block underlying copy

We also see some methods around __BlockViewController__viewDidLoad_block_func_0 such as XXX __BlockViewController__viewDidLoad_block) :

Xxx_copy_0, xxx_dispose_0 xxX_DESc_0, and _Block_object_assign in xxX_COPY_0, etc. What are these? One more thing that we looked at in the last video is why it’s a stack Block at compile time, and it’s a heap Block at run time, and what happens at run time? Let’s explore these questions.

Since we want to explore how run-time blocks manipulate compile-time stack blocks with stacks of blocks, let’s run the code and debug it at a real machine breakpoint.

Open Assembly to see the assembly process:

Here we go to objc_retainBlock.

In objc source code:

id objc_retainBlock(id x) {
    return(id)_Block_copy(x); }...// Create a heap-based copy of the Block or simply add a reference to an existing Block
// This must be paired with Block_release to restore memory, even at run time
// Garbage collection in Objective-C.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    
// The reference is lost, or memory is restored if it is heap-based and last reference
BLOCK_EXPORT void _Block_release(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);


// Used by the compiler. Do not call this function yourself
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteStackBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    
Copy the code

After some searching, we found _Block_copy in libclosure;

_Block_copy

// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
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 {// stack stack (stack Block generated during compilation)
        // Its a stack block. Make a copy.
        size_t size = Block_size(aBlock);
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if(! result)return NULL;
        memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;

#if __has_feature(ptrauth_signed_block_descriptors)
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
            uintptr_t oldDesc = ptrauth_blend_discriminator(
                    &aBlock->descriptor,
                    _Block_descriptor_ptrauth_discriminator);
            uintptr_t newDesc = ptrauth_blend_discriminator(
                    &result->descriptor,
                    _Block_descriptor_ptrauth_discriminator);

            result->descriptor =
                    ptrauth_auth_and_resign(aBlock->descriptor,
                                            ptrauth_key_asda, oldDesc,
                                            ptrauth_key_asda, newDesc);
        }
#endif
#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;
        returnresult; }}... struct Block_layout {void * __ptrauth_objc_isa_pointer isa; // stack or heap Block
    volatile int32_t flags; // Contain reference number (identifier)
    int32_t reserved; // Process data
    BlockInvokeFunction invoke; // Function call
    struct Block_descriptor_1 *descriptor; // Description (whether destructor is in progress, whether there is a keep function, etc.)
    // imported variables}; .// The value of the Block_layout-> flag used to describe the block object
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
};
Copy the code

Let’s read the X0 register to see who received the message.

Is a global Block.

Let’s capture the external variables:

Breakpoint x0:

So here’s a stack Block.

At the end of the assembly code, we break the ret line and look at the X0 register (where the return value of the function is stored) :

Now, this is a heap Block, and we’re going through it_Block_copyAfter function, it goes from stack Block to heap Block;

Isa relabelling

_Block_copy function flow, if it is a free Block or a global Block is returned, then the stack Block generated at compile time and captured external variables will be copied (by size to create memory space, Memmove makes a copy of the return value of result, handles signature, flage, etc. Isa is re-marked as _NSConcreteMallocBlock heap Block),

When printing the X0 register heap, we print the following information:

(lldb) po 0x0000000281238e10
<__NSMallocBlock__: 0x281238e10>
 signature: "v8@? 0"
 invoke   : 0x1028bde58 (/private/var/containers/Bundle/Application/CFEA7FC5-6956-47CE-B759-AF1F32E7DDD5/bb.app/bb`__29-[ViewController viewDidLoad]_block_invoke) copy : 0x1028bde90 (/private/var/containers/Bundle/Application/CFEA7FC5-6956-47CE-B759-AF1F32E7DDD5/bb.app/bb`__copy_helper_block_e8_32s)
 dispose  : 0x1028bdec8 (/private/var/containers/Bundle/Application/CFEA7FC5-6956-47CE-B759-AF1F32E7DDD5/bb.app/bb`__destroy_helper_block_e8_32s)
Copy the code

The signature

Invoke (); invoke (); signature ();

One argument, no return value (v), starting at bit 0, is an object type and a Block type (@?).

(Why should we care about this signature? Invocation (copy or Hook) invokes a layer of message, involving the message mechanism. The last phase of the message flow must be signed before the Invocation.

Here we have an overview of signatures and ISA; But what are the following copy function dispose function?

Block structure

From the _Block_copy process above, at the end of the isa assignment, let’s look at the structure of the Block

struct Block_layout {
    void * __ptrauth_objc_isa_pointer isa; // stack or heap Block
    volatile int32_t flags; // Contain reference number (identifier)
    int32_t reserved; // Process data
    BlockInvokeFunction invoke; // Function call

    // Description (whether destructor is in progress, whether there is a keep function, etc.)
    // Optional parameters starting from Descriptor (memory contiguous optional)
    struct Block_descriptor_1 *descriptor; 
    // imported variables 
};
Copy the code

We found that copy and dispose are only available if you define type BLOCK_DESCRIPTOR_2.

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

We tried to figure out when to use BLOCK_DESCRIPTOR_2, but there were too many to find.

We don’t know how it was generated (the compiler probably did it for us, so we omit it), but we do know how it got there. As follows:

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}
Copy the code

So you get Block_descriptor_2 by saying _Block_get_descriptor += Block_descriptor_1 what does that mean? As shown below (because memory is contiguous, Block_descriptor_2 can be obtained by memory translation and Block_descriptor_3 can also be obtained by memory translation) :

The Block_descriptor_3 contains signature and layout;

#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
};
Copy the code

So our Block must have Block_descriptor_3 and Block_descriptor_2.

In this case, we can look at memory by x/10gx:

The above picture perfectly interprets the Block’s memory structure, including data such as invoke Descriptor (Copy Dispose) signature, which is printed out and verified one by one.

Capture variable

In the previous exploration, _Block_copy was a stack copy to heap process, but using xcrun we found a _Block_object_assign in the compiled code as follows:

__BlockViewController__viewDidLoad_block_copy_0

static void __BlockViewController__viewDidLoad_block_copy_0(struct __BlockViewController__viewDidLoad_block_impl_0*dst, struct __BlockViewController__viewDidLoad_block_impl_0*src) {

_Block_object_assign((void*)&dst->superman, 
(void*)src->superman,
 8/*BLOCK_FIELD_IS_BYREF*/);

}
Copy the code

It’s a little weird here,

There’s no relationship between these two copies, right

In fact, this is the copy in the red box in the picture above; As follows:

static struct __BlockViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  
  // Here here
  void (*copy)(struct __BlockViewController__viewDidLoad_block_impl_0*, struct __BlockViewController__viewDidLoad_block_impl_0*);
  
  
  void (*dispose)(struct __BlockViewController__viewDidLoad_block_impl_0*);
} __BlockViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __BlockViewController__viewDidLoad_block_impl_0),

  // Here here
__BlockViewController__viewDidLoad_block_copy_0, 

__BlockViewController__viewDidLoad_block_dispose_0};
Copy the code

A Block is just an object, and we’re doing an initial assignment to it. There is one Block_descriptor_1 and one Block_descriptor_2 (2 contains copy and dispose (dispose), which should also be initialized to give them an implementation of the function). __BlockViewController__viewDidLoad_block_copy_0, which is the copy function implementation.

There is a _Block_object_assign call in the copy implementation

After locating the source, we found the following comment:

Is the processing of the type of the captured variable

The flags parameter of _Block_object_assign and _Block_object_dispose is set to

    * BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,

    * BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and

    * BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.

If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16)
Copy the code

Verify:

We have an 8 here

This is because our example is a __block modified variable.

Let’s try it out with an OC object:

Next, we’ll enter the implementation of the _Block_object_assign method.

_Block_object_assign

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]; * * * * * * * * /
        // The normal object type is a direct retain operation
        // _Block_retain_object_default = dose nothind
        // Give the system level default ARC operation
        // _Block_retain_object_default = fn (arc)
        _Block_retain_object(object);
        // The value of object outside the block is assigned to the variable inside the block
        // They point to the same memory space and have different addresses
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /******* void (^object)(void) = ... ; [^{ object; } copy]; * * * * * * * * /
               
        // The block type performs _Block_copy
        
        *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; }}Copy the code

_Block_byref_copy

Forwarding is the same as the created forwarding.

This forwarding is ultimately assigned to the variable in the block function execution.

This should give you a clearer idea of what’s going on at the bottom of the Block.

This is why variables modified by __block are the same piece of memory, which is a pointer copy. The following two lines of code explain this clearly:

  • copy->forwarding = copy;
  • src->forwarding = copy;
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    // __block memory is the same as the same guy
    //
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;

        // The captured variable comes in
        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            // External variables are captured - memory processing - saving the life cycle of external variables
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src)); }}// already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
Copy the code

byref_keep

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep; //= __Block_byref_id_object_copy_131
    BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131
};
Copy the code

What are these two functions equal to? — Block_byref

// A variable modified by __block is compiled as a structure

/ / structure
struct Block_byref {
    void * __ptrauth_objc_isa_pointer isa; / / 8
    struct Block_byref *forwarding;  / / 8
    volatile int32_t flags; // contains ref count//4
    uint32_t size; / / 4
};
Copy the code

This should give you a clearer idea of what’s going on at the bottom of the Block.

The __block modified object is one of the following constructs

__Block_byref_superman_0

struct __Block_byref_superman_0 {
  void *__isa;
__Block_byref_superman_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *superman;
};
Copy the code

In viewDidLoad, we assign the following values to the structure:

The byref_keep function assigns the following values:

__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
Copy the code

The copy function calls the following:

Block_byref captures -> Object objects
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, * (void * *) ((char*)src + 40), 131);
}
Copy the code

What does char* DST + 40 mean here?

Our __Block_byref_superman_0 structure is superman, and the memory is shifted 40 bytes. So copy takes Object and then it goes to _Block_object_assign

BLOCK_FIELD_IS_BYREF and then BLOCK_FIELD_IS_OBJECT

supplement

Still an 8 when exploring the __weak modifier block, why?

The _Block_object_assign source code contains details:

    case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
    case BLOCK_FIELD_IS_BYREF:
Copy the code

conclusion

If it is a variable decorated with __block, it will compile to a Block_byref structure at compile time:


  1. Block does some copying from stack space to heap space;
  2. The block then captures the variable Block_byref –> and copies Block_byref (the entire object).
  3. Block_byref copies internal object members when the copy function is called

  • When a block captures a variable, it divides the type;
  • The Block_byref (structure) type is copied and saved. The byref_keep function is used to save the internal OBjec object.
  • Releasing is also basically a reverse process (Block_byref (structure) does a byref_destroy release functionSimilar to the copy
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
Copy the code