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

jul*_*adi 8 swift

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

protocol CustomProtocol {}

class CustomClass: CustomProtocol {

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

}

class AnotherClass: CustomProtocol {

}
Run Code Online (Sandbox Code Playgroud)

现场nonoptionalField是类型AnotherClass,符合CustomProtocol.

另一方面,optionalField实际上是Optional <AnotherClass>,因此不符合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
Run Code Online (Sandbox Code Playgroud)

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

换句话说,如何从Optional <AnotherClass> Type中获取包装的Type AnotherClass

局限性:

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

Ham*_*ish 5

我不相信有一种简单的方法可以做到这一点,因为我们目前无法在没有占位符的情况下谈论泛型类型——因此我们不能简单地强制转换为Optional.Type.

我们也不能Optional<Any>.Type强制转换为 ,因为编译器没有为它为实例提供的元类型值提供相同类型的自动转换(例如 AnOptional<Int>可以转换为 an Optional<Any>,但 anOptional<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 }
}
Run Code Online (Sandbox Code Playgroud)

现在如果fieldMirror.subjectType是 an Optional<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
Run Code Online (Sandbox Code Playgroud)

这仅处理单个级别的可选嵌套,但可以通过简单地重复尝试将结果元类型值强制转换为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
Run Code Online (Sandbox Code Playgroud)