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:
block
The code block is finally compiled to __main_block_IMPL_0The structure of the body
The 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.
throughclang
Let’s compile it to see how it’s used at the bottom__block
What’s the difference between decorating and not using __blockThis is the underlying structure of objects that are not decorated with __block,block
Is aThe structure of the body
The 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__block
Decorated object
- use
__block
The modified object LGTeacher has been compiled__block_byref_lgter_0
The structure, in the block constructor, takes the arguments_block_byref_lgter_0
Structure pointer__forwarding
Assign to the block attribute _block_byref_lgter_0, and__forwarding
anotherPoint 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 a
Passing of Pointers
, eventually pointing to the _block_byref_lgter_0 structure._forwarding can refer to itself
Can also point toA copy of myself
That 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_copy
Methods and___Block_byref_id_object_dispose
Method, as analyzed below.
conclusion
__block
The object of the modifier isPointer passed
The __block variable can be accessed correctly whether configured on the heap or stack.No use __block
The object of the modifier isValue passed
The 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_retainBlock
Will call againBlock_copy
Isn’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 actually
Block_layout
Structure. - If is aBlock
Global block
.copy
It’s a direct return - If is aBlock
Stack block
, then it will be in the heap areamalloc
Open up memory and block the stack areacopy
Into the newThe heap area
.Block_call_copy_helper
Get copy. Copy is called_Block_object_assign
Method 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
- if
External variables are objects
._Block_retain_object
Make an external objectThe reference count increases by 1
, so use external variables inside the block, such asself
You need to use a weak reference, otherwise the block internally will strongly reference self so thatThe reference count increases by 1
.Stack block
Referring to external variables also makesObject's reference count increases by 1
If theStack block
Copy it into theThe heap
The area,The reference count for external objects is increased by one
. - If the external variable is
Block type
, _Block_copy - If the external variable is
__block
Modified, __block modified objects are compiled into oneblock_byref
The 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:weakBlock
Do not make a special designation is oneHeap block
.Temporary scope
Within thestrongBlock
Is aStack block
The 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 pointweakBlock
andstrongBlock
Point to theThe same stack memory space
But after the breakpoint passes 82 lines, strongBlock nowStack memory is freed
StrongBlock 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 pointer
This point is not clear at present, hope to know the guidance. If you don’t want to print null, you can do thisWeakBlock = strongBlock copy
Copy stack blocks to the heap.