macOS 26 窗口圆角不一致?一行代码没改,强制统一

你刚升级完 macOS 26,兴冲冲打开 Safari,然后愣住了。
窗口边角的圆弧半径,怎么跟备忘录不一样?备忘录的边角又跟 Finder 不一样?这种感觉就像买了一双新鞋,左脚鞋带孔比右脚多两个——说不上大毛病,但浑身难受。

这就是 macOS 26 现在的状态。苹果的设计师显然迷上了"圆润"这个概念,但他们在实现上完全放飞了自我。每个 app 各玩各的,圆角半径参差不齐,整个系统看起来像是不同设计师在赶不同的 deadline。
大家都在怎么修
网上最常见的解法是:禁用 SIP,改系统动态库。
SIP 就是系统完整性保护,相当于 Mac 的免疫系统。关掉它你就能改 /System/Library/ 下面的文件,但代价是电脑的安全防线彻底塌了一角。说实话,别人物理接触到你的机器,SIP 也拦不住,但这种解法总让人觉得在用大炮打蚊子。
更关键的是,改系统文件意味着每次系统更新都要重来。苹果一推送补丁,你的圆角补丁就没了。很烦。
换个思路:不拆窗户,统一窗帘
原帖作者给出了一个更有意思的想法:与其把圆角全去掉,不如让它们全都一样丑。
这句话听起来像是在抖机灵,但背后有朴素的产品逻辑。一致的不完美,比混乱的不一致要舒服得多。就像房间里窗帘高度不一很碍眼,但全部统一成同一种丑窗帘,反而顺眼了。
具体怎么实现?他用了一个叫 Method Swizzling 的技术。
什么是 Method Swizzling
Method Swizzling 是 Objective-C Runtime 提供的一个骚操作。
正常情况下,代码调用一个方法,runtime 会找到这个方法的实现然后执行。Swizzling 干的事情是:把方法名 A 和方法实现 B 换个位置。也就是说,以后调用 A,实际上执行的是 B。
类比一下:你叫外卖,系统默认分配小哥 A 送餐。Swizzling 就是把小哥 A 的名字牌和小哥 B 的名字牌互换,结果就是你点的是 A 套餐,送来的是 B 做的饭。整个过程不需要改动任何业务代码,runtime 自己就搞定了。
在 macOS 里,窗口圆角的渲染逻辑在 NSThemeFrame 这个私有类里。这个类决定了每个窗口的角落应该有多圆。原帖作者做的事,就是把计算圆角的方法 Hook 住,无论系统原来想返回什么值,统一返回 23pt。
这样 Safari 也好,Finder 也好,备忘录也好,拿到手的圆角半径都是同一个数。视觉上立刻整齐了。
核心代码就这些
#import <AppKit/AppKit.h>
#import <objc/runtime.h>
static CGFloat kDesiredCornerRadius = 23.0;
// 替换获取圆角半径的方法
static double swizzled_cornerRadius(id self, SEL _cmd) {
return kDesiredCornerRadius;
}
// 替换缓存的圆角半径
static double swizzled_getCachedCornerRadius(id self, SEL _cmd) {
return kDesiredCornerRadius;
}
// 替换顶部角落尺寸
static CGSize swizzled_topCornerSize(id self, SEL _cmd) {
return CGSizeMake(kDesiredCornerRadius, kDesiredCornerRadius);
}
// 替换底部角落尺寸
static CGSize swizzled_bottomCornerSize(id self, SEL _cmd) {
return CGSizeMake(kDesiredCornerRadius, kDesiredCornerRadius);
}
// 程序启动时自动执行
__attribute__((constructor))
static void init(void) {
// 只修改第三方 GUI 应用,不动苹果自己的系统组件
NSString *bid = [[NSBundle mainBundle] bundleIdentifier];
if (!bid || [bid hasPrefix:@"com.apple."]) return;
Class cls = NSClassFromString(@"NSThemeFrame");
if (!cls) return;
Method m1 = class_getInstanceMethod(cls, @selector(_cornerRadius));
if (m1) method_setImplementation(m1, (IMP)swizzled_cornerRadius);
Method m2 = class_getInstanceMethod(cls, @selector(_getCachedWindowCornerRadius));
if (m2) method_setImplementation(m2, (IMP)swizzled_getCachedCornerRadius);
Method m3 = class_getInstanceMethod(cls, @selector(_topCornerSize));
if (m3) method_setImplementation(m3, (IMP)swizzled_topCornerSize);
Method m4 = class_getInstanceMethod(cls, @selector(_bottomCornerSize));
if (m4) method_setImplementation(m4, (IMP)swizzled_bottomCornerSize);
}
编译成动态库:
clang -arch arm64e -arch x86_64 -dynamiclib -framework AppKit \
-o SafariCornerTweak.dylib \
SafariCornerTweak.m
sudo mkdir -p /usr/local/lib/
sudo cp SafariCornerTweak.dylib /usr/local/lib/
sudo codesign -f -s - /usr/local/lib/SafariCornerTweak.dylib
用 launchd 每次开机自动加载:
launchctl load ~/Library/LaunchAgents/com.local.dyld-inject.plist
为什么这个方案更好

第一,不需要动 SIP。DYLD_INSERT_LIBRARIES 这个环境变量允许你在不修改系统文件的情况下给任意进程注入动态库。你自己用没问题,系统更新也不会覆盖它。
第二,作用域可控。代码里明确排除了 com.apple. 前缀的 bundle ID,所以 Safari、Finder 这些苹果自带应用会被放过,只改第三方应用。这个选择见仁见智,原帖作者的意思是系统应用好歹有苹果设计师把关,第三方应用才是重灾区。
第三,这个思路和产品设计里的"设计系统"概念一脉相承。设计系统不追求每个组件都完美无缺,而是先定规则,让所有组件遵守同一套约束。规则统一了,秩序自然就来了,比单独优化每个角落要省心得多。
想改圆角数值怎么办
代码里最显眼的就是这个:
static CGFloat kDesiredCornerRadius = 23.0;
把这个值改成 0.0,窗口就变成直角了。改成 50.0,整个屏幕看起来像被气泡膜包住。根据自己口味调就好。
不过有一点要提醒:这个方案本质上是黑魔法。NSThemeFrame 是苹果的私有类,方法名 _cornerRadius 带下划线前缀,说明本来就不是给你用的。苹果下次改版可能连类名都换掉,这个补丁就失效了。到时候重新编译一把就行,不是什么大事。
技术从来不只有一种解法。禁用 SIP 是一种,它直接但粗暴。把圆角去掉是一种,它有效但可能矫枉过正。Method Swizzling 是第三种——它不改变系统的数据结构,只是改变了执行顺序。温柔,但有效。
下次系统里再有什么让你浑身难受的地方,不妨先想想:有没有一种方法,可以在不动根本的前提下,让它自己变整齐?
常见问题
Q: 这个方法需要禁用 SIP 吗?
不需要。方案用的是 DYLD_INSERT_LIBRARIES 注入,不需要改系统文件,也就不需要关闭系统完整性保护。
Q: 苹果系统更新后补丁会失效吗?
有可能。NSThemeFrame 是苹果的私有类,如果苹果在更新中改了类名或方法名,补丁会失效,重新编译即可。失效时 Safari 会恢复成原来的不一致状态,不会导致系统崩溃。
Q: 23pt 的圆角数值是怎么定的?
原帖作者的建议值。你可以改成 0.0 得到直角窗口,或改成更大的数值得到更圆润的效果。调到自己看着顺眼为止。