我有一个包含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; 。
答案 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
,有帮助,而如果foo
是nil
,我们就没有绑定的实例可以执行类型&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
之上添加一个额外的协议层来解决。
然而,我将完成上述不完整的尝试,因为这些技巧和讨论对于这个主题的读者可能是有益的,即使他们没有设法解决问题