Detects strong pointers held by nsobject objects

Time:2021-8-22

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

Follow: Draveness · Github

Introduced in the last articleFBRetainCycleDetectorIn this article, we begin to analyze how it obtains the strong pointer it holds from each object.

If you don’t read the first article, you’d better read it here and find outFBRetainCycleDetectorHow it works,How to solve the problem of circular reference in IOS

FBRetainCycleDetectorGets a strong pointer to an object throughFBObjectiveCObjectClass- allRetainedObjectsMethod, which is passed through its parent classFBObjectiveCGraphElementInherited, but with different internal implementations.

Allretainedobjects method

We willXXObjectExample demonstration- allRetainedObjectsMethod call procedure:

#import <Foundation/Foundation.h>

@interface XXObject : NSObject

@property (nonatomic, strong) id first;
@property (nonatomic, weak)   id second;
@property (nonatomic, strong) id third;
@property (nonatomic, strong) id forth;
@property (nonatomic, weak)   id fifth;
@property (nonatomic, strong) id sixth;

@end

useFBRetainCycleDetectorThe code of is as follows:

XXObject *object = [[XXObject alloc] init];

FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:object];
__unused NSSet *cycles = [detector findRetainCycles];

stayFBObjectiveCObjectIn,- allRetainedObjectsMethod is just called- _unfilteredRetainedObjects, and then filtered. The article will mainly- _unfilteredRetainedObjectsAnalyze the implementation of:

- (NSSet *)allRetainedObjects {
    NSArray *unfiltered = [self _unfilteredRetainedObjects];
    return [self filterObjects:unfiltered];
}

method- _unfilteredRetainedObjectsThere are still a lot of implementation codes. Here, the code will be divided into several parts. The first is the most important part: how to get the strong reference held by the object:

- (NSArray *)_unfilteredRetainedObjects
    NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);

    NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];

    for (id<FBObjectReference> ref in strongIvars) {
        id referencedObject = [ref objectReferenceFromObject:self.object];

        if (referencedObject) {
            NSArray<NSString *> *namePath = [ref namePath];
            FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
                                                                                    referencedObject,
                                                                                    self.configuration,
                                                                                    namePath);
            if (element) {
                [retainedObjects addObject:element];
            }
        }
    }
    
    ...
}

Get strong references throughFBGetObjectStrongReferencesThis function:

NSArray<id<FBObjectReference>> *FBGetObjectStrongReferences(id obj,
                                                            NSMutableDictionary<Class, NSArray<id<FBObjectReference>> *> *layoutCache) {
    NSMutableArray<id<FBObjectReference>> *array = [NSMutableArray new];
    
    __unsafe_unretained Class previousClass = nil;
    __unsafe_unretained Class currentClass = object_getClass(obj);
    
    while (previousClass != currentClass) {
        NSArray<id<FBObjectReference>> *ivars;
        
        if (layoutCache && currentClass) {
            ivars = layoutCache[currentClass];
        }
        
        if (!ivars) {
            ivars = FBGetStrongReferencesForClass(currentClass);
            if (layoutCache && currentClass) {
                layoutCache[(id<NSCopying>)currentClass] = ivars;
            }
        }
        [array addObjectsFromArray:ivars];
        
        previousClass = currentClass;
        currentClass = class_getSuperclass(currentClass);
    }
    
    return [array copy];
}

The core of the above code is executionFBGetStrongReferencesForClassreturncurrentClassHere we recursively find the pointers of all parent classes and add a cache to speed up the process of finding strong references. The next step is to obtain strong references from the object structure:

