preface

Write this article struggled for a long time, feel it is difficult to write well but feel block is very important, primary exploration went through the source code also read some posts, but explore forgot not a little feeling, only know that when the need to modify block capture external variables, variables need to add __block modification, but why? When a block is a property, it is usually modified with copy, but why? One of the biggest problems with blocks is circular references. We use weak to solve this problem, but are weak references really necessary?

What is a Block?

I’ll just write a block, clang compiles it and see what the underlying structure is

int a=10;
void(^Myblock)(void)=^{
   NSLog("%d",a);
};
Myblock();
Copy the code

The underlying structure of clang after compilation

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0){ impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself){}static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void(*Myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
      ((__block_impl *)Myblock)->FuncPtr)((__block_impl *)Myblock);
    }
    return 0;

}
Copy the code

Analysis:

  • blockThe code block is finally compiled to __main_block_IMPL_0The structure of the bodyThe structure is essentially the same thingobject, so block is also aAn object with a reference count
  • FuncPtr = fp; FuncPtr = fp; FuncPtr = fp; FuncPtr = FP;
  • impl.isa = &_NSConcreteStackBlock“, indicating that a block exists on the stack, but the block is actually a heap block, but why is it a stack block when compiled? If you think about it, heap memory is added dynamically,Heap memory allocation is not possible at compile time, at this time onlyTemporary allocation into stack blocks.

Block type

Since a block is an object, what memory region is it in?

Global block

void (^block)(void) = ^{
       
};
/ / or
static int a=10;
void (^block)(void) = ^{
     NSLog(@"%ld",a);  
};
Copy the code

NSGlobalBlock Global block: As above, no external variables are used inside the block, or only static variables and global variables are used

Stack block

int b=10;
void (^__weak mblock)(void) = ^{
   NSLog(@"%ld",b);
};
Copy the code

NSStackBlock stack block: in the stack area, valid on the stack, destroyed after the stack. When you use external variables internally, when you call a function, you create a chunk of memory on the stack, and when the function is done, that chunk of memory is destroyed and you’re out of the stack. You can’t assign to a strong reference or copy modified variable, because the function is destroyed after the call, and strong references cause wild Pointers. ARC is not specified, and generally there is no stack block

Heap block

int b=10;
void (^mblock)(void) = ^{
   NSLog(@"%ld",b);
};
Copy the code

NSMallocBlock Heap block: In the heap area, external variables can be used. Heap blocks can be strongly referenced and copied, and are destroyed depending on the reference count, just as we create objects, when the reference count reaches zero. In the example, mblock itself is a strong reference. Ninety-nine percent of actual development is heap blocks, so heap blocks are our focus.

Block refers to external variables

Take a look at the demo and console print below

// Create a LGTeacher object
@interface LGTeacher : NSObject
@property (nonatomic, copy) NSString *hobby;
- (void)teacherSay;
@end

LGTeacher* lgter=[[LGTeacher alloc]init];
NSLog(@"%@-%p",lgter,&lgter);
void(^Myblock)(void)=^{
    lgter.hobby=@"test";
    NSLog(@"%@-%p",lgter,&lgter);
};
 Myblock();
Copy the code

Output:

<LGTeacher: 0x101218ad0>-0x7ffeefbff200
<LGTeacher: 0x101218ad0>-0x7ffeefbff1e8
Copy the code

The address of the external lgter pointer is 0x7FFeEFbff200, and the address of the internal lgter pointer is 0x7FFeEFbff1e8. Although they both point to the same object, the address of the pointer is not the same, indicating that the lgter inside the block is a new variable. If we set lgter=nil inside a block, what we want to do is change the address of the external lgter pointer, but inside the block the lgter is no longer the external lgter, we can only change the value of the object that the lgter pointer points to inside the block, Lgter.hobby =@”test” can change the value of the object to which it points.

Take a look at the __block modifier.

@interface LGTeacher : NSObject
@property (nonatomic, copy) NSString *hobby;
- (void)teacherSay;
@end

__block LGTeacher* lgter=[[LGTeacher alloc]init];
NSLog(@"%@-%p",lgter,&lgter);
void(^Myblock)(void)=^{
    lgter.hobby=@"test";
    NSLog(@"%@-%p",lgter,&lgter);
    lgter=nil;
};
 Myblock();
