How do blocks in IOS hold objects

Time:2021-8-15

Pay attention to the warehouse and get updates in time:iOS-Source-Code-Analyze

Follow: Draveness · Github

Block is the author’s favorite feature in Objective-C. it provides a powerful functional programming capability for Objective-C. recently, many new APIs launched by Apple have begun to support block syntax. It can be seen that it is becoming more and more important in Objective-C.

This article will not introduce in detail how blocks exist in memory. It will mainly introduce how blocks hold and release objects. The code in this article comes from Facebook open sourceUsed to detect circular referencesFramework ofFBRetainCycleDetector, this is the last article in the analysis of the framework, and it is also the most interesting part for the author.

If you want to understand the principle of fbretain cycle detector, you can readHow to solve the problem of circular reference in IOSAnd subsequent articles.

Why talk about block

Many readers may have such questions, since this article is rightFBRetainCycleDetectorWhy does the analysis article mention block? The reason is actually very simple. In IOS development, most circular references are caused by improper use of blocks. Because blocks retain the objects they hold, it is easy to cause circular references and eventually lead to memory leakage.

stayFBRetainCycleDetectorSuch a class exists inFBObjectiveCBlock, of this class- allRetainedObjectsMethod will return strong references held by all blocks, which is also the focus of this article.

- (NSSet *)allRetainedObjects {
    NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy];

    __attribute__((objc_precise_lifetime)) id anObject = self.object;

    void *blockObjectReference = (__bridge void *)anObject;
    NSArray *allRetainedReferences = FBGetBlockStrongReferences(blockObjectReference);

    for (id object in allRetainedReferences) {
        FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, object, self.configuration);
        if (element) {
            [results addObject:element];
        }
    }

    return [NSSet setWithArray:results];
}

Most of the code in this part is not important. It just calls the parent method at the beginning and wraps the obtained object into a series at the endFBObjectiveCGraphElementFinally, an array is returned, that is, all strong references held by the current object block.

What is block?

People who know a little about block know that block is actually a structure, and its structure is roughly as follows:

struct BlockLiteral {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct BlockDescriptor *descriptor;
};

struct BlockDescriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy_helper)(void *dst, void *src);
    void (*dispose_helper)(void *src);
    const char *signature;
};

stayBlockLiteralThere is one in the structureisaPointer, and rightisaEveryone who knows knows knows that there are many people hereisaIn fact, it points to a class. The class pointed to by each block may be__NSGlobalBlock____NSMallocBlock__perhaps__NSStackBlock__However, these blocks inherit from a common parent class, that isNSBlock, we can use the following code to get this class:

static Class _BlockClass() {
    static dispatch_once_t onceToken;
    static Class blockClass;
    dispatch_once(&onceToken, ^{
        void (^testBlock)() = [^{} copy];
        blockClass = [testBlock class];
        while(class_getSuperclass(blockClass) && class_getSuperclass(blockClass) != [NSObject class]) {
            blockClass = class_getSuperclass(blockClass);
        }
        [testBlock release];
    });
    return blockClass;
}

Three blocks in Objective-C__NSMallocBlock____NSStackBlock__and__NSGlobalBlock__Occurs when:

ARC Non arc
Capture external variables __NSMallocBlock__
__NSStackBlock__
__NSStackBlock__
External variable not captured __NSGlobalBlock__ __NSGlobalBlock__
  • In arc, the class that captures the block of external variables will be__NSMallocBlock__perhaps__NSStackBlock__, if block is assigned to a variable, it will be executed in this process_Block_copyReplace the original__NSStackBlock__become__NSMallocBlock__; But if block is not assigned to a variable, its type is__NSStackBlock__; A class that does not capture a block of external variables will be__NSGlobalBlock__It is neither on the heap nor on the stack. It will be in the code segment like C language functions.

  • In non arc, the class of block that captures external variables will be__NSStackBlock__, which is placed on the stack and does not capture the block of external variables, is the same as in the arc environment.

If we keep printing a blocksuperclassIt will eventually be found in the inheritance chainNSBlockFigure of:

How do blocks in IOS hold objects

Then you can use this method to determine whether the current object is a block:

BOOL FBObjectIsBlock(void *object) {
    Class blockClass = _BlockClass();
    
    Class candidate = object_getClass((__bridge id)object);
    return [candidate isSubclassOfClass:blockClass];
}

How does block hold objects

In this section, we will discuss that block isHow to hold objects, we will analyze the source code of fbretain cycle detector and finally answer this question in detail as much as possible.

Go back to what was mentioned at the beginning of the article- allRetainedObjectsmethod:

- (NSSet *)allRetainedObjects {
    NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy];

    __attribute__((objc_precise_lifetime)) id anObject = self.object;

    void *blockObjectReference = (__bridge void *)anObject;
    NSArray *allRetainedReferences = FBGetBlockStrongReferences(blockObjectReference);

    for (id object in allRetainedReferences) {
        FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, object, self.configuration);
        if (element) {
            [results addObject:element];
        }
    }

    return [NSSet setWithArray:results];
}

Through the sign of the function, we can also guess that in the above methodFBGetBlockStrongReferencesGet all strong references held by the block:

NSArray *FBGetBlockStrongReferences(void *block) {
    if (!FBObjectIsBlock(block)) {
        return nil;
    }

    NSMutableArray *results = [NSMutableArray new];

    void **blockReference = block;
    NSIndexSet *strongLayout = _GetBlockStrongLayout(block);
    [strongLayout enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
        void **reference = &blockReference[idx];

        if (reference && (*reference)) {
            id object = (id)(*reference);

            if (object) {
                [results addObject:object];
            }
        }
    }];

    return [results autorelease];
}

andFBGetBlockStrongReferencesIs another private function_GetBlockStrongLayoutThe encapsulation of is also the most interesting part of the implementation.

Several necessary concepts

In the specific introduction_GetBlockStrongLayoutBefore the source code of the function, I hope to have a brief introduction to its principle for your understanding; Here are three concepts that need to be introduced. The first is the location where all objects held by block exist.

How to hold objects

The structure of block has appeared on the top of the article. I wonder if readers still have an impression:

struct BlockLiteral {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct BlockDescriptor *descriptor;
    // imported variables
};

Under each block structure, all objects held by the current block will be stored, regardless of strength. We can do a small experiment to verify this view. We declare such a block in the program:

NSObject *firstObject = [NSObject new];
__attribute__((objc_precise_lifetime)) NSObject *object = [NSObject new];
__weak NSObject *secondObject = object;
NSObject *thirdObject = [NSObject new];

__unused void (^block)() = ^{
    __unused NSObject *first = firstObject;
    __unused NSObject *second = secondObject;
    __unused NSObject *third = thirdObject;
};

Then make a breakpoint in the code:

How do blocks in IOS hold objects

In the above code, the block is executed because it is referenced by a variable_Block_copy, so its type is__NSMallocBlock__, all blocks that are not referenced by variables are__NSStackBlock__

  1. First, print the size of the block variable. Because the block variable is actually just a pointer to the structure, the size is 8, while the size of the structure is 32;

  2. Take the address of the block as the base address, offset 32, and get a pointer

  3. use$3[0] $3[1] $3[2]Print the address in sequence as0x1001023b0 0x1001023b8 0x1001023c0You can find that they are all references captured by the block. The first two are strong references and the last is weak references

It can be concluded that block stores the captured references under the structure, but why is the order here not in the order of references? Next, add several variables and do another experiment:

How do blocks in IOS hold objects

After adding several more objects to the code, the layout order of the objects held by the block is still the sameStrong reference first, weak reference last, we might as well make a hypothesis:Block places strongly referenced objects in front of weakly referenced objects。 But this assumption can help us inWhen there is only block but no context informationDistinguish which are strong references? I don’t think so, because we can’t know where the dividing line between them is.

dispose_helper

The second thing to introduce isdispose_helper, this isBlockDescriptorA pointer in the structure:

struct BlockDescriptor {
    unsigned long int reserved;                // NULL
    unsigned long int size;
    // optional helper functions
    void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
    void (*dispose_helper)(void *src);         // IFF (1<<25)
    const char *signature;                     // IFF (1<<30)
};

There are two function pointers in the above structure,copy_helperCopy for block,dispose_helperFor blockdisposeThat is, when a block is destructed, it will call this function pointer to destroy its own objects. This principle is also the key to distinguish strong and weak references, because indispose_helperWill be sent for strong referencesreleaseMessage, no processing will be done for weak references.

FBBlockStrongRelationDetector

Finally, it is used todispose_helperThe class that receives the messageFBBlockStrongRelationDetectorYes; Its instance is being acceptedreleaseThe message is not really released, only the tag_strongYes:

- (oneway void)release {
    _strong = YES;
}

- (oneway void)trueRelease {
    [super release];
}

Only real implementationtrueReleaseWill be sent to the object whenreleaseNews.

Because this file is overwrittenreleaseMethod, so compile under non arc:

#if __has_feature(objc_arc)
#error This file must be compiled with MRR. Use -fno-objc-arc flag.
#endif

If a block holds another block object,FBBlockStrongRelationDetectorYou can also make your own fake block to prevent a crash when receiving a message about block release:

struct _block_byref_block;
@interface FBBlockStrongRelationDetector : NSObject {
    // __block fakery
    void *forwarding;
    int flags;   //refcount;
    int size;
    void (*byref_keep)(struct _block_byref_block *dst, struct _block_byref_block *src);
    void (*byref_dispose)(struct _block_byref_block *);
    void *captured[16];
}

