reflective-equality

1.0.1

Compare anything for value equality in Swift
drseg/reflective-equality

What's New

Reflective Equality 1.0.1

2023-05-02T18:34:37Z

This minor version adds support for all Apple platforms - macOS, iOS, tvOS and watchOS.

Reflective Equality

codecov Testspace tests GitHub Workflow Status Platform Compatability Swift Compatability

Reflective Equality is a set of global functions that allows two Swift 'Any' instances to be compared by value.

Values are compared by recursively traversing their properties and subproperties, as well as those of their parents (and parents' parents, etc.), combining the reflection affordances of Swift.Mirror, Swift.String(describing:), and the Objective C runtime. If all property values match, they are considered equal, even if the class instances are not identical (i.e., !==).

Guidance

There is no way to prove that this works on a general basis, and there are inevitably edge cases where a spurious result will be generated. The output is therefore only guaranteed to be correct against the kinds of test cases used to drive the package’s development.

If you’re not sure if you need this, you almost certainly don’t! It is best used for very particular cases when you can be sure it will do the job for you, and that the job is one that actually should be done in the first place. Though it is still under active development (and suggestions are welcome), consider it more of a curiosity than anything else!

Usage

The framework offers three main public functions:

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

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

These can be used as follows:

import ReflectiveEquality

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

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

Clearly the above examples are equatable, so the use of String instances is purely for example’s sake - clearly an extra package isn’t needed to do this.

Slightly more interestingly, a comparison of Equatable conforming types that can’t be made reliably with ==:

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) // ❌
    }
}

This case applies to various NS… classes. Maybe you’d like to see if two NSFont instances are the same font? Or stuff like that.

Here’s an unusual use case for 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 has a lovely way of telling you that all kinds of things are an NSObject. Which they sort of are. And aren’t. And maybe. But really, if I ask it if struct Swift.String { } is an NSObject, in a pure Swift context I’d prefer it just said no. Which ReflectiveEquality does.

Reflective Equality also has a completely experimental set of functions for probing ObjC properties and ivars:

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 are fine, but ivars have a habit of crashing horribly with EXC_BAD_ACCESS when you try to probe them from Swift. So, have fun blowing things up!

Description

  • Swift Tools 5.5.0
View More Packages from this Author

Dependencies

  • None
Last updated: Tue Mar 19 2024 07:45:49 GMT-0900 (Hawaii-Aleutian Daylight Time)