在没有计算机的情况下切换 iOS 渲染调试选项

1,051 阅读5分钟

您是否曾经离开过您的计算机,在您的应用程序上玩弄一项功能,并在那时发现了您想要深入了解的性能问题?

我们用来调试这些问题的一些工具,比如 Instruments,已经足够强大了,我不会责怪它们需要单独的机器来使用。

然而,其他人没有同样的借口——比如非常有用的渲染调试选项(比如颜色混合层或颜色离屏渲染)。尽管这些是通过 Xcode 启用的,但它们本质上只是在设备本身上切换一个标志,因此我们可以将 Xcode 完全从图片中删除似乎是合理的。

注意:这篇文章的时间可能看起来有点偏离,因为现在远离计算机比平时更不可行。

作为对比,我想提一下 WWDC 距离不到一周的时间,我觉得现在发布这篇文章会略微增加由于运气不好而获得官方支持并且这篇文章变得多余的可能性——这会总体来说还是一个不错的结果。

CARenderServerSetDebugOption

当您在 Xcode 中切换渲染调试选项之一时,最终结果是CARenderServerSetDebugOption在您的设备或模拟器上调用。该函数接受三个参数:

CARenderServerSetDebugOption(
    0,    /* render server mach port */
    0x02, /* debug option (Color Blended Layers) */
    1     /* new value */
)

CARenderServerSetDebugOption 通过与 iOS 的渲染服务器通信来工作,这是一个单独的进程,处理……嗯,渲染。

这里的第一个参数是一个允许其他进程与该服务器通信的 mach 端口。你可以先调用相应的端口自己CARenderServerGetServerPort,但你也可以通过0CARenderServerSetDebugOption看它给你的。

剩下的两个参数相当简单——我们传递一个代表我们想要更新的选项的整数值,然后是它的新值(01)。

以下是 Xcode 允许您切换的选项:

Color Blended Layers:              0x02
Color Hits Green and Misses Red:   0x13
Color Copied Images:               0x01
Color Layer Formats:               0x14
Color Immediately:                 0x03
Color Misaligned Images:           0x0E
Color Offscreen-Rendered Yellow:   0x11
Color Compositing Fast-Path Blue:  0x12
Flash Updated Regions:             0x00

从模拟器设置选项

由于CARenderServerSetDebugOption不是公共函数,我们需要在运行时查找它。我们可以使用 来做到这一点 dlsym,它允许我们在给定的动态库(在本例中为 QuartzCore)中找到符号的地址:

// The location of the QuartzCore
// framework on iOS
let quartzCorePath =
    "/System/Library/Frameworks/" +
    "QuartzCore.framework/QuartzCore"

// Use `dlopen` to get a handle
// for the QuartzCore framework
let quartzCoreHandle = dlopen(
    quartzCorePath,
    RTLD_NOW)

// Store the address of our function
let functionAddress = dlsym(
    quartzCoreHandle,
    "CARenderServerSetDebugOption")

// Create a typealias representing our
// function's param types / return type
typealias functionType = @convention(c) (
    CInt, CInt, CInt
) -> Void

// Cast the address to the
// above function type
let CARenderServerSetDebugOption = unsafeBitCast(
    functionAddress,
    to: functionType.self)

// Call the function!
CARenderServerSetDebugOption(0, 0x2, 1)

如果您在模拟器上运行的应用程序中调用上述代码,您将看到颜色混合图层选项生效,您的应用程序现在绘制在红色和绿色的海洋中。

CARenderServerSetDebugOption上面列表中的其他值替换调用中的第二个参数 允许您切换其他选项 - 包括通常只为设备而不是模拟器公开的选项(尽管并非所有都可以完美运行)。

从设备设置选项

权利

上面的代码在物理设备上运行时不会出现任何错误或日志——但它实际上不会产生任何影响。那是因为调用此函数的二进制文件必须包含com.apple.QuartzCore.debug权利。

使用ldid 或jtool很容易添加此权限以在越狱设备上使用 ,但不幸的是,这确实限制了我们在标准设备上使用此功能的能力。

注意:这里也可以使用权利绕过,但我对这个特殊权利的运气不太好。部分问题可能是我仍然不清楚实际检查此权利的确切位置 - 另一个问题。

不妨建立一个偏好包

由于我们在这里基本上仅限于越狱设备,我们不妨切换到通过设置来控制它,因为这比保留一些随机应用程序来切换渲染选项感觉更自然。

PreferenceLoader提供了一种简单的方法来做到这一点;它允许我们创建自己的首选项窗格,并完全控制它显示的视图控制器,这非常适合我们的用例。

我们可以从创建 的子类开始 PSListController,由 iOS' 提供Preferences.framework。这为我们提供了很多便利,包括使用PSSpecifier 实例轻松创建新开关单元的能力 ,每个实例都描述了个人偏好。

例如,下面的设置将创建一个带有单个开关的首选项窗格,以及在切换该开关时运行任意代码的能力:

#import <Preferences/PSListController.h>
#import <Preferences/PSSpecifier.h>

// Create a `PSListController` subclass
@interface ExampleListController: PSListController

@end

@implementation ExampleListController

// Override `viewDidLoad` to setup our specifiers,
// which describe each preference we support
- (void)viewDidLoad {
    [super viewDidLoad];

    // Specifier for a switch labeled "Basic Switch"
    // that calls our setter below when toggled
    PSSpecifier *specifier =
        [PSSpecifier preferenceSpecifierNamed:@"Basic Switch"
                                       target:self
                                          set:@selector(valueChanged:forSpecifier:)
                                          get:nil
                                       detail:Nil
                                         cell:PSSwitchCell
                                         edit:Nil];

    // Update our controller's specifiers list
    self.specifiers = [NSMutableArray arrayWithObject:specifier];
}

// Setter called when our switch is toggled
- (void)valueChanged:(NSNumber *)value forSpecifier:(PSSpecifier *)specifier {
    // Logs "Specifier 'Basic Switch' changed to 1" (or 0)
    NSLog(@"Specifier '%@' changed to %i",
        specifier.name,
        value.boolValue);
}

@end

从这里,我们可以轻松地PSSpecifier为每个想要公开的渲染调试选项创建一个,然后调用CARenderServerSetDebugOption我们的 setter 来启用该选项。我们甚至可以添加一个 getter,用于CARenderServerGetDebugOption确保我们的首选项窗格在重新启动时始终反映当前状态。

最终结果

通过此设置,我们可以构建完整的首选项窗格,我对最终结果非常满意:

bryce.co/on-device-r…

如果您对生成的代码感兴趣,或者想在自己的设备上试用,可以在GitHub 上找到该项目。

文末推荐:iOS热门文集

iOS面试基础知识 (一)

iOS面试基础知识 (二)

iOS面试基础知识 (三)

iOS面试基础知识 (四)

iOS面试基础知识 (五)