Copy the code

Output:

<LGTeacher: 0x1007275d0>-0x7ffeefbff1f0
<LGTeacher: 0x1007275d0>-0x7ffeefbff1f0
Copy the code

The external lgter pointer address of the block is 0x7FFeEFBff1f0, and the internal lgter pointer address of the block is 0x7FFeEFBff1F0, which points to the same LGTeacher object 0x1007275d0. The address of the pointer to the variables inside the block has not changed, so we can change the pointer like lgter=nil, because the Pointers to the variables inside and outside the block are the same.

throughclangLet’s compile it to see how it’s used at the bottom__blockWhat’s the difference between decorating and not using __blockThis is the underlying structure of objects that are not decorated with __block,blockIs aThe structure of the bodyThe structure has an external lgter that belongs to the structure. The external lgter is passed as a parameter to the constructor of the block structure. In the constructor, the external lgter is assigned to the lgter inside the block.This is just assignment.Lgters inside blocks have their own memory structure.

Let’s look at usage__blockDecorated object

  • use__blockThe modified object LGTeacher has been compiled__block_byref_lgter_0The structure, in the block constructor, takes the arguments_block_byref_lgter_0Structure pointer__forwardingAssign to the block attribute _block_byref_lgter_0, and__forwardinganotherPoint to your__block_byref_lgter_0 pointer
  • It’s a bit of a mouthful, but to put it in perspective, the short summary is that this is aPassing of Pointers, eventually pointing to the _block_byref_lgter_0 structure._forwarding can refer to itselfCan also point toA copy of myselfThat is, with the presence of this pointer, __block variables can be accessed correctly whether they are configured on the heap or stack.
  • If you look closely at an object decorated with a __block, the object is compiled into a structure with one__Block_byref_id_object_copyMethods and___Block_byref_id_object_disposeMethod, as analyzed below.

conclusion

  • __blockThe object of the modifier isPointer passedThe __block variable can be accessed correctly whether configured on the heap or stack.
  • No use __blockThe object of the modifier isValue passedThe variables inside the block and the variables outside the block are not the same pointer, but have the same value orThe objects are the same

A circular reference to a block

Let’s think about why we always use the copy keyword when we declare a block property.

We call a block a code block, which is like a method. And each method is called when the hard disk to memory, and then to execute, execute disappeared, so, method memory does not need us to manage, that is to say, the method is in the memory stack area. So, unlike OC class objects (in the heap), blocks are also in the stack. If we use a block as a property of an object, we will use the keyword copy to modify it, because it is on the stack and we have no control over its demise. When we use copy, the system will copy the implementation of the block to the heap, so that our corresponding property has ownership of the block. This guarantees that the block will not die prematurely.

Remember that demo we wrote in the beginning, where it was a heap block, but it compiled as a stack block, so how does a stack block become a heap block?

int a=10;
void(^Myblock)(void)=^{
  NSLog("%d",a);
};
Myblock();
Copy the code

Assembles a look at the calling procedureAssembler looks at the flow, initially will call objc_retainBlock, follow upobjc_retainBlockWill call againBlock_copyIsn’t that a copy of the heap block that we’re looking at? Look at the source code of this function

void *_Block_copy(const void *arg) {
    // The underlying structure of block is a Block_layout
    struct Block_layout *aBlock;
    if(! arg)return NULL;
    aBlock = (struct Block_layout *)arg;
    // Whether the block needs to be released
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // Global block returns directly
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // stack-heap (compile time), stack block copy, stack block copy from the stack
        size_t size = Block_size(aBlock);
        // Create heap block memory
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if(! result)return NULL;
        // copy stack blocks into heap blocks
        memmove(result, aBlock, size); 
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
        / / to omit...
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2// logical refcount 1
        // Get copy, which is actually a copy of an object
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        returnresult; }}Copy the code

Analysis:

  • We have analyzed that the underlying structure of block is a structure through Clang above, and the specific structure of this structure is actuallyBlock_layoutStructure.
  • If is aBlockGlobal block.copyIt’s a direct return
  • If is aBlockStack block, then it will be in the heap areamallocOpen up memory and block the stack areacopyInto the newThe heap area.Block_call_copy_helperGet copy. Copy is called_Block_object_assignMethod changes the reference count or memory copy of an external object.

