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

时间:2017-03-12 19:46:33

标签: 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 实际上是 可选< 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

如何解包 optionalField 属性的类型(而非值),以便我可以将其与协议 CustomProtocol 相关联?

换句话说,如何从 Optional&lt; 获取包装的 AnotherClass AnotherClass&gt; 类型?

限制:

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

2 个答案:

答案 0 :(得分:4)

我不相信这是一个简单的方法,因为我们目前无法在没有占位符的情况下谈论泛型类型 - 因此我们不能简单地转换为Optional.Type

我们也不能转发Optional<Any>.Type,因为编译器不为它为实例提供的元类型值提供相同类型的自动转换(例如,Optional<Int>可转换为{ {1}},但Optional<Any>无法转换为Optional<Int>.Type)。

然而,一个解决方案,虽然有点笨拙,但是要定义一个虚拟协议&#39;表示任何Optional<Any>.Type实例&#39;,无论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.subjectType,我们可以将其转换为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 并获取{{1}来适应任意可选嵌套级别。 },然后检查OptionalProtocol.Type一致性。

wrappedType

答案 1 :(得分:1)

这是一个有趣的问题,但是在摆弄了一段时间之后,我之前认为(并且纠正了错误)使用原生的Swift无法解决这个问题,然而,这可能是{{3 }}

目标

我们希望在运行时有条件地访问包含在Wrapped中的实例的Optional<Wrapped>类型(Any),而不会实际知道Wrapped,只知道Wrapped 1}}可能符合某些协议;在您的示例CustomProtocol中。

(并非不可克服的)障碍

阻碍我们找到这个内省问题的解决方案有一些障碍,即在运行时测试Optional<Wrapped>的实例本身是否包含在Any的实例中,保持符合给定协议的类型Wrapped(其中Wrapped未知)。具体而言,阻碍我们从一个可行的通用解决方案,即使对于被内省的值恰好是Optional<Wrapped>.none 的情况。

正如你的问题中已经提到的那样,第一个问题是包含在Any个实例中的选项不是协变的(选项本身是协变的,但在特殊情况下也适用于例如某些集合,而对于自定义包装类型非协方差的默认行为成立)。因此,即使Any本身符合Optional<MyProtocol>,我们也无法成功测试Wrapped中包含的类型与MyProtocol的可选级别的一致性。

protocol Dummy {}
extension Int : Dummy {}

let foo: Int? = nil
let bar = foo as Any

if type(of: bar) is Optional<Int>.Type {
    // OK, we enter here, but here we've assumed that we actually
    // know the type of 'Wrapped' (Int) at compile time!
}

if type(of: bar) is Optional<Dummy>.Type {
    // fails to enter as optionals wrapped in 'Any' are not covariant ...
}

第二个问题有些重叠:我们可能不会将包含可选项的Any实例直接转换为可选类型,或者(通过非协方差)转换为包装类型符合的可选类型的协议。 E.g:

let foo: Int? = 1
let bar = foo as Any
let baz = bar as? Optional<Int>
// error: cannot downcast from 'Any' to a more optional type 'Optional<Int>'
let dummy = bar as? Optional<Dummy>
// error: cannot downcast from 'Any' to a more optional type 'Optional<Dummy>'

现在,我们可以使用值绑定模式来避免这种情况:

protocol Dummy {}
extension Int : Dummy {}

let foo: Int? = 1
let bar = foo as Any
if case Optional<Any>.some(let baz) = bar {
    // ok, this is great, 'baz' is now a concrete 'Wrapped' instance,
    // in turn wrapped in 'Any': but fo this case, we can  test if 
    // 'baz' conforms to dummy!
    print(baz)          // 1
    print(baz is Dummy) // true <--- this would be the OP's end goal
}

// ... but what if 'bar' is wrapping Optional<Int>.none ?

但这只是一种解决方法,如果上面的foo不是nil,有帮助,而如果foonil,我们就没有绑定的实例可以执行类型&amp;协议一致性分析。

protocol Dummy {}
extension Int : Dummy {}

let foo: Int? = nil
let bar = foo as Any
if case Optional<Any>.none = bar {
    // ok, so we know that bar indeed wraps an optional,
    // and that this optional happens to be 'nil', but
    // we have no way of telling the compiler to work further
    // with the actual 'Wrapped' type, as we have no concrete
    // 'Wrapped' value to bind to an instance.
}

我一直在玩几种不同的方法,但最后我回到了nil包含的可选Any值实例的问题,访问{{ 1}}(不知道它:例如作为元类型)似乎是不可能的。但是,如@Hamish:s answer所示,这确实不是不可克服的,可以通过在 Wrapped之上添加一个额外的协议层来解决。

然而,我将完成上述不完整的尝试,因为这些技巧和讨论对于这个主题的读者可能是有益的,即使他们没有设法解决问题