static NSArray<id<FBObjectReference>> *FBGetStrongReferencesForClass(Class aCls) {
    NSArray<id<FBObjectReference>> *ivars = [FBGetClassReferences(aCls) filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
        if ([evaluatedObject isKindOfClass:[FBIvarReference class]]) {
            FBIvarReference *wrapper = evaluatedObject;
            return wrapper.type != FBUnknownType;
        }
        return YES;
    }]];

    const uint8_t *fullLayout = class_getIvarLayout(aCls);

    if (!fullLayout) {
        return nil;
    }

    NSUInteger minimumIndex = FBGetMinimumIvarIndex(aCls);
    NSIndexSet *parsedLayout = FBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout);

    NSArray<id<FBObjectReference>> *filteredIvars =
    [ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id<FBObjectReference> evaluatedObject,
                                                                             NSDictionary *bindings) {
        return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
    }]];

    return filteredIvars;
}

The implementation of this method has about three parts:

  1. callFBGetClassReferencesGet all references it points to from the class, whether strong or weak

  2. callFBGetLayoutAsIndexesForDescriptionGet the location information of strong references from the variable layout of the class

  3. useNSPredicateFilter weak references in arrays

Gets the Ivar array of the class

FBGetClassReferencesMethod mainly calls theclass_copyIvarListGet all of the classesivar

The processing of structure attributes is omitted here because it is too complex and involves a large amount of C + + code. Interested readers can check itFBGetReferencesForObjectsInStructEncodingMethod.

NSArray<id<FBObjectReference>> *FBGetClassReferences(Class aCls) {
    NSMutableArray<id<FBObjectReference>> *result = [NSMutableArray new];

    unsigned int count;
    Ivar *ivars = class_copyIvarList(aCls, &count);

    for (unsigned int i = 0; i < count; ++i) {
        Ivar ivar = ivars[i];
        FBIvarReference *wrapper = [[FBIvarReference alloc] initWithIvar:ivar];
        [result addObject:wrapper];
    }
    free(ivars);

    return [result copy];
}

The above implementation is very direct and traversalivarsArrays, usingFBIvarReferenceWrap it up and add itresultClass inFBIvarReferenceIt only serves as a wrapper to save all the attributes saved in Ivar:

typedef NS_ENUM(NSUInteger, FBType) {
  FBObjectType,
  FBBlockType,
  FBStructType,
  FBUnknownType,
};

@interface FBIvarReference : NSObject <FBObjectReference>

@property (nonatomic, copy, readonly, nullable) NSString *name;
@property (nonatomic, readonly) FBType type;
@property (nonatomic, readonly) ptrdiff_t offset;
@property (nonatomic, readonly) NSUInteger index;
@property (nonatomic, readonly, nonnull) Ivar ivar;

- (nonnull instancetype)initWithIvar:(nonnull Ivar)ivar;

@end

Including the name, type, offset and index of the attribute. The type is throughType codeTo get it, inFBIvarReferenceWhen an instance of is initialized, private methods are used- _convertEncodingToType:Convert type encoding to enumeration type:

- (FBType)_convertEncodingToType:(const char *)typeEncoding {
    if (typeEncoding[0] == '{') return FBStructType;

    if (typeEncoding[0] == '@') {
        if (strncmp(typeEncoding, "@?", 2) == 0) return FBBlockType;
        return FBObjectType;
    }

    return FBUnknownType;
}

When the code is about to start fromFBGetClassReferencesMethod, print using lldbresultAll elements in:

Detects strong pointers held by nsobject objects

The above method is successful fromXXObjectClass, but these arrays contain not only strong references, but alsoweakWeak reference to tag:

<__NSArrayM 0x7fdac0f31860>(
    [_first,  index: 1],
    [_second, index: 2],
    [_third,  index: 3],
    [_forth,  index: 4],
    [_fifth,  index: 5],
    [_sixth,  index: 6]
)

Get Ivar layout

When we take outXXObjectAfter all the attributes in, you also need to filter the attributes; So how do we judge whether an attribute is a strong reference or a weak reference? The concept of Ivar layout is introduced into Objective-C to describe the strength of various attributes in the class.

How does it work? Let’s continue firstFBGetStrongReferencesForClassmethod:

Detects strong pointers held by nsobject objects

In objc runtimeclass_getIvarLayoutYou can get the Ivar layout of a class, andXXObjectWhat is Ivar layout like?

(lldb) po fullLayout
"\x01\x12\x11"

Ivar layout is a series of characters in groups of two, such as\xmn, the first digit in each group of Ivar layout indicates yesmA non strong attribute, and the second indicates that there arenStrong attributes; If we don’t understand, weXXObjectAs an example:

@interface XXObject : NSObject

@property (nonatomic, strong) id first;
@property (nonatomic, weak)   id second;
@property (nonatomic, strong) id third;
@property (nonatomic, strong) id forth;
@property (nonatomic, weak)   id fifth;
@property (nonatomic, strong) id sixth;

@end
  • First group\x01Indicates that there are 0 Non strong attributes and then 1 strong attributesfirst

  • Group II\x12Indicates that there is 1 non strong attributesecond, then there are 2 strong attributesthird forth

  • Group 3\x11Indicates that there is 1 non strong attributefifth, then there is a strong attributesixth

After we have a certain understanding of Ivar layout, we can continue toFBGetStrongReferencesForClassAfter analysis, the next thing to do is to use the information provided by Ivar layout to filter all non strong references, which requires the help of two methods. FirstFBGetMinimumIvarIndexMethod to get the minimum value of the variable index:

static NSUInteger FBGetMinimumIvarIndex(__unsafe_unretained Class aCls) {
    NSUInteger minimumIndex = 1;
    unsigned int count;
    Ivar *ivars = class_copyIvarList(aCls, &count);

    if (count > 0) {
        Ivar ivar = ivars[0];
        ptrdiff_t offset = ivar_getOffset(ivar);
        minimumIndex = offset / (sizeof(void *));
    }

    free(ivars);

    return minimumIndex;
}

Then executeFBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout)Get all strongly referencedNSRange

static NSIndexSet *FBGetLayoutAsIndexesForDescription(NSUInteger minimumIndex, const uint8_t *layoutDescription) {
    NSMutableIndexSet *interestingIndexes = [NSMutableIndexSet new];
    NSUInteger currentIndex = minimumIndex;

    while (*layoutDescription != '\x00') {
        int upperNibble = (*layoutDescription & 0xf0) >> 4;
        int lowerNibble = *layoutDescription & 0xf;

        currentIndex += upperNibble;
        [interestingIndexes addIndexesInRange:NSMakeRange(currentIndex, lowerNibble)];
        currentIndex += lowerNibble;

        ++layoutDescription;
    }

    return interestingIndexes;
}

Because the high bit indicates the number of non strong references, we need to addupperNibble, thenNSMakeRange(currentIndex, lowerNibble)Is the scope of strong reference; SkiplowerNibbleIndex of length, movinglayoutDescriptionPointer until allNSRangeAll joinedinterestingIndexesIn this collection, you can return.

Filter weak references in arrays

In the previous stage, since the scope of strong reference has been obtained, we use it directly hereNSPredicatePredicate to filter:

NSArray<id<FBObjectReference>> *filteredIvars =
[ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id<FBObjectReference> evaluatedObject,
                                                                         NSDictionary *bindings) {
    return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
}]];

Detects strong pointers held by nsobject objects

====

Next, let’s go back to the beginning of the article- _unfilteredRetainedObjectsmethod:

- (NSSet *)allRetainedObjects {
    NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);

    NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];

    for (id<FBObjectReference> ref in strongIvars) {
        id referencedObject = [ref objectReferenceFromObject:self.object];

        if (referencedObject) {
            NSArray<NSString *> *namePath = [ref namePath];
            FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
                                                                                    referencedObject,
                                                                                    self.configuration,
                                                                                    namePath);
            if (element) {
                [retainedObjects addObject:element];
            }
        }
    }
    
    ...
}

