Mobile Development 15 min read

Hooking iOS UICollectionView Delegate with Method Swizzling and NSProxy: Challenges and Solutions

This article explains how to use AOP techniques such as Method Swizzle and NSProxy to hook UICollectionView and UIViewController delegate methods for precise event tracking, analyzes compatibility problems caused by multiple delegate proxies in large apps, and proposes three practical solutions to avoid crashes and maintain extensibility.

ByteDance Dali Intelligent Technology Team
ByteDance Dali Intelligent Technology Team
ByteDance Dali Intelligent Technology Team
Hooking iOS UICollectionView Delegate with Method Swizzling and NSProxy: Challenges and Solutions

In modern iOS apps, precise recommendation systems require extensive data collection, but manually adding instrumentation to every UI element is costly, so developers often adopt a zero‑injection approach based on Aspect‑Oriented Programming (AOP). The simplest way to achieve this is by method swizzling the delegate methods of UIViewController or UICollectionView to record timestamps.

Below is a basic Method Swizzle example for UIViewController :

@interface UIViewController (MyHook)
@end

@implementation UIViewController (MyHook)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        swizzleMethods(self, @selector(viewDidAppear:), @selector(my_viewDidAppear:));
    });
}
- (void)my_viewDidAppear:(BOOL)animated {
    // custom logic before original implementation
    [self my_viewDidAppear:animated]; // calls original method
}
@end

When applying the same idea to UICollectionView , developers quickly encounter two obstacles in large projects like the 今日头条 App:

The setDelegate call often receives a proxy object that does not conform to UICollectionViewDelegate , breaking the usual swizzle.

Multiple consecutive setDelegate calls (first with a bridge, then with the real delegate) cause the original proxy to be released, leaving a dangling pointer that crashes the app.

To solve these issues, the article proposes three strategies:

1. Use a delegate proxy (NSProxy) to wrap the original delegate

Define a generic DelegateProxy subclass of NSProxy that forwards messages to the real delegate while allowing extra tracking logic:

@interface DelegateProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
@end

@implementation DelegateProxy
- (id)forwardingTargetForSelector:(SEL)sel {
    return self.target;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    // insert tracking code here
    [invocation invokeWithTarget:self.target];
}
@end

Then swizzle setDelegate: to replace the supplied delegate with a DelegateProxy instance, storing the proxy in an associated object to keep it alive.

2. Adjust RxCocoa integration

RxCocoa also creates its own delegate proxy. When both RxCocoa and the custom SDK try to wrap the delegate, assertions fire because RxCocoa expects to be the sole proxy. The solution is to modify the Rx proxy creation to detect an existing proxy and skip re‑wrapping, or to let the SDK’s proxy forward to the Rx proxy instead of replacing it.

3. Intercept forwardingTargetForSelector: before RxCocoa’s forwardInvocation:

By handling the delegate callbacks in forwardingTargetForSelector: , the SDK can perform its tracking without interfering with RxCocoa’s message forwarding chain, thus avoiding circular references and dead‑loops.

The article concludes with best‑practice recommendations: use hooks cautiously, ensure any hooked system method follows a clear contract, and test SDK integration thoroughly to prevent hidden crashes.

iosHookingObjective-CMethod SwizzlingDelegate ProxyNSProxy
ByteDance Dali Intelligent Technology Team
Written by

ByteDance Dali Intelligent Technology Team

Technical practice sharing from the ByteDance Dali Intelligent Technology Team

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.