_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)) {
      // The object reference count increases by 1
      case BLOCK_FIELD_IS_OBJECT:
        _Block_retain_object(object);
        *dest = object;
        break;
        // Block type, _Block_copy
      case BLOCK_FIELD_IS_BLOCK:
        *dest = _Block_copy(object);
        break;
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      // The __block modified object copies the entire structure into the heap
      case BLOCK_FIELD_IS_BYREF:
        *dest = _Block_byref_copy(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        *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:
        *dest = object;
        break;
      default:
        break; }}Copy the code
  • ifExternal variables are objects._Block_retain_objectMake an external objectThe reference count increases by 1, so use external variables inside the block, such asselfYou need to use a weak reference, otherwise the block internally will strongly reference self so thatThe reference count increases by 1.Stack blockReferring to external variables also makesObject's reference count increases by 1If theStack blockCopy it into theThe heapThe area,The reference count for external objects is increased by one.
  • If the external variable isBlock type, _Block_copy
  • If the external variable is__blockModified, __block modified objects are compiled into oneblock_byrefThe structure, then it willCopy the entire structure into the heap.

Example exploration and analysis

After the analysis of blocks above, we have a general cognition and understanding, the ultimate goal is to use and avoid mistakes in development, so let’s look at a few examples to consolidate and understand.

Object reference counting understanding

    NSObject *objc = [NSObject new];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); / / 1
    void(^strongBlock)(void) = ^ {// 1 -block -> objc capture + 1 = 2
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();
    void(^__weak weakBlock)(void) = ^ {/ / + 1
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();
Copy the code

Output:

1
3
4
5
Copy the code

Analysis: the first output 1 can be understood, why is the second output 3? StrongBlock compiles the underlying stack block, passes in external variables and the reference count is +1. Run time copies the stack block into the heap and calls _Block_retain_object to increase the reference count of the object by 1. The increments of the third and fourth reference counts are actually the second decomposition, the first stack +1, and the second heap +1.

Memory copy understanding

    int a = 0;
    void(^ __weak weakBlock)(void) = ^{
        NSLog(@"-----%d", a);
    };
    struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
    // Depth copy
    id __strong strongBlock = [weakBlock copy];
    blc->invoke = nil;
    void(^strongBlock1)(void) = strongBlock;
    strongBlock1();
Copy the code

The output

0
Copy the code

Analysis: WeakBlock is a stack block, BLC through type strong transfer is also a stack block with weakBlock pointing to the same piece of memory space, strongBlock is weakBlock memory copy is a heap block, BLC ->invoke=nil to empty the stack block memory, But this does not affect the memory of the heap block, so it can output

Stack release difference

- (void)blocktest{
    NSObject *a = [NSObject alloc];
    // Check that the block is not a block
    void(^weakBlock)(void) = nil;
    {// temporary scope
        / / stack block
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"% @" -- -, a);
        };
        weakBlock = strongBlock;
        NSLog(@"1 - %@ - %@",weakBlock,strongBlock);

    }
    weakBlock();
  }
Copy the code

Output:

 1 - <__NSMallocBlock__: 0x281e830c0> - <__NSStackBlock__: 0x16b80cf10>
 
---(null)
Copy the code

Analysis:weakBlockDo not make a special designation is oneHeap block.Temporary scopeWithin thestrongBlockIs aStack blockThe heap block is assigned to a stack block, notice hereNot a copy, is just an assignment to the same piece of memory. Print __NSMallocBlock__ and __NSStackBlock__ respectively. Why print a to be null? LLDB breakpointAt the break point at line 81, at this pointweakBlockandstrongBlockPoint to theThe same stack memory spaceBut after the breakpoint passes 82 lines, strongBlock nowStack memory is freedStrongBlock lifetime is in temporary scope and is released when out of scope. If the stack memory is released and the heap block points to this memory, why print null? Should not beWild pointerThis point is not clear at present, hope to know the guidance. If you don’t want to print null, you can do thisWeakBlock = strongBlock copyCopy stack blocks to the heap.