FBGetObjectStrongReferencesJust returnid<FBObjectReference>Object, also needFBWrapObjectGraphElementWithContextPackage it intoFBObjectiveCGraphElement

FBObjectiveCGraphElement *FBWrapObjectGraphElementWithContext(id object,
                                                              FBObjectGraphConfiguration *configuration,
                                                              NSArray<NSString *> *namePath) {
    if (FBObjectIsBlock((__bridge void *)object)) {
        return [[FBObjectiveCBlock alloc] initWithObject:object
                                           configuration:configuration
                                                namePath:namePath];
    } else {
        if ([object_getClass(object) isSubclassOfClass:[NSTimer class]] &&
            configuration.shouldInspectTimers) {
            return [[FBObjectiveCNSCFTimer alloc] initWithObject:object
                                                   configuration:configuration
                                                        namePath:namePath];
        } else {
            return [[FBObjectiveCObject alloc] initWithObject:object
                                                configuration:configuration
                                                     namePath:namePath];
        }
    }
}

Finally, the encapsulated instance will be added to theretainedObjectsArray.

- _unfilteredRetainedObjectsAt the same time, we should also deal with collection classes, such as arrays or dictionaries, but if they are seamlessly bridged CF collections or metaclasses, although they may followNSFastEnumerationProtocols, but they are not handled here:

- (NSArray *)_unfilteredRetainedObjects {
    ...

    if ([NSStringFromClass(aCls) hasPrefix:@"__NSCF"]) {
        return retainedObjects;
    }

    if (class_isMetaClass(aCls)) {
        return nil;
    }
    
    ...
}

When traversing the content, the elements in mutable’s collection class may change, so it will be retried several times to ensure that all elements in the collection class are obtained:

- (NSArray *)_unfilteredRetainedObjects {
    ...

    if ([aCls conformsToProtocol:@protocol(NSFastEnumeration)]) {

        NSInteger tries = 10;
        for (NSInteger i = 0; i < tries; ++i) {
            NSMutableSet *temporaryRetainedObjects = [NSMutableSet new];
            @try {
                for (id subobject in self.object) {
                    [temporaryRetainedObjects addObject:FBWrapObjectGraphElement(subobject, self.configuration)];
                    [temporaryRetainedObjects addObject:FBWrapObjectGraphElement([self.object objectForKey:subobject], self.configuration)];
                }
            }
            @catch (NSException *exception) {
                continue;
            }

            [retainedObjects addObjectsFromArray:[temporaryRetainedObjects allObjects]];
            break;
        }
    }

    return retainedObjects;
}

Here, the code that traverses the elements in the collection is put into@tryIf other elements are inserted during traversal, an exception will be thrown, and thencontinueIterate over the collection again, and finally return all the held objects.

The final filter section will useFBObjectGraphConfigurationMediumfilterBlocksFilter out elements that do not need to be added to the collection:

- (NSSet *)filterObjects:(NSArray *)objects {
    NSMutableSet *filtered = [NSMutableSet new];

    for (FBObjectiveCGraphElement *reference in objects) {
        if (![self _shouldBreakGraphEdgeFromObject:self toObject:reference]) {
            [filtered addObject:reference];
        }
    }

    return filtered;
}

- (BOOL)_shouldBreakGraphEdgeFromObject:(FBObjectiveCGraphElement *)fromObject
                               toObject:(FBObjectiveCGraphElement *)toObject {
    for (FBGraphEdgeFilterBlock filterBlock in _configuration.filterBlocks) {
        if (filterBlock(fromObject, toObject) == FBGraphEdgeInvalid) return YES;
    }

    return NO;
}

summary

FBRetainCycleDetectorFinding strong references in objects depends on the Ivar layout of the class. It provides us with information about the strength of attribute references to help filter strong references.

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

Follow: Draveness · Github

Original link:http://draveness.me/retain-cy…