The type of the Block

There are three common types of blocks in daily development: __NSGlobalBlock__(global Block) __NSStackBlock__(stack Block) __NSMallocBlock__(heap Block)

We can see in the Block source and _NSConcreteFinalizingBlock _NSConcreteAutoBlock, _NSConcreteWeakBlockVariable type of Block, but we will not meet in development, won’t go to the discussion

What exactly is a Block?

We already know that blocks are divided into three types, but what exactly is a Block? Let’s write the simplest Block in main.m

int main(int argc, const char * argv[]) {
	id block = ^{};
	return 0;
}
Copy the code

Use xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m to compile C++ code and delete useless code

main

int main(int argc, const char * argv[]) {
	id block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
	return 0;
}
Copy the code

In main, the __main_block_IMPL_0 method is called, and two parameters, __main_block_func_0 and &__main_block_desc_0_DATA, are passed. __main_block_IMPL_0 () is the constructor of the same name, which assigns values to the members of the Block. The Block also contains an ISA pointer. From the previous article, we learned that all isa Pointers are OC objects. Then we see that the __main_block_IMPL_0 structure contains the __block_impl structure:

struct __block_impl {
	void *isa;
	int Flags;
	int Reserved;
	void *FuncPtr;
};
Copy the code
  • Isa pointer to a class
  • Flags Flags variables that are used to implement internal operations on blocks
  • Reserved Variable
  • FuncPtr function pointer, which the Block calls

    Also contains a__main_block_desc_0The structure of the body
static struct __main_block_desc_0 {
	size_t reserved;
	size_t Block_size;
} 
Copy the code
  • Reserved Variable
  • Block_size Block size

So when we declare a Block, we actually generate a structure and pass the function pointer along with the __main_block_desc_0 structure. In the Block source code, we can see the Block structure

struct Block_descriptor_1 { uintptr_t reserved; uintptr_t size; }; 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 };Copy the code

The C++ code we compiled is exactly the same except for some member names

Copy of blocks in ARC

In the previous MRC era, Block assignment needed to use [Block copy], this is because the generated Block(^{}) is on the stack, the release of blocks on the stack is not our control, so we need to use copy to copy blocks to the heap, so that we can manually manage memory; After ARC, the compiler optimized us by declaring a Block that uses a non-static variable and assigning the Block to a strong pointer automatically copied to the heap. You can find the following method in the Runtime. c file in the Block source code

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 = malloc(aBlock->descriptor->size);
        if(! result)return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // 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; }}Copy the code

Else: create a new Block structure with the size of the Block, assign the contents of the Block on the stack to the Block on the heap, change the flags, ISA value, so that the Block is copied to the heap Void (*invoke)(void *,…) ; Pointer, the rest of the values are copied from the stack to the heap

The _Block_call_copy_helper method copies variables held in blocks, not tables, as described below

Block captures variables

Blocks capture the underlying data types

We all know that this is an error, so why is it an error?

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

Because changing the value of I directly in a Block will give you an error, I’m going to change the code in the Block to NSlog

As you can see, there are some differences between the compiled code and the last one

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int i; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

There is an int I member in the __main_block_IMPL_0 structure, and also in the main method

int main(int argc, const char * argv[]) {
	int i = 10;
	id block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, i));
	return 0;
}
Copy the code

We’re passing I to the Block, and we’re passing it as a value type, so we’re going to get an error when we modify it inside the Block

Block captures the OC object type

So what does it look like to use an object type inside a Block? Edit as in Main

int main(int argc, const char * argv[]) {
	int i = 10;
	id objc = [NSObject new];
	id block = ^{ NSLog(@"% @",objc); };return 0;
}
Copy the code

The compiled

__main_block_desc_0

