You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Objective-C Swizzle方法时如何让栈下游调用走原实现避免递归

macOS闭源应用滚动条适配Bug修复相关问题

我在为一款第三方闭源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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 06:18:22