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

XCTest自定义XCTMetric调用时出现未识别选择器崩溃问题

自定义XCTMetric崩溃问题:unrecognized selector sent to instance

问题场景

自定义了继承自NSObject并遵循XCTMetric协议的MyXCTApplicationLaunchMetric类,用于包装系统的XCTApplicationLaunchMetric,但在测试类中调用时,程序在myLaunchMetric.willBeginMeasuring()行崩溃,报错信息如下:

Thread 2: "-[XCTApplicationLaunchMetric willBeginMeasuring]: unrecognized selector sent to instance 0x60000026c620"

同时发现MyXCTApplicationLaunchMetric的init会被调用一次,之后copy(with:)方法也会被调用,不清楚为什么需要执行复制操作。

自定义指标类代码

class MyXCTApplicationLaunchMetric: NSObject, XCTMetric {
    let myLaunchMetric: XCTApplicationLaunchMetric

    override init() {
        myLaunchMetric = XCTApplicationLaunchMetric(waitUntilResponsive: true)
        super.init()
    }

    // XCTMetric conformance
    func willBeginMeasuring() {
        myLaunchMetric.willBeginMeasuring()
    }

    // XCTMetric conformance
    func didStopMeasuring() {
        myLaunchMetric.didStopMeasuring()
    }

    // XCTMetric conformance
    func reportMeasurements(from startTime: XCTPerformanceMeasurementTimestamp, to endTime: XCTPerformanceMeasurementTimestamp) throws -> [XCTPerformanceMeasurement] {
        try myLaunchMetric.reportMeasurements(from: startTime, to: endTime)
    }

    // NSObject conformance
    func copy(with zone: NSZone? = nil) -> Any {
        self
    // have also tried returning MyXCTApplicationLaunchMetric()
    // and MyXCTApplicationLaunchMetric(myLaunchMetric) when provided    
    // an init that accepts that param. 
    }
}

测试类代码

final class PerformanceUITests: XCTestCase {
    func testLaunchPerformance() throws {
        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
            let options = XCTMeasureOptions()
            options.iterationCount = 1

            measure(metrics: [MyXCTApplicationLaunchMetric()],
                    options: options) {
                XCUIApplication().launch()
            }
        }
    }
}

原因分析

  1. 私有API调用问题:XCTApplicationLaunchMetric虽然遵循XCTMetric协议,但willBeginMeasuring、didStopMeasuring这些方法属于框架内部私有方法,并未对外公开。直接调用这些方法会触发“未识别选择器”的崩溃。
  2. copy方法的必要性:XCTest框架在执行性能测试时,会对传入的XCTMetric实例执行复制操作——因为测试可能会多次迭代,每次迭代需要独立的指标实例来维护状态,避免不同迭代间的状态干扰。之前的copy实现要么返回自身(导致同一实例被复用,状态混乱),要么初始化新实例但未正确处理内部逻辑,都会引发后续问题。

解决方案

方案1:直接使用系统指标类

如果只是需要使用启动性能测量功能,完全不需要自定义包装类,直接创建XCTApplicationLaunchMetric实例传入即可:

func testLaunchPerformance() throws {
    if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
        let options = XCTMeasureOptions()
        options.iterationCount = 1
        let launchMetric = XCTApplicationLaunchMetric(waitUntilResponsive: true)
        measure(metrics: [launchMetric], options: options) {
            XCUIApplication().launch()
        }
    }
}

方案2:正确实现自定义指标(避免调用私有方法)

如果确实需要扩展启动指标的功能,需要自己实现XCTMetric的逻辑,不要直接调用XCTApplicationLaunchMetric的私有方法,同时正确实现NSCopying协议:

class MyXCTApplicationLaunchMetric: NSObject, XCTMetric, NSCopying {
    private let waitUntilResponsive: Bool
    
    init(waitUntilResponsive: Bool = true) {
        self.waitUntilResponsive = waitUntilResponsive
        super.init()
    }
    
    // MARK: - NSCopying
    func copy(with zone: NSZone? = nil) -> Any {
        // 返回全新的实例,确保每次迭代使用独立状态
        return MyXCTApplicationLaunchMetric(waitUntilResponsive: self.waitUntilResponsive)
    }
    
    // MARK: - XCTMetric
    func willBeginMeasuring() {
        // 在这里添加你的自定义前置逻辑,比如日志、环境准备等
    }
    
    func didStopMeasuring() {
        // 在这里添加你的自定义后置逻辑,比如数据清理、日志收集等
    }
    
    func reportMeasurements(from startTime: XCTPerformanceMeasurementTimestamp, to endTime: XCTPerformanceMeasurementTimestamp) throws -> [XCTPerformanceMeasurement] {
        // 如果需要复用系统的启动测量逻辑,可以在这里临时创建XCTApplicationLaunchMetric实例获取结果
        // 注意:不要调用它的will/did方法,仅使用reportMeasurements获取数据
        let systemMetric = XCTApplicationLaunchMetric(waitUntilResponsive: waitUntilResponsive)
        return try systemMetric.reportMeasurements(from: startTime, to: endTime)
    }
}

内容的提问来源于stack exchange,提问作者fingia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:05:34