FBRetainCycleDetector

repository·main·Indexed 26 days ago

https://github.com/facebook/fbretaincycledetector

An iOS library that uses runtime analysis to identify retain cycles in Objective-C applications. It allows developers to find memory leaks by detecting cycles in the object graph, with support for custom maximum cycle lengths, filtering via FBObjectGraphConfiguration, and detection of cycles caused by Objective-C Associations using fishhook to rebind symbols.

Tokens
1.7K
Snippets
7
Records
8
Agent score
38%

What's inside FBRetainCycleDetector

  1. Detect retain cycles caused by Objective-C Associations

    main

    To detect retain cycles caused by associated objects (e.g., using OBJC_ASSOCIATION_RETAIN_NONATOMIC), you must hook into the Objective-C runtime early in the application lifecycle.

    Call [FBAssociationManager hook] in your main.m file. This uses fishhook to interpose objc_setAssociatedObject and objc_resetAssociatedObjects.

    #import <FBRetainCycleDetector/FBAssociationManager.h>
    
    int main(int argc, char * argv[]) {
      @autoreleasepool {
        [FBAssociationManager hook];
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
      }
    }
  2. Install FBRetainCycleDetector via CocoaPods

    main

    Add the following line to your podspec:

    pod 'FBRetainCycleDetector'

    Note: By default, FBRetainCycleDetector is only fully functional in Debug builds. You can enable it in other configurations by providing a specific compilation flag (refer to the source header for details).

  3. Rebind symbols with fishhook

    main

    Use fishhook to dynamically rebind symbols in Mach-O binaries running on iOS (both simulator and device). This is useful for hooking calls in libSystem for debugging or tracing purposes.

    To use it, add fishhook.h and fishhook.c to your project and call rebind_symbols with an array of struct rebinding elements. Each element specifies the symbol name to hook, the replacement function, and a pointer to store the original function pointer so you can call it from your hook.

    #import <dlfcn.h>
    #import <UIKit/UIKit.h>
    #import "fishhook.h"
    
    // 1. Define function pointers to hold the original implementations
    static int (*orig_close)(int);
    static int (*orig_open)(const char *, int, ...);
    
    // 2. Define your replacement functions
    int my_close(int fd) {
      printf("Calling real close(%d)\n", fd);
      return orig_close(fd); // Call the original function
    }
    
    int my_open(const char *path, int oflag, ...) {
      // ... implementation logic ...
      return orig_open(path, oflag, mode); // Call the original function
    }
    
    int main(int argc, char * argv[])
    {
      @autoreleasepool {
        // 3. Perform the rebinding
        // The array contains: {symbol_name, replacement_function, original_function_pointer_storage}
        rebind_symbols((struct rebinding[2]){
          {"close", my_close, (void *)&orig_close},
          {"open", my_open, (void *)&orig_open}
        }, 2);
    
        // ... rest of application ...
      }
    }
  4. Install FBRetainCycleDetector via Carthage

    main

    Add the following line to your Cartfile:

    github "facebook/FBRetainCycleDetector"

    Because FBRetainCycleDetector is built out of non-debug builds, you must use the --configuration Debug flag when updating to test it:

    carthage update --configuration Debug
    carthage update --configuration Debug
  5. Basic usage of FBRetainCycleDetector

    main

    To find retain cycles for a specific object, import the header, instantiate the detector, add your object as a candidate, and call findRetainCycles.

    findRetainCycles returns an NSSet<NSArray<FBObjectiveCGraphElement *> *> where each array represents a single retain cycle. Each element in the array is a wrapper around an object in that cycle.

    #import <FBRetainCycleDetector/FBRetainCycleDetector.h>
    
    FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
    [detector addCandidate:myObject];
    NSSet *retainCycles = [detector findRetainCycles];
    NSLog(@
  6. Find retain cycles with a custom maximum cycle length

    main

    By default, the detector looks for cycles no longer than 10 objects. To search for longer cycles (at the cost of performance), use findRetainCyclesWithMaxCycleLength:.

    FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
    [detector addCandidate:myObject];
    NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:100];
  7. Configure filters and NSTimer inspection

    main

    You can use FBObjectGraphConfiguration to filter out specific retain cycles that are not considered leaks. You can also choose whether or not to inspect NSTimer objects, which often cause retain cycles by retaining their targets.

    Use FBFilterBlockWithObjectIvarRelation to create filters based on object/ivar relations. For more filter types, check FBStandardGraphEdgeFilters.

    NSMutableArray *filters = @[
      FBFilterBlockWithObjectIvarRelation([UIView class], @"_subviewCache"),
    ];
    
    // Configuration object can describe filters as well as some options
    FBObjectGraphConfiguration *configuration = [[FBObjectGraphConfiguration alloc] initWithFilterBlocks:filters
                                         shouldInspectTimers:YES];
    FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] initWithConfiguration:configuration];
    [detector addCandidate:myObject];
    NSSet *retainCycles = [detector findRetainCycles];