反射相等性

codecov Testspace tests Platform Compatability Swift Compatability

反射相等性是一组全局函数,允许按值比较两个 Swift 'Any' 实例。

通过递归遍历它们的属性和子属性,以及它们的父类(以及父类的父类等)的属性和子属性来比较值,结合了 Swift.MirrorSwift.String(describing:) 和 Objective C 运行时的反射功能。如果所有属性值都匹配,则认为它们相等,即使类实例不相同(即,!==)。

指导

没有办法证明它在一般情况下有效,而且不可避免地会出现边缘情况,产生虚假结果。因此,输出仅保证针对用于驱动包开发的测试用例类型是正确的。

如果您不确定是否需要它,那么您几乎肯定不需要!它最适合用于非常特殊的情况,您可以确定它会对您有效,并且这项工作实际上应该首先完成。虽然它仍在积极开发中(欢迎提出建议),但请将其视为一种好奇心,而不是其他东西!

用法

该框架提供三个主要的公共函数

public func haveSameValue(_ args: [Any]) -> Bool
public func haveSameValue(_ lhs: Any, _ rhs: Any) -> Bool

public func deepDescription(_ instance: Any) -> String

这些可以如下使用

import ReflectiveEquality

class MyClass {
    func myMethod() {
        _ = haveSameValue("cat", "cat") // true
        _ = haveSameValue(["cat", "cat", "cat", "cat"] // true

        _ = haveSameValue("cat", "bat") // false
    }
}

显然,上面的示例是可比较的,因此使用 String 实例纯粹是为了示例 - 显然不需要额外的包来执行此操作。

稍微有趣的是,比较无法通过 == 可靠地进行的符合 Equatable 的类型

import ReflectiveEquality
import XCTest

class MyClass: XCTestCase {
    func testComplexNSAttributedString() {
        typealias ReadingOption = NSAttributedString.DocumentReadingOptionKey
        typealias DocumentType = NSAttributedString.DocumentType
        
        var parsingOptions: [ReadingOption: Any] {
            [ReadingOption.documentType: DocumentType.html]
        }

        var parsedHTML: NSAttributedString {
            NSAttributedString(html: giantHTML.data(using: .utf8)!,
                               options: parsingOptions,
                               documentAttributes: nil)!
        }

        XCTAssertEqual(deepDescription(parsedHTML), deepDescription(parsedHTML)) // ✅
        XCTAssertEqual(parsedHTML, parsedHTML) // ❌
    }
}

这种情况适用于各种 NS… 类。也许你想看看两个 NSFont 实例是否是相同的字体?或者诸如此类的东西。

这是一个用于 deepDescription 的不寻常用例

import ReflectiveEquality

class MyClass: NSObject {
    func myMethod() {
        _ = "cat" is NSObject // true!
        _ = isNSObject("cat") // false!
        _ = isNSObject(self)  // true!
    }

    func isNSObject(_ instance: Any) -> Bool {
        deepDescription(instance).contains("NSObject")
    }
}

Swift 有一种很好的方式告诉你,各种各样的东西都是 NSObject。它们在某种程度上是。又不是。也许吧。但实际上,如果我问它 struct Swift.String { } 是否是 NSObject,在纯 Swift 上下文中,我更希望它直接说不。而 ReflectiveEquality 就是这样做的。

Reflective Equality 还具有一组完全实验性的函数,用于探测 ObjC 属性和 ivar

extension NSObject {    
    public var propertiesAndIvars: [String: Any]
    public var ivars: [String: Any]
    public var properties: [String: Any]

    public var propertyAndIvarValues: [Any]
    public var propertyValues: [Any]
    public var ivarValues: [Any]
}

properties 没问题,但是当您尝试从 Swift 探测它们时,ivars 经常会发生可怕的崩溃,出现 EXC_BAD_ACCESS。所以,尽情地搞破坏吧!