I'm trying to use reflection to collect all the properties on an object of a certain type. I've mostly got it working, but I can't quite figure out how to take lazy properties into account. Any suggestions?
Here is some code to show what I mean.
Mirror.children.count returns 3 indicating it's aware of the uninitialized lazy vars. So I created a function to count the number of Int properties. Notice how it returns 1 instead of 2 until the lazy var is accessed. I guess that makes sense. Question is, how can I modify my function to count those lazy vars as well?
class MyObject {
let str: String = "this is a string"
let number: Int = 5
lazy var lazyNumber: Int = 42
}
func countIntChildren(_ children: Mirror.Children) -> Int {
var total = 0
for child in children {
switch child.value {
case let optionalObj as Optional<Any>:
switch optionalObj {
case .some(let innerObj):
if innerObj is Int {
total += 1
}
case .none:
()
}
}
}
return total
}
let obj = MyObject()
let objMirror = Mirror(reflecting: obj)
print(objMirror.children.count) // prints 3
print(countIntChildren(objMirror.children)) // prints 1, skipping my lazy var int
print(obj.lazyNumber) // accessing the lazy var
print(countIntChildren(objMirror.children)) // prints 2
I cannot check that child.value is Optional<Int> because I get: Cannot downcast from 'Any' to a more optional type 'Optional<Int>' error.
It seems if I continue with the path I have now, (checking if it's Optional, and then switching on the optional enum cases won't do either, because the .none case will get it and I'll know I have a lazy var, but not what its type is.
If you decide to count both Int and Int?, you can do:
func countIntChildren(_ children: Mirror.Children) -> Int {
return children.lazy.filter {
let value = $0.value
return value is Int || type(of: value) == Int?.self
} .count
}
Due to the way is interact, you can’t just check if value is Int? since it will erraneously include String?.none, that’s why I have it as a separate case.
Caveat is that it won’t include Int?? and its family, but I can’t think of a way to nicely unwrap arbitrary number of wrapping.
Now to complicate things a bit, instead of just counting lazy ‘Int’ properties, I want to create an array holding all the lazy properties who conform to a protocol. Having trouble using as? To cast Child.value to my protocol. Any suggestions?
It seems checking for Protocol? is much more elusive compared to concrete types:
protocol Test { }
extension Int: Test { }
let a: Int? = 3
let anyA: Any = a
a is Test // true
a as? Test // 3
type(of: a) == Test?.self // false
anyA is Test // false
anyA as? Test // nil
I’m unsure if Swift provides enough metatype gymnastics to achieve what you want. And if the Protocol has associatedtypes, it’s much less likely—you can’t even express its metatype properly.