Mobile Development 14 min read

Practical Guide to Detecting Circular References in iOS Applications

This article explains the fundamentals of iOS memory management, describes common memory‑leak scenarios such as circular references, introduces detection tools like MLeaksFinder and FBRetainCycleDetector, and provides concrete code examples and best‑practice techniques for preventing and fixing retain cycles in production apps.

Beike Product & Technology
Beike Product & Technology
Beike Product & Technology
Practical Guide to Detecting Circular References in iOS Applications

Memory is a critical component in computers, temporarily storing data processed by the CPU; its performance directly affects program efficiency. Common memory issues include memory leaks, out‑of‑memory crashes, and wild pointers. This article focuses on practical detection of circular references in iOS.

The lifecycle of memory can be summarized as allocation → usage → release. When dynamically allocated heap memory is not released, a memory leak occurs, leading to wasted resources, slower execution, or crashes.

In iOS development, typical leaks arise from unreleased C/C++ allocations, and especially from circular references between objects such as Blocks, NSTimer, and self. After ARC, circular references have become the primary cause of leaks.

Common detection tools include Xcode’s static analyzer, Instruments’ Leak and Allocation tools, Memory Graph, as well as third‑party frameworks like MLeaksFinder and FBRetainCycleDetector.

Principle

MLeaksFinder adds a willDealloc method to NSObject . When an object should be deallocated, a weak pointer is created; after a short delay, the method asserts whether the object is still alive. If it is, a leak is reported via a popup.

False‑positive cases include objects designed as singletons or cached instances, which are intentionally retained after a view controller is popped, and delayed releases in asynchronous code. For singletons or caches, the detection can be ignored; for delayed releases, overriding willDealloc to return NO suppresses the warning.

FBRetainCycleDetector treats the object under inspection as a root node, traverses its strong references, filters according to configuration, and then performs a depth‑first search to find cycles, reducing the problem to directed‑graph cycle detection.

Finding strong references involves:

Ordinary Objective‑C objects: inspect ivars via class_copyIvarList and class_getIvarLayout , enumerate collection elements, and hook objc_setAssociatedObject for associated objects.

Blocks: identify the block type ( __NSGlobalBlock , __NSMallocBlock , __NSStackBlock ) and use the block’s dispose_helper function to retrieve captured strong references.

NSTimer: obtain the CFRunLoopTimerContext via CFRunLoopTimerGetContext and extract target and userInfo from the _FBNSCFTimerInfoStruct .

Typical leak scenarios in real projects are often caused by improper Block usage. The common mitigation is the weak‑strong dance, but misuse of @weakify and @strongify can still create cycles.

Example of RAC macros:

#define weakify(...) \
      rac_keywordify \ 
      metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
#define strongify(...) \ 
   rac_keywordify \ 
_Pragma("clang diagnostic push") \ 
   _Pragma("clang diagnostic ignored \"-Wshadow\"") \ 
   metamacro_foreach(rac_strongify_,, __VA_ARGS__) \ 
   _Pragma("clang diagnostic pop")
// usage equivalence
__weak __typeof__(self) self_weak_ = self;
__strong __typeof__(self) self = self_weak_;

In practice, @weakify and @strongify must be paired; otherwise the weak reference is unused or the strong reference is undefined, leading to compiler warnings and potential leaks.

When dealing with nested Blocks, apply @weakify/@strongify on the outer Block and only @strongify inside inner Blocks:

@weakify(self)
self.block1 = ^BOOL {
    @strongify(self)
    self.block2 = ^BOOL {
        @strongify(self)
        NSLog(@"hello %@ ", self);
    };
};

In real‑world debugging, two common leak patterns were observed:

Child view retains a Block that captures its parent, forming a cycle after the parent view controller is popped.

Child view is held by a singleton, preventing its release when the parent controller is deallocated.

Both cases lead to memory not being reclaimed, causing increased RAM usage and potential OOM on low‑end devices.

To improve detection, after MLeaksFinder reports a leak, the system can scan the binary’s __DATA segment for global objects, temporarily add a reverse reference from the leaked object to the global, and re‑run the cycle detector to see if a global‑to‑leak reference exists.

For offline prevention, the article suggests silent leak notifications (no pop‑ups) that send alerts to a development chat bot and log the leak status, reducing developer interruption while ensuring leaks are still tracked.

In summary, the article presents the theory and practice of iOS circular‑reference detection, reports a >20% reduction in OOM incidents after systematic remediation, and emphasizes the need for ongoing vigilance and tooling to maintain app stability.

iOSmemory-leakcircular referenceFBRetainCycleDetectorMLeaksFinderstrongifyweakify
Beike Product & Technology
Written by

Beike Product & Technology

As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.