Reputation: 643
I initially posted this question as followup edit of this question here, but it seems sufficiently separate to stand as its own question. So here it is.
I'm trying validate that the values of a [String: Any]
dictionary are of the type that I expect. What I'd like to do is something like this:
func objectIsType<T>(object: Any, someObjectOfType: T.Type) -> Bool {
return object is T
}
let validator: [String: Any.Type] = [
"gimme an int": Int.self,
"this better be a string": String.self
]
let validatee: [String: Any] = [
"gimme an int": 3,
"this better be a string": "it is!"
]
for (key, type) in validator {
if !objectIsType(validatee[key], type) {
selfDestruct()
}
}
But I'm getting the error, <>protocol.Type is not convertible to T.type.
If, instead of using the objectIsType
function, I just use is
directly, making the loop
for (key, type) in validator {
if !(validatee[key] is type) {
selfDestruct()
}
}
I get the error, 'type' is not a type
. I've looked at the Swift Metatype documentation, but I'm still a bit confused. Is there a good way to do this type of thing?
Upvotes: 3
Views: 2224
Reputation: 40955
This approach isn’t going to work I’m afraid. The reason being, you are trying to mix dynamic run-time behaviour with static compile-time checks.
The function:
func objectIsType<T>(object: Any, someObjectOfType: T.Type) -> Bool {
return object is T
}
is using a kind of a hack/trick to do something that Swift doesn’t like doing, which is specifying the type for a generic placeholder without context. What does that mean? Well, really, what you wanted to do is this:
func objectIsType<T>(obj: Any) -> Book { return obj is T }
and then call it like this:
// See if myAny is an Integer...
objectIsType<Int>(myAny)
But in Swift (unlike in, say, C++), you can’t do this. You can’t tell the compiler “use Int
for the placeholder T
”. Swift will only let you determine T
from the context you’re calling it in. So instead, the trick you use is to fix T
by some other means. Here, you’re using the type’s metatype. An alternative would be to supply a dummy value that isn’t used (like in your original question).
func objectIsType<T>(object: Any, dummyValOfThatType: T) -> Bool {
// no use of the dummy value is made...
return object is T
}
// then, to detect Ints, supply a dummy Int
objectIsType(myAny, 1)
Obviously magicking up dummy values is a bit rubbish, hence the metatype solution feels better. But unfortunately, it’s given the illusion that there is a variable out there that can be used to refer to a run-time type, and can then be used with is
or as
. But this is just that, an illusion. You cannot do this with Swift’s basic types. They just don’t support it.
Bear in mind what happens when you call a generic function is that at compile time, Swift writes you a version of the function that you need for the type you are using it with. So when you supply an Int.self
, what actually happens is the compiler writes you this function:
objectIsType(obj: Any, someObjectOfType: Int.Type) -> Bool {
return obj is Int
}
I can’t stress this enough, it does this at compile time. So there is no way that this function can be called with a variety of different types determined at runtime and have it work. Instead, when you declare your dictionaries, you are declaring a dictionary full of values of Any.Type
. Since Any.Type
is a super type of all other types, you can still assign String.Type
to it, but really all you’ll be storing is Any.Type
. The protocol<>.Type
is actually another way of saying Any.Type
because that’s what Any
is: it’s defined as typealias Any = protocol<>
. Even if you did get your code to compile, it would always return true as what you’re asking is if objectIsType(Any, Any.Type)
(I’m not quite sure why it won’t, other than that you need to unwrap the value you’re getting out of the dictionary since it’s optional, but this ought to do it: if let obj = validatee[key] where !objectIsType(obj, type.self) { selfDestruct() }
)
Basically, if you want this kind of runtime dynamic type behaviour, you’re probably going to have to stick with @objc
land, unless someone else out there knows a good trick.
However, this is the point where I ask: what are you actually trying to achieve? Do you really need this kind of dynamic behaviour? Quite often, you can rephrase your actual goal in terms of compile-time generics instead of mucking around with converting Any
. Not always, but often.
Upvotes: 4