Objective-C Swizzle方法时如何让栈下游调用走原实现避免递归
我在为一款第三方闭源macOS应用修复滚动条样式适配bug。macOS系统提供三种滚动条显示模式,可在系统偏好设置中配置:
- 始终显示:对应系统常量
NSScrollerStyleLegacy - 滚动时显示:对应系统常量
NSScrollerStyleOverlay - 自动判断:连接触控板时生效
NSScrollerStyleOverlay样式,未连接触控板时生效NSScrollerStyleLegacy样式
按照官方开发规范,应用应当先判断当前系统实际生效的滚动条样式,再决定是否为传统样式滚动条预留布局内边距,标准实现代码如下:
if ([NSScroller preferredScrollerStyle] == NSScrollerStyleLegacy) addPaddingForLegacyScrollbars();
原应用存在的逻辑缺陷
经反编译确认,涉事应用没有调用上述官方推荐API,而是错误地直接读取NSUserDefaults中的配置值做判断,相关逻辑代码如下:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if ([[defaults objectForKey:@"AppleShowScrollBars"] isEqual: @"Always"]) addPaddingForLegacyScrollbars();
该逻辑默认AppleShowScrollBars字段除@"Always"外的所有值都对应Overlay样式滚动条,当系统设置为「自动」模式且设备未连接触控板时,会出现样式判断错误,导致滚动条布局异常。
初始Hook方案的问题
为修复该问题,我使用ZKSwizzle库对NSUserDefaults的objectForKey:方法进行方法混淆(Swizzle),最初的实现代码如下:
- (id)objectForKey:(NSString *)defaultName { if ([defaultName isEqual: @"AppleShowScrollBars"]) { if ([NSScroller preferredScrollerStyle] == NSScrollerStyleLegacy) { return @"Always"; } else { return @"WhenScrolling"; } } return ZKOrig(id, defaultName); }
该实现直接触发栈溢出崩溃:[NSScroller preferredScrollerStyle]的内部实现本身就会调用[NSUserDefaults objectForKey:@"AppleShowScrollBars"]读取用户配置,导致递归调用Swizzle后的方法,最终栈溢出。
临时兼容方案的不足
后续我通过NSThread callStackSymbols获取调用栈信息,提取上一层调用方的标识,当调用方来自AppKit框架时直接走原始方法实现,非AppKit调用才走自定义修复逻辑,调整后的代码如下:
- (id)objectForKey:(NSString *)defaultName { if ([defaultName isEqual: @"AppleShowScrollBars"]) { NSString *caller = [[[NSThread callStackSymbols] objectAtIndex:1] substringWithRange:NSMakeRange(4, 6)]; if (![caller isEqualToString:@"AppKit"]) { if ([NSScroller preferredScrollerStyle] == NSScrollerStyleLegacy) { return @"Always"; } else { return @"WhenScrolling"; } } } return ZKOrig(id, defaultName); }
该方案可以正常运行解决布局问题,但存在明显缺陷:
- 获取调用栈使用的
backtrace_symbols是面向调试场景的API,官方文档明确说明该API不适合用于生产环境的业务逻辑判断 - 根据调用方所属框架返回不同值的实现方式规范性较差
由于目标是闭源应用,我无法修改应用原有业务代码,仅能在方法边界通过Hook做逻辑修改。
核心诉求:仅当被Swizzle的方法被栈上层的业务代码调用时,才执行自定义的替换逻辑;Swizzle逻辑内部(栈下游)发起的同名方法调用,直接走原始实现。
待确认问题:是否有更优雅的方式实现该需求?当前基于调用栈判断调用方的方案是否具备生产环境使用的合理性?
内容的提问来源于stack exchange,提问作者Wowfunhappy

