Optional field type doesn't conform protocol in Swift 3

后端 未结 2 1272
梦谈多话
梦谈多话 2020-12-10 05:33

I have a class with 1 optional field and 1 non-optional field, both of them with Type AnotherClass and also conform CustomProtocol

2条回答
  •  星月不相逢
    2020-12-10 06:16

    I do not believe there's a simple way to do this, given that we currently cannot talk in terms of generic types without their placeholders – therefore we cannot simply cast to Optional.Type.

    Nor can we cast to Optional.Type, because the compiler doesn't provide the same kinds of automatic conversions for metatype values that it provides for instances (e.g An Optional is convertible to an Optional, but an Optional.Type is not convertible to a Optional.Type).

    However one solution, albeit a somewhat hacky one, would be to define a 'dummy protocol' to represent an 'any Optional instance', regardless of the Wrapped type. We can then have this protocol define a wrappedType requirement in order to get the Wrapped metatype value for the given Optional type.

    For example:

    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 }
    }
    

    Now if fieldMirror.subjectType is an Optional.Type, we can cast it to OptionalProtocol.Type, and from there get the wrappedType metatype value. This then lets us check for CustomProtocol conformance.

    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.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 and conforms CustomProtocol
    

    This only deals with a single level of optional nesting, but could easily be adapted to apply to an arbitrary optional nesting level through simply repeatedly attempting to cast the resultant metatype value to OptionalProtocol.Type and getting the wrappedType, and then checking for CustomProtocol conformance.

    class CustomClass : CustomProtocol {
        var nonoptionalField: AnotherClass = AnotherClass()
        var optionalField: AnotherClass??
        var str: String = ""
    }
    
    /// If `type` is an `Optional` 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> and conforms CustomProtocol
    // str is String and DOES NOT conform CustomProtocol
    

提交回复
热议问题