Understanding the Sunglasses Crash Investigation Tool and Objective‑C Runtime Mechanisms
This article introduces the Sunglasses crash investigation tool, explains iOS Objective‑C runtime message handling—including method lookup, dynamic resolution, and forwarding—details method swizzling implementation with code examples, and discusses class clusters and future enhancements for mobile development debugging.
The document begins with an overview of the Sunglasses tool, a utility suite created by JD's shopping cart team to improve development and testing efficiency on mobile platforms. It aggregates debugging, UI inspection, performance tuning, and data analysis tools, offering low integration cost and non‑intrusive code.
It then describes the crash investigation component, which captures crash information in real time, eliminating the need for manual log export and symbol resolution, thereby speeding up feedback to developers.
Runtime Message Mechanism
In Objective‑C, every method call is transformed into objc_msgSend(id self, SEL _cmd, ...) and passes through three stages: message sending, dynamic resolution, and message forwarding. If all stages fail, the runtime raises an unrecognized selector sent to instance error.
The article outlines the method lookup process, showing how the runtime checks caches, performs binary or linear search on method lists, and traverses superclass hierarchies. Key functions include:
static method_t *search_method_list(const method_list_t *mlist, SEL sel) {
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
for (auto & meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
return nil;
}
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list) {
const method_t *first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}Dynamic resolution is attempted once via resolveClassMethod or resolveInstanceMethod . If successful, the runtime retries the lookup.
Message forwarding involves three methods: -forwardingTargetForSelector: , -methodSignatureForSelector: , and -forwardInvocation: . The default forward handler aborts execution with a fatal error.
Method Swizzling Implementation
The article presents a typical swizzle that exchanges viewDidAppear: with a custom implementation, using class_addMethod , class_replaceMethod , and method_exchangeImplementations :
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidAppear:);
SEL swizzledSelector = @selector(swizzle_viewDidAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}Supporting functions are also shown:
static IMP _class_addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace) { ... }
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { ... }
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) { ... }
void method_exchangeImplementations(Method m1, Method m2) { ... }Class Clusters
The article explains that class clusters group private concrete subclasses under a public abstract superclass, simplifying the public API while preserving rich functionality. This design follows the Abstract Factory pattern.
It also lists common crash sources (KVO, NSNull, NSTimer, etc.) and shows how the tool hooks into methodSignatureForSelector: and forwardInvocation: to provide custom crash handling without invoking the system’s default behavior.
Future Plans
Improve real‑time crash UI with a floating window.
Add client‑side data caching and aggregation of crash information.
Support multiple crash scenarios and capture additional context such as operation paths and device state.
Enable server‑side data synchronization and web‑based crash analytics.
References to the Apple Objective‑C runtime source, class cluster documentation, and type encoding guides are provided at the end of the article.
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.