When an instance of this class is initialized, it will be setforwardingbyref_keepandbyref_dispose, the implementation of the latter two methods is empty, just to prevent crash:

+ (id)alloc {
    FBBlockStrongRelationDetector *obj = [super alloc];
    
    // Setting up block fakery
    obj->forwarding = obj;
    obj->byref_keep = byref_keep_nop;
    obj->byref_dispose = byref_dispose_nop;
    
    return obj;
}

static void byref_keep_nop(struct _block_byref_block *dst, struct _block_byref_block *src) {}
static void byref_dispose_nop(struct _block_byref_block *param) {}

Gets the object strongly referenced by the block

So far, all the knowledge required to obtain the strong reference object of block has been introduced. Next, you can make a description of the private method_GetBlockStrongLayoutThe following are analyzed:

static NSIndexSet *_GetBlockStrongLayout(void *block) {
    struct BlockLiteral *blockLiteral = block;
    
    if ((blockLiteral->flags & BLOCK_HAS_CTOR)
        || !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
        return nil;
    }
    
    ...
}
  • If the block has a constructor / destructor for CPP, explain itIt is very likely that the objects held are not aligned according to the pointer size, it is difficult to detect all objects

  • If block doesn’tdisposeFunction, indicating that it cannotretainObject, that is, we have no way to test which objects are strongly referenced

static NSIndexSet *_GetBlockStrongLayout(void *block) {
    ...
    void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper;
    const size_t ptrSize = sizeof(void *);    
    const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;
    
    void *obj[elements];
    void *detectors[elements];
    
    for (size_t i = 0; i < elements; ++i) {
        FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
        obj[i] = detectors[i] = detector;
    }
    
    @autoreleasepool {
        dispose_helper(obj);
    }
    ...
}
  1. fromBlockDescriptortake outdispose_helperas well assize(size of all objects held by block)

  2. adopt(blockLiteral->descriptor->size + ptrSize - 1) / ptrSizeRound up to get the number of pointers held by the block

  3. Initialize two containselementsindividualFBBlockStrongRelationDetectorAn array of instances, where the first array is passed indispose_helper, the second array is used for detection_strongMarked asYES

  4. Execute in auto release pooldispose_helper(obj), release the object held by the block

static NSIndexSet *_GetBlockStrongLayout(void *block) {
    ...
    NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];
    
    for (size_t i = 0; i < elements; ++i) {
        FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
        if (detector.isStrong) {
            [layout addIndex:i];
        }
        
        [detector trueRelease];
    }
    
    return layout;
}

becausedispose_helperOnly callreleaseMethod, but this will not lead to ourFBBlockStrongRelationDetectorThe instance is released and marked instead_stringProperty. Here, we only need to judge whether this property is true or false, add the corresponding index to the array, and then calltrueReleaseReal release object.

We can execute the following code to analyze its working process:

NSObject *firstObject = [NSObject new];
__attribute__((objc_precise_lifetime)) NSObject *object = [NSObject new];
__weak NSObject *secondObject = object;
NSObject *thirdObject = [NSObject new];

__unused void (^block)() = ^{
    __unused NSObject *first = firstObject;
    __unused NSObject *second = secondObject;
    __unused NSObject *third = thirdObject;
};

FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:block];
[detector findRetainCycles];

staydispose_helperBefore calling:

How do blocks in IOS hold objects

objEvery position in the array is storedFBBlockStrongRelationDetectorBut indispose_helperAfter call:

How do blocks in IOS hold objects

The instances with indexes 4 and 5 have been cleared, and the correspondingFBBlockStrongRelationDetectorInstancestrongHas been marked asYES, add to the array and return; Finally, the indexes of all strong references are obtained, and the objects strongly referenced by block are obtained.

summary

In fact, at the beginning, the author was interested in thisdispose_helperThe implementation mechanism is not particularly certain, but there is a guess, but I’m askingFBBlockStrongRelationDetectorAfter the author of the bookdispose_helperIs really responsible for sending to all captured variablesreleaseNews, if you are interested, you can see thisissue。 This part of the code actually originated from the great God of Mike ashCircleHowever, I don’t know how he found this. If you have relevant information or reasonable explanation, you can contact me at any time.

Pay attention to the warehouse and get updates in time:iOS-Source-Code-Analyze

Follow: Draveness · Github

Original link:http://draveness.me/block-ret…

Recommended Today

Swift advanced (XV) extension

The extension in swift is somewhat similar to the category in OC Extension can beenumeration、structural morphology、class、agreementAdd new features□ you can add methods, calculation attributes, subscripts, (convenient) initializers, nested types, protocols, etc What extensions can’t do:□ original functions cannot be overwritten□ you cannot add storage attributes or add attribute observers to existing attributes□ cannot add parent […]