static struct __main_block_desc_0 {
	size_t reserved;
	size_t Block_size;
	void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
	void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
Copy the code

And as you can see from the initialization, these two Pointers point to the method

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
	_Block_object_assign( (void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{
_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
Copy the code

We have already said that ARC will copy blocks on the stack to the heap, but there is a method we haven’t mentioned yet

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if(! desc)return;

    (*desc->copy)(result, aBlock); // do fixup
}
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}
Copy the code

This method gets the Block_descriptor_2 structure from the Block according to the offset

struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};
Copy the code

Call (* desc – > copy) (result, aBlock); The __main_block_copy_0 method, which calls the _Block_object_assign party, is passed as a Block on the heap and a Block on the stack method

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)) {
      caseBLOCK_FIELD_IS_OBJECT: /******* id object = ... ; [^{ object; } copy]; ********/ _Block_retain_object(object); *dest = object;break;

      caseBLOCK_FIELD_IS_BLOCK: /******* void (^object)(void) = ... ; [^{ object; } 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 inthe __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

In __main_block_copy_0, _Block_object_assign((void*)& DST ->objc, (void*) SRC ->objc, 3/*BLOCK_FIELD_IS_OBJECT*/); The enumeration value passed in is BLOCK_FIELD_IS_OBJECT, so _Block_retain_object(object) is called; methods

void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
    _Block_retain_object = callbacks->retain;
    _Block_release_object = callbacks->release;
    _Block_destructInstance = callbacks->destructInstance;
}
Copy the code

_Block_retain_object is a retain pointer, keep looking, as you can see in the libdispatch source code

void
_os_object_init(void)
{
	_objc_init();
	Block_callbacks_RR callbacks = {
		sizeof(Block_callbacks_RR),
		(void (*)(const void *))&objc_retain,
		(void (*)(const void *))&objc_release,
		(void (*)(const void *))&_os_objc_destructInstance
	};
	_Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
	const char *v = getenv("OBJC_DEBUG_MISSING_POOLS"); _os_object_debug_missing_pools = v && ! strcmp(v,"YES");
#endif
}
void*
os_retain(void *obj)
{
	return objc_retain(obj);
}
Copy the code

Ultimately, the objC_retain method in objC4’s source code is called, so when a Block captures an OC object, it simply retains that object

The _OS_OBJect_init method is called before _objc_init

When a block is destroyed, _Block_object_dispose will be called to dispose the block, and release will be executed accordingly

Block captures the underlying data type that __block modifies

We all know that if we want to change the value of a local variable in a Block, we need to use __block modifier, so why can change the value with __block modifier? Let’s write the following code

int main(int argc, const char * argv[]) {
	__block	int i = 10;
	id 	block = ^{
		i = 100;
		NSLog(@"%d",i);
	};
	return 0;
}
Copy the code

Compiled into c + +

__Block_byref_i_0

struct __Block_byref_i_0 {
	void *__isa;
	__Block_byref_i_0 *__forwarding;
	int __flags;
	int __size;
	int i;
};
Copy the code

The int I we modify with __block is a member of it, so modifying a variable with __block is automatically compiled into a structure by the compiler. Since the member becomes a structure, there is no problem modifying a member of the structure. The __Block_byref_i_0 structure has a __Block_byref_i_0 *__forwarding; Pointer, set in main to point to itself. __main_block_DESc_0 also contains copy and dispose methods, but the enumeration value passed in is BLOCK_FIELD_IS_BYREF, which corresponds to the _Block_object_assign method

