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_0
The 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