首页 > 解决方案 > 在 NSDictionary 中包装 Dictionary 以比较相等性(当值是 Any 时?)是一种安全模式吗?

问题描述

包装是否能够使用实例方法Dictionary来比较相等性(当值为 时)是一种安全模式?NSDictionaryisEqual(to:)Any?

let initial: [String: Any?] = ["a": nil, "b": 3, "c": true]
let current: [String: Any?] = ["a": nil, "b": 3, "c": true]

if NSDictionary(dictionary: initial as [AnyHashable: Any]).isEqual(to: current as [AnyHashable: Any]) {
    print("same")
} else {
    print("different")
}

输出按预期工作,但是使用 Objective-C 对象作为 Swift 对象的包装器是一种可靠的方法吗?

标签: swift

解决方案


不,Objective-C 不能处理 Swift 结构,除了特殊的可桥接结构。

struct : Equatable { }
let dictionary = ["moo": ()]
dictionary == dictionary // true
NSDictionary(dictionary: dictionary).isEqual(to: dictionary) // false

更直接地使用AnyHashable也不会处理所有事情,但它比那种NSDictionary方法更好。

public extension Equatable {
  /// Equate two values of unknown type.
  static func equate(_ any0: Any, _ any1: Any) -> Bool {
    guard
      let equatable0 = any0 as? Self,
      let equatable1 = any1 as? Self
    else { return false }

    return equatable0 == equatable1
  }
}
typealias Hashables = [String: AnyHashable?]
let dictionary: Hashables = ["a": nil, "b": 3, "c": true]
Hashables.equate(dictionary, dictionary) // true

如果这还不够好……</p>

/// A type-erased equatable value.
///
/// An `Equatable` instance is stored as a "`Cast`".
/// Only instances that can be cast to that type can be `==`'d with the `AnyEquatable`.
public struct AnyEquatable<Cast> {
  public init<Equatable: Swift.Equatable>(_ equatable: Equatable) throws {
    equals = try equatable.getEquals()
    cast = equatable as! Cast
  }

  private let equals: (Cast) -> Bool
  private let cast: Cast
}

extension AnyEquatable: Swift.Equatable {
  public static func == (equatable0: Self, equatable1: Self) -> Bool {
    equatable0 == equatable1.cast
  }
}

public extension AnyEquatable {
  static func == (equatable: Self, cast: Cast) -> Bool {
    equatable.equals(cast)
  }

  static func == (cast: Cast, equatable: Self) -> Bool {
    equatable.equals(cast)
  }
}
public extension Equatable {
  /// A closure that equates another instance to this intance.
  /// - Parameters:
  ///   - _: Use the metatype for `Castable` to avoid explicit typing.
  /// - Throws: `CastError.impossible` if a `Castable` can't be cast to `Self`.
  func getEquals<Castable>(_: Castable.Type = Castable.self) throws -> (Castable) -> Bool {
    if let error = CastError(self, desired: Castable.self)
    { throw error }

    return { self == $0 as? Self }
  }
}
/// An error that represents casting gone wrong. ‍♀️
public enum CastError: Error {
  /// An undesired cast is possible.
  case possible

  /// An desired cast is not possible.
  case impossible
}

public extension CastError {
  /// `nil` if  an `Instance` can be cast to `Desired`. Otherwise, `.impossible`.
  init?<Instance, Desired>(_: Instance, desired _: Desired.Type) {
    self.init(Instance.self, desired: Desired.self)
  }

  /// `nil` if  a `Source` can be cast to `Desired`. Otherwise, `.impossible`.
  init?<Source, Desired>(_: Source.Type, desired _: Desired.Type) {
    if Source.self is Desired.Type
    { return nil }

    self = .impossible
  }

  /// `nil` if  an `Instance` cannot be cast to `Undesired`. Otherwise, `.possible`.
  init?<Instance, Undesired>(_: Instance, undesired _: Undesired.Type) {
    self.init(Instance.self, undesired: Undesired.self)
  }

  /// `nil` if  a `Source` cannot be cast to `Undesired`. Otherwise, `.possible`.
  init?<Source, Undesired>(_: Source.Type, undesired _: Undesired.Type) {
    guard Source.self is Undesired.Type
    else { return nil }

    self = .possible
  }
}
let dictionary: [String: AnyEquatable<Any>] = try [
  "a": .init(Double?.none), "b": .init(3), "c": .init(true)
]
dictionary == dictionary // true

推荐阅读