*dest = _Block_byref_copy(object);
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;
    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: onefor 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;

        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;
            }

            (*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

If ((SRC ->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) branch, it can be seen from this code that when a Block is copied from the stack to the heap, a Block_byref structure will be created on the heap, and the forwarding on the stack and the newly created forwarding on the heap both point to Block_byref on the heap, so __b is changed inside the Block The value of a variable modified by lock is also modified externally. The _Block_object_dispose method calls the _Block_byref_release method to dispose of Block_byref from the heap and (*src2->byref_keep)(copy, SRC); So let’s look at that

Block captures the __block-modified OC object type

Without further ado, write a piece of code before converting to C++

int main(int argc, const char * argv[]) {
	__block	NSObject *object = [NSObject new];
	id 	block = ^{
		object = [NSObject new];
		NSLog(@"% @",object);
	};
	return 0;
}
Copy the code

Since the code was too long to screenshot, I pasted the parts that were different from the previous ones

struct __Block_byref_object_0 { void *__isa; //8 __Block_byref_object_0 *__forwarding; //8 int __flags; //4 int __size; //4 void (*__Block_byref_id_object_copy)(void*, void*); //8 void (*__Block_byref_id_object_dispose)(void*); //8 NSObject *object; }; int main(int argc, const char * argv[]) { __attribute__((__blocks__(byref))) __Block_byref_object_0 object = { (void*)0, (__Block_byref_object_0 *)&object, 33554432, sizeof(__Block_byref_object_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))}; id block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_object_0 *)&object, 570425344));return 0;
}
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
	_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
	_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
Copy the code

The rest of the code is basically the same as the __block modified base datatype, except that the __Block_byref_id_object_copy and __Block_byref_id_object_ function Pointers are added to the __Block_byref_object_0 structure The dispose. _Block_byref_copy (*src2->byref_keep)(copy, SRC); Struct Block_byref_2 *src2 = (struct Block_byref_2 *)(SRC +1); The Block_byref_2 structure is obtained

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
    void (*byref_destroy)(struct Block_byref *);
};
Copy the code

Byref_keep is the __Block_byref_id_object_copy_131 method we passed in. This method calls the familiar _Block_object_assign method, but the difference is that the encoded parameters look weird (char*) DST + 40 first, we can see that DST is the address of the Block_byref structure pointer on the stack, and our __Block_byref_object_0 fixed member takes up exactly 40 space, so (char*) DST + 40 is get NSObject *object pointer; *(void * *) ((char*) SRC + 40) takes the NSObject *object pointer on the stack, but there is an * in front of it, so it takes the value to which the pointer points; So the first two are clear. So what is 131? View the enumeration values found BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT (3 | 128) value is just 131, then we go to _Block_object_assign method to check

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        *dest = object;
        break;
Copy the code

When the value passed in is 131, it assigns the value of NSObject * Object on the stack to the value of NSObject * Object on the heap

Of course, the _Block_object_assign method is not simple only these four cases, the following other cases are not to analyze, interested friends can go to the source code analysis

A circular reference

After watching the above friends, surely I don’t need to tell all know why there are circular references, when an object holds a block, the block also holds the object will cause a circular reference, so how to remove a circular reference very clearly as well as long as we use __weak can solve, but there are still more magical solutions, to use __block

	__block Person * block_self = self;
		self.block = ^{
			NSLog(@"% @",block_self);
			block_self = nil;
		};
		self.block();
Copy the code

This block has to be called, otherwise block_self won’t be set to nil, and it still hasn’t broken the circular reference

The last mention

Why would you use self in the navigation Block?

[self.tableview mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self.view);  make.bottom.mas_equalTo(-30.f); }];Copy the code

Click on the source code to see

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints =  NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; block(constraintMaker);return [constraintMaker install];
}
Copy the code

The self does not hold or indirect holding block, so will not result in a circular reference, the same [UIView animateWithDuration: animations:] can be used inside the block is the same reason

confusion

I had finished reading Block and thought I had some understanding of it, but when I saw the questions sun Yuan posted on weibo, I found that I was too naive

struct YTY_Block_descriptor_1 { uintptr_t reserved; uintptr_t size; }; struct YTY_Block_layout { void *isa; volatile int32_t flags; // contains ref count int32_t reserved; void (*invoke)(void *, ...) ; struct Block_descriptor_1 *descriptor; }; void hookInvokePrintHelloWorld(void *obj, ...) {printf("Hello,world\n");
}
void HookBlockToPrintHelloWorld(id block){
    struct YTY_Block_layout *yty_block = (__bridge struct YTY_Block_layout *)block;
    yty_block->invoke = &hookInvokePrintHelloWorld;
	((__bridge struct YTY_Block_layout *)block)->invoke = &hookInvokePrintHelloWorld;
}
Copy the code

I tried using method Swizzling to replace the – (void)invoke method in NSBlock. The method can be replaced but the Block will not be called. I don’t know if there’s a god who can help answer these questions


Article reference: broken bow Block thematic Block source code