This chapter begins with the analysis of “familiar” blocks, introducing the types of blocks, several solutions to Block circular reference, and how blocks operate at the bottom.
💪 Let ‘s Go
0x00 – block
The type of
By printing, you can see that there are several types of blocks
__NSGlobalBlock__
A normal block, which uses no external data, is a global block
__NSMallocBlock__
The use of external variables will change the location of the block, called the heap block, because the block is both a function and an object, the underlying copy a
__NSStackBlock__
Where the local variable A is stack block before processing (before copying) and heap block after processing (after copying), the current stack block is less and less, ⚠️⚠️ the current Xcode is 11.6 iOS 13.6, if it is Xcode12 iOS14 after, The printout is NSMallocBlock
It can also be held with __weak, and blocks are also in the stack
Xcode12 iOS14 environment print
conclusion
block
Do not access external variables, directly inThe global area
- If you were using external data, it would be
block
Carry out correspondingcopy
.- if
block
Is a strong reference, at this timeblock
The heap area - If you use
__weak
modifiedblock
At this time,block
In the stack
- if
0x01 – Loop ♻️ reference
The most common problem with using blocks in development is the problem of circular references
- Normal releaseObject A release is called when object A holds object B
dealloc
Method, which is then sent to object Brelease
Signal, object B receives the signal, if the reference count of object B is 0, object B’s owndealloc
Method, release yourself.
- A circular reference: Object A and object B hold each other. Therefore, object A cannot release itself and cannot send itself to object B
release
The signal, object B can’t get the signal, can’t release itself, keeps taking up this memory. Also calledMemory leaks
0x02 – Loop ♻️ reference resolution
When you have a problem with circular references, look for a solution:
First look at the following two pieces of code, will the sound loop reference?
/ / code
NSString *name = @"cc";
self.block = ^(void) {NSLog(@ "% @".self.name);
};
self.block();
/ / code 2
UIView animateWithDuration:1 animations:^{
NSLog(@ "% @".self.name);
};
Copy the code
- It’s pretty obvious
Code 1
A circular reference problem occurred because inblock
Memory usedself
, resulting inblock
holdself
And theself
Also heldblock
So it leads to mutual holding that cannot be released. Code section 2
The circular reference problem does not occur in theself
They don’t own itanimation block
, does not constitute a mutual holding relationship.
Solution:
weak-strong-dance
typedef void(^testBlock)(void);
@property(nonatomic.copy) testBlock tBlock;
__weak typeof(self) weakSelf = self;
self.tBlock = ^(void) {NSLog(@ "% @",weakSelf.name);
};
Copy the code
If there is no nested block inside the block, the __weak modifier is directly used. At this time, weakSelf points to that area of self, but it does not change the reference count of that memory. WeakSelf just keeps the relation of a weak reference to that memory area.
The previous one only uses a single _weak to solve the problem, but in the actual project, there is still a drop problem, which is easy to be released in advance in some scenarios, such as:
// VC2:
__weak typeof(self) weakSelf = self;
self.tBlock = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
Copy the code
In the code above, there is a piece of code in Vc2 that executes output after 2 seconds in the block, jumping from the previous VC1 to vC2 and returning to the previous page before the delayed output is executed.
__strong typeof(weakSelf) strongSelf = weakSelf;
StrongSelf is a temporary variable inside the block, the internal block executes, the scope is released,
This is breaking self’s strong reference to a block, depending on the mediator pattern, and is auto-set to nil, auto-release
__block
Modified variable mode
This approach also relies on the mediator pattern, but is changed from automatic to manual
self.name = @"cccc"; __block ViewController *vc = self; self.tBlock = ^(void){ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",vc.name); vc = nil; // manually release}); }; self.tBlock();Copy the code
Notice that the block here has to be called, if it’s not called, vc is not null, it’s still a circular reference, self and block are not released
- Object itself as a parameter
typedef void(^testBlock)(ViewController *);
@property(nonatomic.copy) testBlock tBlock;
self.name = @"cccc";
self.tBlock = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@ "% @",vc.name);
});
};
self.tBlock(self);
Copy the code
The object self is supplied as an argument to the block for internal use, with no reference counting problems
NSProxy
Virtual class
OC
Only single inheritance, based onRuntime mechanism
, can be accessed throughNSProxy
implementationPseudo multiple inheritance
To fill the gap of multiple inheritanceNSProxy
Is andNSObject
The same level of class, it also implementsNSObject
The agreement.NSProxy
It’s actually aMessage redirection encapsulates an abstract class
, like a proxy, middleware can implement message forwarding to another instance by inheriting it and overriding the following two methods
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
Copy the code
Usage scenarios
- Realize multiple inheritance function
- To solve the
NSTimer&CADisplayLink
When creatingStrong reference to self
Question, referenceYYKit
theYYWeakProxy
.
Show scenes
- Custom one
NSProxy
A subclass of
@interface MyProxy: NSProxy // Two initialization methods, save an external object - (id)transformObjc:(NSObject *)objc; + (instancetype)proxyWithObjc:(id)objc; @end @interface MyProxy () @property(nonatomic, weak, readonly) NSObject *objc; @end @implementation MyProxy - (id)transformObjc:(NSObject *)objc{ _objc = objc; return self; } + (instancetype)proxyWithObjc:(id)objc{ return [[self alloc] transformObjc:objc]; } #pragma mark - //2. Invocation - (void)forwardInvocation:(NSInvocation *)invocation {SEL SEL = [invocation) selector]; if ([self.objc respondsToSelector:sel]) { [invocation invokeWithTarget:self.objc]; }} / / 1, query the signature of the method - (NSMethodSignature *) methodSignatureForSelector: (SEL) SEL {NSMethodSignature * signature; if (self.objc) { signature = [self.objc methodSignatureForSelector:sel]; } else { signature = [super methodSignatureForSelector:sel]; } return signature; } - (BOOL)respondsToSelector:(SEL)aSelector { return [self.objc respondsToSelector:aSelector]; } @endCopy the code
- through
NSProxy
Subclassing implementationMultiple inheritance
function
- through
MyProxy
To solveA strong reference to self in the timer
The problem
self.timer = [NSTimer timerWithTimeInterval:1 target:[MyProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code
conclusion
There are essentially two types of circular reference breaks. take self –> block —-> self for example
- break
self---->block
A strong reference to theweak
Embellish properties, but in that case,block
It was released before the game was created, so it doesn’t work here - break
block------>self
Strong referential relationships at this level,weak-strong
__block modifier, internally empty, must call block
- The object
self
As a parameter - through
NSProxy
In the way ofself
0x03 –Block
Analysis of the underlying
Analyze the underlying logical structure through Clang, breakpoint debugging and assembly
Write a block file first, then use Clang rewrite.
#include "stdio.h"
int main(a){
__block int a = 10;
char c = 'b';
void(^block)(void) = ^ {printf("test block %d %c", a, c);
a+=1;
};
block(a);return 0;
}
Copy the code
Use xcrun-sdk iphonesimulator clang-arch x86_64-rewrite-objc block.c to generate a block. CPP file. Block is compiled at the bottom to the following form
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("test block");
}
int main(a){
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
/ / * * * * * * simplify the * * * * * *
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));// constructor
block->FuncPtr(block);// The block call executes
Copy the code
- First of all to see
__main_block_impl_0
, a structure type, descriptionblock
It’s also an object, and whyblock
You can use% @
Reasons for printing. And no references to external variables, itsimpl.isa = &_NSConcreteStackBlock;
, is a block in the stack (in this case, copy to the heap at run time).
//** The structure type of the block **
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; }};//** Block structure type **
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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)};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
Copy the code
Summary: The essence of a block is an object, a function, and a structure. Since a block function has no name, it is also called an anonymous function, similar to c/ C ++ function Pointers
Diagram of the underlying source code structure for Clang compilation
1. block
Why is it called?
The underlying block is compiled into a struct __main_block_IMPL_0, and when initialized,
The first argument is the block of code that the block needs to execute, namely {…….. } Function address of the curly braces. *fp, assigned to impl.FuncPtr = fp
Then call block->FuncPtr in main
Function declaration
The internal implementation of a block is declared as a function__main_block_func_0
Perform specific function implementations
: by calling a blockFuncPtr
Pointer to call block execution
2. How does block get external variables?
int main(a){
int a = 11;
void(^block)(void) = ^ {printf("tsetBlock - %d", a);
};
block(a);return 0;
}
Copy the code
The above code snippet is compiled to the bottom layer by Clang as follows
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;// The corresponding variables are automatically generated at compile time
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;// Block isa defaults to stackBlockimpl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// bound by copy: a = 10, a is not the same as the a passed in __cself
int a = __cself->a;
printf("tsetBlock - %d", a);
}
int main(a){
int a = 11;
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
block)->FuncPtr(block);
return 0;
}
Copy the code
The ain __main_block_IMPL_0 exists as a copy of the value. If a is changed internally, the compiler does not allow this because a is read-only and prompts __block modifier.
Conclusion:
When a block captures an external variable, it automatically generates the same attribute internally to store it
3 __block
The principle of
- Copy the value
- Deep copy, only copies the value, and the copy value cannot be changed, pointing to different memory space, in the case of ordinary variables
ais
Copy the valuePointer to the copy
– Shallow copy. The generated objects point to the same memory space__block
Modified variablea
It’s a pointer copy
-
As you can see from the above, the variable A is decorated with __block in the code, and is encapsulated as an object in the underlying Clang compilation
-
Then, in main, the address of the encapsulated object is passed to the constructor
-
The processing of A inside __main_block_func_0 is a pointer copy, in which the created object A points to the same memory space as the passed object A
struct __Block_byref_a_0 {//__block decorates the structure of an external variable
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {// Block structure type
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {// constructorimpl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {// Block internal implementation
__Block_byref_a_0 *a = __cself->a; // a copy of the bound by ref pointer that points to the same address space as the __cself object
// is equivalent to a++
(a->__forwarding->a)++;
printf("CJL - %d", (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); }static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); }int main(a){
//__Block_byref_a_0 is a struct, and a equals the assignment of the struct, that is, enclosing the external variable a as an object
//&a is the address of the external variable A
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0.sizeof(__Block_byref_a_0), 11};
// The third argument in __main_block_impl_0 &a is the address of the encapsulated object A
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
Copy the code
conclusion
External variables
use__block
The modifier produces one__Block_byref_a_0
The structure of the body- The structure is used
Saves the pointer and value of the original variable
- To generate a variable
The pointer address of the structure object is passed to the block
You can then operate on external variables inside the block
4 Block
The underlying true type
Let’s first explore where Block is in the underlying source code.
- Through the
Block
At the break point, openAlways Show Disassembly
See the assembly
- See there are
objc_retainBlock
Add this symbol breakpoint to continue debugging
- add
_Block_copy
Symbol breakpoint, see debugging
Clang-compiled CPP static files also have this sentence
You can download libclosure-74 from Apple’s open source website, and check the _Block_copy implementation to see that the underlying block type is Block_layout
Block
Exact type
// Block structure
struct Block_layout {
// Points to a class that indicates the block type
void *isa;/ / 8 bytes
// Used as an identifier, similar to the bit field in ISA, which represents some additional block information in bits
volatile int32_t flags; // Contains ref count 4 bytes
// Reserved information, which can be understood as the reserved position, is used to store information about internal variables of the block
int32_t reserved;/ / 4 bytes
// A function pointer to the calling address of the specific block implementation
BlockInvokeFunction invoke;
// Additional information about the block
struct Block_descriptor_1 *descriptor;
// imported variables
};
Copy the code
isa
: points to a class that indicates the block typeflags
The flags identifier represents additional information about blocks in bits, similar to the bit fields in ISA. Flags are of the following typesBLOCK_HAS_COPY_DISPOSE
和BLOCK_HAS_SIGNATURE
.BLOCK_HAS_COPY_DISPOSE
To decide whether or notBlock_descriptor_2
.BLOCK_HAS_SIGNATURE
To decide whether or notBlock_descriptor_3
- 1 a –
BLOCK_DEALLOCATING
– BLOCK_NEEDS_FREE is used for bitwise operations and is passed along with Flags to indicate that the block can be freed. - 16 – lower
BLOCK_REFCOUNT_MASK
, stores the value of the reference count; Is an optional parameter - 24th –
BLOCK_NEEDS_FREE
, low 16 is a valid flag, according to which the program decides whether to increase or decrease the value of the reference count bit; - 25 –
BLOCK_HAS_COPY_DISPOSE
, whether a copy helper function is available; - 26 –
BLOCK_IS_GC
, whether there is a block destructor; - 27th, indicating whether there is garbage collection; //OS X
- 28 –
BLOCK_IS_GLOBAL
Is a global block; - 30th –
BLOCK_HAS_SIGNATURE
, as opposed to BLOCK_USE_STRET, judgeWhether the current block has a signature
. Used for dynamic invocation at Runtime.
- 1 a –
/ / flags logo
// Values for Block_layout->flags to describe block objects
enum {
// The free flag is used for BLOCK_BYREF_NEEDS_FREE and is passed along with flags to indicate that the block can be freed
BLOCK_DEALLOCATING = (0x0001), // runtime
// Stores the value of the reference reference count, which is an optional parameter
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
// Indicates whether the lower 16 bits are valid, which the program uses to decide whether to increase or decrease the value of the reference count bit
BLOCK_NEEDS_FREE = (1 << 24), // runtime
// Whether the copy helper function is available, (a copy helper function) determines block_description_2
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
// whether a block C++ destructor is available
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
// Flag if there is garbage collection, OSX
BLOCK_IS_GC = (1 << 27), // runtime
// Whether the flag is a global block
BLOCK_IS_GLOBAL = (1 << 28), // compiler
// In contrast to BLOCK_HAS_SIGNATURE, check whether the current block has a signature that can be invoked dynamically at Runtime
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if ! BLOCK_HAS_SIGNATURE
// Whether there is a signature
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
// Use expanded, decide block_description_3
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
Copy the code
reserved
: Reserved information: reserved information, understood as some reserved locations, presumably used to store information about variables in the blockinvoke
: function pointer, pointing toBlock
The block of function code to executedescriptor
:block
Additional information, such as the number of variables to keep, the size of the block, and the pointer to the auxiliary function to copy or dispose. Have three kinds ofBlock_descriptor_1
Is choiceBlock_descriptor_2
andBlock_descriptor_3
It’s optional. Configure these two according to some conditions
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;// Keep the information
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;// Copy the function pointer
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;/ / signature
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Copy the code
In Runtime. h, get Block_descriptor_1 directly from the Block_layout structure, Both Block_descriptor_2 and Block_descriptor_3 are obtained by the memory translation size of Block_descriptor_1.
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;// Prints by default
}
#endif
//Block description: copy and dispose functions
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;/ / descriptor_1 address
desc += sizeof(struct Block_descriptor_1);// Get by memory translation
return (struct Block_descriptor_2 *)desc;
}
// Block description: signature related
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
Copy the code
Memory changes
void (^block1)(void) = ^ {NSLog(@"testBlock");
};
block1();
Copy the code
The code snippet above
A breakpoint at a block reads the value of a register, and the block is global
block
Internal uses external variables
int a = 10;
void (^block1)(void) = ^ {NSLog(@"testBlock - %d", a);
};
block1();
Copy the code
Breakpoint stops at objc_retainBlock, look at the memory value type before entering, now stack block
After entering objc_retainBlock, print the type again as stack block
Then skip to _Block_copy, place the breakpoint at the end, and let _Block_copy run to see if the value has changed
The value becomes mallocBlcok, and the address changes accordingly, mainly because the block address has been changed to a heap block
Calling process
X11 added 0x10 from X9 (16 in decimal), and then BLR X11 jumped to execute. _block_invoke was called from the read X11. Memory translation was also used to obtain the invoke in block_layout, which was preceded by 16 bytes. So we add 0x010.
// Block structure
struct Block_layout {
// Points to a class that indicates the block type
void *isa;/ / 8 bytes
// Used as an identifier, similar to the bit field in ISA, which represents some additional block information in bits
volatile int32_t flags; // Contains ref count 4 bytes
// Reserved information, which can be understood as the reserved position, is used to store information about internal variables of the block
int32_t reserved;/ / 4 bytes
// A function pointer to the calling address of the specific block implementation
BlockInvokeFunction invoke; / / 8 bytes
// Additional information about the block
struct Block_descriptor_1 *descriptor;
// imported variables
};
Copy the code
Functions that are actually executed after coming in from X11.
From the source code of the Block_layout structure mentioned above, it can be seen that there is an attribute called invoke, that is, the executor of the block, which is transferred to invoke from isa’s first address translation of 16 bytes, and then executes the call
The signature
To proceed, read register X0 to get Block_descriptor_1 by translation 24, where Descriptor_3 has the block’s signature
Continue to check whether descriptor2 is present by flags and above BLOCK_HAS_COPY_DISPOSE = (1 << 25)
Flags and BLOCK_HAS_SIGNATURE = (1 << 30) check whether descriptor3 is present,
Continue printing the signature:
The signature block
// No return value
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
//encoding = (@), type @?
type encoding(@) '@? '//@ is isObject,? IsBlock stands for isBlockObject
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
// The offset is 8 bytes
memory {offset = 0, size = 8}
Copy the code
The block signature information is similar to the method signature information. It mainly shows the block return value, parameters, and types
_Block_copy
Analysis of the
Go to _Block_copy source:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
Stack block -> heap block
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;// Forcibly convert to Block_layout to prevent external impact
if (aBlock->flags & BLOCK_NEEDS_FREE) {// Whether to release
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {// If it is a global block, return it directly
return aBlock;
}
else {// Stack block or heap block, because the heap needs to allocate memory, so only stack block
// Its a stack block. Make a copy. It's a stack block block, copy.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);// Request space and receive it
if(! result)return NULL;
// Copy aBlock to result via memmove memory copy
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;// Invoke can be invoked directly
#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;// Set block object type to heap block
returnresult; }}Copy the code
- Fault-tolerant processing,
arg
Does not exist, returnNULL
- through
flags
Determine whether to release the device directly - If it is
global block
, directly return, need copy - The other two cases are
Stack block
andHeap block
Because theHeap area block
You need to open up memory space in memory, there’s no memory opening code in front of it, so it has to be a stack block, as the comment saysIts a stack block. Make a copy
- Apply for memory space to store the stack
blcok
- through
memmove
willblock
Copy to the newly created memory - Set up the
block
theisa
for_NSConcreteMallocBlock
- Apply for memory space to store the stack
_Block_object_assign
Analysis of the
Analyze _Block_object_assign, of which BLOCK_FIELD_IS_OBJECT and BLOCK_FIELD_IS_BYREF are the most commonly used
// The type of external variables that Block captures
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
// Plain object, i.e. no other reference type
BLOCK_FIELD_IS_OBJECT = 3.// id, NSObject, __attribute__((NSObject)), block, ...
// Block as a variable
BLOCK_FIELD_IS_BLOCK = 7.// a block variable
// A variable decorated with __block
BLOCK_FIELD_IS_BYREF = 8.// the on stack structure holding the __block variable
//weak Weak reference variable
BLOCK_FIELD_IS_WEAK = 16.// declared __weak, only used in byref copy helpers
// The returned call object - handles an extra flag added to the internal object memory of block_byref, used in conjunction with flags
BLOCK_BYREF_CALLER = 128.// called from __block (byref) copy/dispose support routines.
};
Copy the code
In the underlying compiled code, _Block_object_assign is the method that is called when external variables are copied
// Clang compiles CPP files
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
In _Block_object_assign,
- If it is
BLOCK_FIELD_IS_OBJECT
Normal object, to the systemARC
Process, copy object pointer, point to the same memory space, memory reference count +1, so external reference variables cannot be freed
case BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
*dest = object;
break;
static void _Block_retain_object_default(const void *ptr __unused) { }// Do nothing
Copy the code
- If it is
BLOCK_FIELD_IS_BLOCK
, the block type is passed_Block_copy
Action to remove a block fromThe stack is copied to the heap
, analyzed above
case BLOCK_FIELD_IS_BLOCK:
*dest = _Block_copy(object);
break;
Copy the code
- If it is
__block modification
, that is,BLOCK_FIELD_IS_BYREF
To call_Block_byref_copy
Method for memory copy and general processing
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object); // Copy the variables of the current structure
break;
Copy the code
Go to the _Block_byref_copy method
static struct Block_byref* _Block_byref_copy(const void *arg) {
// Force the Block_byref structure type and save a copy
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: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// The Block_byref inside a block holds the same object as the Block_byref outside. This is why __block modifiers are modifiable
// The address pointer of copy and SCR has reached a perfect copy of the same address pointer
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 you have copy capability
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
//Block_byref_2 is a struct, __block may modify objects, which are saved by byref_keep and called when appropriate
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;
}
// This is equivalent to __Block_byref_id_object_copy
(*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
- Force an incoming object to
Block_byref
Type, save a copy - A chunk of memory is reallocated according to the size of the incoming object
- If it has already been copied, processing returns
src
andcopy
theforwarding
The pointer is pointing to the same piece of memory, so this is__block
Modify the reason why a value can be modified.
The key methods are executed in the following order: _Block_copy -> _Block_byref_copy -> _Block_object_assign, which corresponds to the preceding three-layer copy
Three-layer copy summary
So, to sum up, a three-layer copy of a block refers to the following three layers:
- [Layer 1] Through
_Block_copy
Implementing objectTheir own copy
From stack to heap - [Second layer] Pass
_Block_byref_copy
Method to copy the object toBlock_byref
Structural type - [Third layer] call
_Block_object_assign
Methods,__block
Modification of theCopy of the current variable
Note: Block copy has three levels only for __block modified objects
_Block_object_dispose
Analysis of the
Like retain and release, blocks have _Block_object_assign, as well as _Block_object_dispose
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// To help dispose of the contents When Blocks or Block_byrefs holds an object, its destruction helper routine calls this entry point to help dispose of the contents
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:// a __block modified variable of type bref
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:// Block variable
_Block_release(object) ;
break;
case BLOCK_FIELD_IS_OBJECT:// Common objects
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break; }}Copy the code
- Enter the
_Block_byref_release
Source code, is mainly the release of objects, variables destroyed
static void _Block_byref_release(const void *arg) {
// The object is forcibly converted to a Block_byref structure
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;// Cancel the pointer reference
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {// Whether there is a copy helper function
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);// Destroy the copy object
}
free(byref);/ / release}}}Copy the code
see
Blocks Programming Topics
Block test