一尘不染

可选字段类型不符合Swift 3中的协议

swift

我有一个带有1个可选字段和1个非可选字段的类,它们都具有Type AnotherClass 类型,并且还符合
CustomProtocol

protocol CustomProtocol {}

class CustomClass: CustomProtocol {

    var nonoptionalField: AnotherClass = AnotherClass()
    var optionalField: AnotherClass?

}

class AnotherClass: CustomProtocol {

}

现场 nonoptionalField 的类型 AnotherClass 和符合 CustomProtocol

另一方面, optionalField 实际上是 Optional ,因此不符合
CustomProtocol

for field in Mirror(reflecting: CustomClass()).children {
    let fieldMirror = Mirror(reflecting: field.value)
    if fieldMirror.subjectType is CustomProtocol.Type {
        print("\(field.label!) is \(fieldMirror.subjectType) and conforms CustomProtocol")
    } else {
        print("\(field.label!) is \(fieldMirror.subjectType) and DOES NOT conform CustomProtocol")
    }
}
// nonoptionalField is AnotherClass and conforms CustomProtocol
// optionalField is Optional<AnotherClass> and DOES NOT conform CustomProtocol

如何解开 optionalField 属性的Type(而不是值),以便可以将其与其协议 CustomProtocol 关联?

换句话说,如何从 Optional 类型中获取包装 好的 Type
AnotherClass __

局限性:

我真的必须通过镜像使用Swift反射,不幸的是, 到目前为止,.subjectType 属性不允许解开 Optional
的可选包装类型。


阅读 273

收藏
2020-07-07

共1个答案

一尘不染

我不认为有一种简单的方法来执行此操作,因为我们目前无法在没有占位符的情况下就泛型类型进行交谈-因此我们不能简单地转换为Optional.Type

我们Optional<Any>.Type也不能转换为,因为编译器无法为其提供实例的元类型值提供相同类型的自动转换(例如An
Optional<Int>可以转换为an Optional<Any>,而an Optional<Int>.Type不能转换为a
Optional<Any>.Type)。

然而,一种解决方案(尽管有些棘手)将是定义一个“虚拟协议”以表示“任何Optional实例”,而与Wrapped类型无关。然后,我们可以让该协议定义一个wrappedType要求,以便获得Wrapped给定Optional类型的元类型值。

例如:

protocol OptionalProtocol {
  // the metatype value for the wrapped type.
  static var wrappedType: Any.Type { get }
}

extension Optional : OptionalProtocol {
  static var wrappedType: Any.Type { return Wrapped.self }
}

现在,如果fieldMirror.subjectTypeOptional<Wrapped>.Type,我们可以将其转换为OptionalProtocol.Type,然后从中获取元wrappedType类型值。然后,这使我们可以检查CustomProtocol一致性。

for field in Mirror(reflecting: CustomClass()).children {
  let fieldMirror = Mirror(reflecting: field.value)

  // if fieldMirror.subjectType returns an optional metatype value
  // (i.e an Optional<Wrapped>.Type), we can cast to OptionalProtocol.Type,
  // and then get the Wrapped type, otherwise default to fieldMirror.subjectType
  let wrappedType = (fieldMirror.subjectType as? OptionalProtocol.Type)?.wrappedType
    ?? fieldMirror.subjectType

  // check for CustomProtocol conformance.
  if wrappedType is CustomProtocol.Type {
    print("\(field.label!) is \(fieldMirror.subjectType) and conforms CustomProtocol")
  } else {
    print("\(field.label!) is \(fieldMirror.subjectType) and DOES NOT conform CustomProtocol")
  }
}

// nonoptionalField is AnotherClass and conforms CustomProtocol
// optionalField is Optional<AnotherClass> and conforms CustomProtocol

这仅处理单个级别的可选嵌套,但可以通过简单地反复尝试将结果元类型值强制转换为OptionalProtocol.Type并获取wrappedType,然后检查CustomProtocol一致性来轻松地应用于任意可选嵌套级别。

class CustomClass : CustomProtocol {
    var nonoptionalField: AnotherClass = AnotherClass()
    var optionalField: AnotherClass??
    var str: String = ""
}

/// If `type` is an `Optional<T>` metatype, returns the metatype for `T`
/// (repeating the unwrapping if `T` is an `Optional`), along with the number of
/// times an unwrap was performed. Otherwise just `type` will be returned.
func seeThroughOptionalType(
  _ type: Any.Type
) -> (wrappedType: Any.Type, layerCount: Int) {

  var type = type
  var layerCount = 0

  while let optionalType = type as? OptionalProtocol.Type {
    type = optionalType.wrappedType
    layerCount += 1
  }
  return (type, layerCount)
}

for field in Mirror(reflecting: CustomClass()).children {

  let fieldMirror = Mirror(reflecting: field.value)
  let (wrappedType, _) = seeThroughOptionalType(fieldMirror.subjectType)

  if wrappedType is CustomProtocol.Type {
    print("\(field.label!) is \(fieldMirror.subjectType) and conforms CustomProtocol")
  } else {
    print("\(field.label!) is \(fieldMirror.subjectType) and DOES NOT conform CustomProtocol")
  }
}
// nonoptionalField is AnotherClass and conforms CustomProtocol
// optionalField is Optional<Optional<AnotherClass>> and conforms CustomProtocol
// str is String and DOES NOT conform CustomProtocol
2020-07-07