Tag: block Type of block Circular reference solution block underlying analysis
This chapter mainly introduces
1. The type of block
2. Circular reference solution
3. Underlying analysis of block
1 block type
There are three main types of blocks
1.1 __NSGlobalBlock__
: global block, stored in a global area
void(^block)(void) = ^ {NSLog(@"ypy");
};
NSLog(@ "% @", block);
Copy the code
In this case, the block has no parameters and no return value, and belongs to the global block
1.2 __NSMallocBlock__
: heap block, because block is both a function and an object
int a = 10;
void(^block)(void) = ^ {NSLog(@"ypy - %d", a);
};
NSLog(@ "% @", block);
Copy the code
The block accesses an external variable, the underlying copy of A, so it’s a heap block
int a = 10;
void(^block)(void) = ^ {NSLog(@"ypy - %d", a);
};
NSLog(@ "% @"The ^ {NSLog(@"ypy - %d", a);
});
Copy the code
Where the local variable A is a stack block before processing (before no copy), and a heap block after processing (after copy). Currently, there are fewer and fewer stack blocks
1.3 __NSStackBlock__
Block: the stack area
int a = 10;
void(^ __weak block)(void) = ^ {NSLog(@"ypy - %d", a);
};
NSLog(@ "% @", block);
Copy the code
Strong holding can be disabled by __weak, and a block is still a stack block
conclusion
- Blocks are stored directly in
The global area
- if
Block accesses external variables
And copy blocks- If at this point
Blocks are strong references
The block,Stored in the heap area
, that is, the heap block - If at this point
A block becomes a weak reference by __weak
The block,Stored in the stack area
, that is, the stack block
- If at this point
2. Block circular reference
Normal release
When A calls the dealloc method and sends A release signal to B, B receives the release signal. If B’s retainCount (reference count) is 0, B’s dealloc method will be calledA circular reference
: A and B hold each other, so A cannot call dealloc method to send release signal to B, and B cannot receive release signal either. So neither A nor B can be released
Do the following two pieces of code have circular references?
/ / code
NSString *name = @"ypy";
self.block = ^(void) {NSLog(@ "% @".self.name);
};
self.block();
/ / code 2
UIView animateWithDuration:1 animations:^{
NSLog(@ "% @".self.name);
};
Copy the code
In code one, a circular reference occurs, because the external variable name is used inside the block, causing the block to hold self, which originally held the block, and thus self and the block to hold each other. There is no circular reference in code 2. Although external variables are also used, self does not hold the animation block. Only the animation holds self, which does not constitute mutual holding
There are several common ways to resolve circular references.
2.1 Solving circular References
- [Method 1]
weak-strong-dance
- 【 Method 2 】
__block
Modify objects (note that this is required inside blocksempty
Object, andBlock must call
) - 【 Mode 3 】 Pass
Recursion object self
As a block,parameter
For internal use within blocks - [Mode 4] Use
NSProxy
2.1.1 weak-stong-dance
- If there is no nested block inside the block, use it directly
__weak
You can just modify self
typedef void(^YPYBlock)(void);
@property(nonatomic.copy) YPYBlock ypyBlock;
__weak typeof(self) weakSelf = self;
self.ypyBlock = ^(void) {NSLog(@ "% @",weakSelf.name);
}
Copy the code
At this time, weakSelf and self point to the same piece of memory space, and the use of __weak will not lead to the change of self’s reference count, which can be verified by printing the pointer addresses of weakSelf and self and self’s reference count.
- If a block contains nested blocks, use both
__weak
和__strong
__weak typeof(self) weakSelf = self;
self.ypyBlock = ^(void){
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@ "% @",strongSelf.name);
});
};
self.yppBlock();
Copy the code
StrongSelf is a temporary variable in the scope of ypyBlock, which releases strongSelf when the inner block is finished executing
This is breaking self’s strong reference to a block, depending on the mediator pattern, and is auto-set to nil, auto-release
2.1.2 __block decorates a variable
This approach, which also relies on the mediator pattern, is manual release and modifies objects with __block, mainly because __block modifies objects that can be changed
__block ViewController *vc = self;
self.ypyBlock = ^(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.ypyBlock();
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
2.1.3 Object self as the argument
The object self is supplied as an argument to the block for internal use, with no reference counting problems
typedef void(^YPYBlock)(ViewController *);
@property(nonatomic.copy) YPYBlock ypyBlock;
self.ypyBlock = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@ "% @",vc.name);
});
};
self.ypyBlock(self);
Copy the code
2.1.4 NSProxy Virtual Class
OC
Is the onlySingle inheritance
Language, but it isRuntime-based mechanisms
So we can passNSProxy
To implement thePseudo multiple inheritance
To fill the gap of multiple inheritanceNSProxy
和NSObject
It’s a class of the same class, or oneVirtual class
, just implements the protocol of NSObjectNSProxy
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`
Usage scenarios
There are two scenarios for using NSProxy
- implementation
Multiple inheritance
function - To solve the
NSTimer&CADisplayLink
When creatingStrong reference to self
Question, referenceYYKit
theYYWeakProxy
.
Circular reference solution principle
This is mainly done by replacing self with a custom NSProxy class object and using methods to implement message forwarding
The following is an implementation of the NSProxy subclass and the scenario in which it is used
- Custom one
NSProxy
A subclass ofYPYProxy
@interface YPYProxy : NSProxy
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(id)objc;
@end
@interface YPYProxy(a)
@property(nonatomic.weak.readonly) NSObject *objc;
@end
@implementation YPYProxy
- (id)transformObjc:(NSObject *)objc{
_objc = objc;
return self;
}
+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] transformObjc:objc];
}
//2. The method implementation is called once the method signature is in place
- (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];
}
@end
Copy the code
- The custom
Cat
Classes andDog
class
/ / * * * * * * * * the Cat class * * * * * * * *
@interface Cat : NSObject
@end
@implementation Cat
- (void)eat{
NSLog(@" Cats eat fish");
}
@end
/ / Dog class * * * * * * * * * * * * * * * *
@interface Dog : NSObject
@end
@implementation Dog
- (void)shut{
NSLog(@ "dog");
}
@end
Copy the code
- Use YPYProxy
Multiple inheritance
function
- (void)yyy_proxyTest{
Dog *dog = [[Dog alloc] init];
Cat *cat = [[Cat alloc] init];
YPYProxy *proxy = [YPYProxy alloc];
[proxy transformObjc:cat];
[proxy performSelector:@selector(eat)];
[proxy transformObjc:dog];
[proxy performSelector:@selector(shut)];
}
Copy the code
- Use YPYProxy
A strong reference to self in the timer
The problem
self.timer = [NSTimer timerWithTimeInterval:1 target:[YPYProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code
2.2 summarize
There are basically two solutions to looping applications, self -> block -> self for example
- break
The self to block
You can use the block attribute modifier weak, but this will cause the block to be freed as soon as it is created, so breaking strong references from here is not an option - break
Block of the self
Strong references to self and block scopescommunication
, communication withProxy, pass value, notification, pass parameter
And other ways to solve the cycle, the common solutions are as follows:weak-strong-dance
__block
(Object inside block is empty and block is called)- The object
self
As a block,parameter
- through
NSProxy
Subclass ofself
3. Block underlying analysis
Mainly through clang, breakpoint debugging and other ways to analyze the Block bottom
3.1 nature
- define
block.c
file
#include "stdio.h"
int main(){
void(^block)(void) = ^{
printf("ypy");
};
return 0;
}
Copy the code
- through
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
, compile block.c toblock.cpp
, where the block is compiled at the bottom to the following form
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;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("ypy");
}
/ / * * * * * * 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
Block = __main_block_IMPL_0, which is a function
- To view
__main_block_impl_0
, it is aThe structure of the body
Block is a block__main_block_impl_0
Type of object, that’s why, rightblock
To be able to% @
Reasons for printing
//** 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;
};
Copy the code
Summary: The essence of a block is an object, a function, or a structure. Since a block function has no name, it is also called an anonymous function
The relationship between block source code compiled by Clang is shown below, using a variable decorated with __block as an example
3.2 Why does a block need to call *
In the underlying block’s __main_block_IMPL_0 structure, created by its constructor of the same name, the first passed block’s internal implementation code block, __main_block_func_0, is expressed as FP, and then assigned to the FuncPtr property of the IMPL, The call is then made in main, which is why the block needs to be called. If not called, the code block implemented inside the block will not execute, which can be summarized as follows
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
3.3 How does a block get an external variable
- Define a variable and call it in a block
int main(){
int a = 11;
void(^block)(void) = ^{
printf("ypy - %d", a);
};
block();
return 0;
}
Copy the code
- The underlying compilation looks like this
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) {
int a = __cself->a; // bound by copy: a = 10; a is not the same as __cself's a
printf("ypy - %d", a);
}
int main(){
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_func_0 is a copy of the value, and if you do a++ inside a block implementation, it is problematic and will cause confusion in the compiler code that a is read-only
Summary: When a block captures an external variable, it automatically generates the same internal property to hold
3.4 The principle of __block
- right
a
Add a__block
, and then on a in the block++
operation
int main(){
__block int a = 11;
void(^block)(void) = ^{
a++;
printf("ypy - %d", a);
};
block();
return 0;
}
Copy the code
- The underlying compilation is as follows
- In the main
a
Is encapsulated by external variablesobject
__main_block_impl_0
,Object a
The address of the&a
To the constructor- in
__main_block_func_0
The internal processing of A isPointer to the copy
, the created object A and the passed object APointing to the same memory space
- In the main
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("ypy - %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(){
//__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
Will be generated__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
The two copies are compared as follows
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 variablesa
isCopy the value
Pointer to the copy
– Shallow copy. The generated objects point to the same memory space__block
Modified variablea
It’s a pointer copy
3.5 Block Underlying type
Analyze the location of block source code
-
Analyze run-time blocks by breaking points at them
-
Add objc_retainBlock symbol breakpoint and find _Block_copy
-
Add _Block_copy symbol breakpoint, run to break, in libsystem_blocks. Dylib source code
You can download libclosure-79 from Apple’s open source website, and check the _Block_copy implementation to see that the underlying block type is Block_layout
Block true type
View the definition of the Block_layout type, which is a structure
// 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 –
// Comment: flags flags
// 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. It can be understood that the reserved position is used to store information about variables inside the blockinvoke
: is a function pointer to the block’s executing codedescriptor
: Additional information about a block, such as the number of variables to keep, the size of the block, and Pointers to auxiliary functions to copy or dispose. Have three kinds ofBlock_descriptor_1
Is choiceBlock_descriptor_2
和Block_descriptor_3
It’s all optional
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;// Keep the information
uintptr_t size;/ / block 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
The above descriptors can be seen in their constructors, where Block_descriptor_2 and Block_descriptor_3 are obtained by translation of the Block_descriptor_1 address
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
3.6 Memory Changes
-
Run at the break point, go to objc_retainBlock, and read register X0 at the block breakpoint, where the block is a global block, that is, __NSGlobalBlock__ class
-
Add the external variable A and print it inside the block
int a = 10;
void (^block1)(void) = ^ {NSLog(@"ypy - %d", a);
};
block1();
Copy the code
X0 — stack block — __NSStackBlock__ is read at the block breakpoint
- Execute to symbol breakpoint
objc_retainBlock
, or stack bock
-
Add _Block_copy symbol breakpoint and break it, add a breakpoint directly to the last ret, read x0, find after _Block_copy, changed to heap block, that is __NSMallocBlock__, mainly because the block address changed to heap block
Call the situation
-
This can also be verified using breakpoints
-
Register read x0 Reads x0 as a heap block
-
Register read X9 Reads x9
-
Register read x11, which points to a memory space where _block_INVOK is stored
-
-
Press control + step into to enter _block_invoke, and you can get the source code of the block structure to realize the aforementioned Block_layout by memory translation. It can be seen from the source code that there is an attribute invoke, that is, the executor of the block, The invocation is executed by fetching 16 bytes from isa’s first address pan to invoke
3.7 the signature
-
Go ahead, read the X0 register, look at the memory layout, and get the property descriptor Block_layout by translation 3*8, mainly to see if there is Block_descriptor_2 and Block_descriptor_3, Three of them have block’s signature
-
Register read x0, read register x0
-
Po 0x0000000281f90000 prints the block
-
X /8gx 0x0000000281F90000, that is, block memory information is printed
-
X /8gx 0x00000001020F0068, view the memory information of descriptor, where the third 0x00000001020EF640 indicates the signature
-
-
Check whether Block_descriptor_2 is present, that is, whether BLOCK_HAS_COPY_DISPOSE (copy helper function) of flags has a value
-
P /x 1<<25, that is, 1 moved 25 bits left and was 0x2000000 in hex
-
P 0x02000000&0x00000000C1000002, that is, BLOCK_HAS_COPY_DISPOSE & flags, equals 0, indicating no Block_descriptor_2
-
-
Check whether Block_descriptor_3 is present
-
P /x 1<<30, that is, 1 moves 30 bits to the left
-
P 0x40000000&0x00000000C1000002, that is, BLOCK_HAS_SIGNATURE & flags, indicates that Block_descriptor_3 is present
-
P (char *) 0x00000001020EF640 — Get the attribute signature in Block_descriptor_3
-
Po [NSMethodSignature signatureWithObjCTypes:”v8@?0″], that is, print signatures
-
The signature part is described as follows
// 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
3.8 Block Copy analysis for three times
3.8.1 _Block_copy source code analysis
- Enter the
_Block_copy
Source code, copy block from stack to heap- If you need to release it, release it if you need to
- If it is
globalBlock
Copy is not required, return directly - Stack block or heap block; stack block or heap block
The stack area block
.- through
malloc
Allocates memory space for receiving blocks - through
memmove
Copies blocks to the newly allocated memory - Set the block object type to heap block, i.e
result->isa = _NSConcreteMallocBlock
- through
// 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
3.8.2 _Block_object_assign analysis
To analyze the three-tier copy of a block, you first need to know what kinds of external variables are available. The most commonly used are BLOCK_FIELD_IS_OBJECT and BLOCK_FIELD_IS_BYREF
// 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
The _Block_object_assign method is called when external variables are copied in the underlying compiled code
- Enter the
_Block_object_assign
The source code- If it is a common object, hand it to
System ARC processing
And,Copy object pointer
, i.e.,Reference count +1
, so external variables cannot be released - If it is
Block type
Is passed_Block_copy
Action to remove a block fromThe stack is copied to the heap
- If it is
__block modification
Is called_Block_byref_copy
Function for memory copy and general processing
- If it is a common object, hand it to
//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
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)) {
case BLOCK_FIELD_IS_OBJECT:
/******* id object = ... ; [^{ object; } copy]; * * * * * * * * /
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_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 in the __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
- Enter the
_Block_byref_copy
The source code- Force to an object passed in
Block_byref
Structure type object, save a copy - External variables are not copied to the heap, so memory needs to be allocated and copied
- If it has already been copied, it is processed and returned
- The forwarding Pointers to copy and SRC point to the same piece of memory, which is why __block objects can be modified
- Force to an object passed in
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
Code debugging
- To define a
__block
Modification of theNSString
object
__block NSString *ypy_name = [NSString stringWithFormat:@"ypy"];
void (^block1)(void) = ^ {// block_copy
lg_name = @"ypy";
NSLog(@"ypy - %@",ypy_name);
/ / block of memory
};
block1();
Copy the code
- Xcrun compiles as follows,
- The compiled
ypy_name
More than ordinary variables__Block_byref_id_object_copy_131
和__Block_byref_id_object_dispose_131
__Block_byref_ypy_name_0
I have a lot of structures__Block_byref_id_object_copy
and__Block_byref_id_object_dispose
- The compiled
//******** Compiled ypy_name********
__Block_byref_ypy_name_0 ypy_name =
{(void*)0,
(__Block_byref_ypy_name_0 *)&ypy_name,
33554432.sizeof(__Block_byref_ypy_name_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSString * _Nonnull (*)(id, SEL, NSString* _Nonnull, ...) ) (void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_main_9f330d_mi_0)};
//********__Block_byref_ypy_name_0 structure ********
struct __Block_byref_ypy_name_0 {
void *__isa;
__Block_byref_ypy_name_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*); / / 5 * 8 = 40
NSString *ypy_name;
};
//********__Block_byref_id_object_copy_131********
// Copy the block itself (_Block_copy) -- copy the __block bref structure (_Block_object_assign) -- copy the external variables (stored in BREF) from _Block_object_assign to memory
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// select ypy_name(ypy_name is assigned when bref is initialized)
_Block_object_assign((char*)dst + 40, * (void((* *)char*)src + 40), 131);
}
//********__Block_byref_id_object_dispose_131********
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void((* *)char*)src + 40), 131);
}
Copy the code
- through
libclosure-74
Breakpoint debugging of compilable source code, the order of execution of key methods is:_Block_copy -> _Block_byref_copy -> _Block_object_assign
, exactly corresponding to the above three levels of copy
How does block get ypy_name?
- 1, through the
_Block_copy
Methods,block
Make a copy toThe heap area
- 2, through the
_Block_object_assign
Method normal copy because__block
The external variables of the modifier are at the bottomBlock_byref structure
- 3. Find that external variables still exist
I have an object, and I take the corresponding object ypy_name from bref
Copy toBlock the space
, can be used (the same space can be used, different cannot be used). At last,Translation memory
Ypy_name is the same memory space as the external ypy_name_Block_object_assign
In the methoddest = object;
See)
3.8.3 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
3.8.4 _Block_object_dispose analysis
As with retain and release, _Block_object_object is essentially retain, so there is also a corresponding release, _Block_object_dispose method. Its source code is as follows. Perform different release operations
// 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
Therefore, to sum up, the three-layer copy flow of Block is shown in the figure below
🌹 feel harvest, can come a wave, like + collection + attention, comment + forward, so as not to find me next time 😁🌹