I have a function in Swift that returns Any? value and I would like to cast the result (based on the value type) to Bool, String or Int.
The function is this one:
static func value(forKey: SettingKeys) -> Any? {
return settingManager.settings.filter({$0.key == forKey.rawValue}).first?.value
}
and then when I do:
let printingEnabled = AppSettings().value(forKey:"printingEnabled") as? Int
i get NIL. Without the casting, the value is Optional("1"). Similar when I try casting results to String or Int. Any idea of how I could cast the result to a type of my choosing?
Thank you.
You don't want a function to return Any?. It is a nightmare of a type. Instead, you want to use generics to make sure the value is the type you want. For example:
static func value<T>(ofType: T.Type, forKey: SettingKeys) -> T? {
return settingManager.settings.filter({$0.key == forKey.rawValue}).first?.value as? T
}
Then this would be:
let printingEnabled = AppSettings().value(ofType: Int.self, forKey:"printingEnabled")
printingEnabled will in this case be Int?.
You can also, of course, wrap this up in helpers like:
static func int(forKey: SettingKeys) -> Int? {
return value(ofType: Int.self, forKey: forKey)
}
(BTW, while you definitely want to avoid Any? and use this kind of approach, I can't actually reproduce your issue. printingEnabled is Int? for me, and is Optional(1) as expected. I don't see any case where it's nil. But I can't make your code compile as written, either, since it's not clear what SettingKeys and settings are, so my code is probably significantly different than yours.)
Related
Assuming I have overloaded a Swift method like this
func someValue<T> ( _ name: PropertyName ) -> T? {
// ...
}
func someValue ( _ name: PropertyName ) -> URL? {
// ...
}
I can call this method like this
let u = o.someValue("propA")
And u will be of type URL?. Or I can also call it like this
let i: Int? = o.someValue("propB")
let s: String? = o.someValue("propC")
And i will be Int? and s will be String?.
But how can I use the result of this method call without assigning it to a variable? E.g. I want to use it for loop or maybe simpler, in a switch statement:
switch o.someValue("propB") {
This call will assume the result is URL?:
switch o.someValue("propB") as? Int {
I am told that URL? cannot be casted to Int, which is correct. But URL? means that already the wrong method is being called in the first place.
Update
Here is a minimal test case as I must not post the original code here.
https://swiftfiddle.com/srkys22vofdqlpl2sglnfs4q5u
switch o.someValue("propB") as Int? {
and
switch o.someValue("propB") as URL? {
work fine. You are mistaken in saying
Doesn't work
You may also disambiguate like
(o.someValue as (_ name: PropertyName) -> Int?)("propB")
(o.someValue as (_ name: PropertyName) -> URL?)("propB")
…though you should not have to.
switch o.someValue("propB") as? Int {
will not work as the compiler interprets that as
(switch o.someValue("propB")) as? Int {
in which case the URL? returning method wins, which is most specific overload, and then the case will fails as URL? cannot be cased to Int.
And then pointed out, that the correct code needs to be
switch o.someValue("propB") as Int? {
Note how the question mark is the only thing that moved here. As now the cast can match the generic version, too. This is basically hinted by the first line of Jessy's answer.
What is the easiest way to get the string-representation of a value's data type if that value is stored in an 'Any' variable?
For instance, I'm debugging code that has this...
extension SomeClass : Mappable{
static func map(value:Any) -> SomeClass{
return Parse(value)
}
}
I'm trying to figure out what data types are being passed through the function, but if I use type(of:) I keep getting 'Any' and not the value held in it.
extension SomeClass : Mappable{
static func map(value:Any) -> SomeClass{
let nameOfType = ??? <-- This is what I'm trying to figure out
log(nameOfType)
return Parse(value)
}
}
I simply want to print the data type to the debug window, not do testing with is or as, etc. It's strictly for logging/debugging reasons.
Ok, I figured it out. It's a two-step process.
You have to:
Use type(of:) to get the type of the variable (as others have described)
Use String(describing:) to get the name of that type (that was the missing piece)
Here's an example...
let typeName = String(describing: type(of:value))
That's what I was after. Thanks for the other answers. Hope this helps!
static func map(value:AnyObject) -> AnyClass{
return value.classForCoder
}
Or
static func map(value:Any) -> AnyClass{
return (value as AnyObject).classForCoder
}
In Swift 4 you can achieve that like this:
static func map(value: Any) -> Any.Type {
return type(of: value)
}
I'm working on a Swift version of a Keychain wrapper class. I'm a bit puzzled why this works:
private func executeFetch(query: KeyStoreObject) throws -> AnyObject? {
var result: AnyObject?
try executeQuery(query) { SecItemCopyMatching(query.data, &result) }
return result
}
And this doesn't:
private func executeFetch<T: AnyObject>(query: KeyStoreObject) throws -> T? {
var result: T?
try executeQuery(query) { SecItemCopyMatching(query.data, &result) }
return result
}
I believe that the error is that SecItemCopyMatching may attempt to assign anything of type AnyObject (i.e. anything at all) to result. However, in the second example result is not necessarily of type AnyObject; it is some particular type T that is a subclass of AnyObject. Thus, SecItemCopyMatching may be unable to correctly set result. For instance, what if T is Int, but SecItemCopyMatching wants to set result to a String? When result is of type AnyObject this is no longer an issue.
I'm wondering if I can do something like this:
override func someCocoaFunc(someParameter:AnyObject?) {
if let parameter = someParameter as! Tuple {
let parameterType = parameter.1
if parameterType == "Heads up, this is an Int" {
print(parameter.0 + 1) //prints 2
}
else {
//fallback
}
}
}
let myTuple = (1,"Heads up, this is an Int")
someCocoaFunc(myTuple)
Obviously this doesn't work because Tuple is not a class, or at least not one I can cast to anyway. Is there a way to get this to work, if so how? If not, what is the best way to determine the type of an AnyObject? I tried:
if parameter is Bool {
//some code
}
else if parameter is Int {
//some code
}
But it doesn't seem to work, I think because Bool is just a typealias'd Int or something? So to summarize, can I use Tuples here, and if not, what should I do? To add some context, I'm writing one class that is used in serval different targets, so I can't be sure what the value will be, just that it is either an Int or Bool.
You cannot use tuples here because they are compound types and therefore no classes.
The problem with the if is that for compatibility Bool and Int get converted to NSNumber that has a boolValue and integerValue property which gets called if you cast them. So 0 and 1 is equivalent to true and false and therefore ambiguous.
As solution I would suggest to make a class which holds both values as Optionals:
class Holder {
let boolValue: Bool?
let intValue: Int?
}
I would like to implement some code so that I can call something like:
NSUserDefaults("key1", "value1")
let s = NSUserDefaults("key1") // "value1" expected
NSUserDefaults("key2", 2.01)
let s = NSUserDefaults("key2") // 2.01 expected
I have some code in concept as below, but obviously it's not going to work. So my question is, instead of writing a series of functions like class func bool(key: String, _ v: Bool? = nil) -> Bool? is there any way to take the advantage of generic please?
extension NSUserDefaults {
class func object<T: AnyObject>(key: String, _ v: T? = nil) -> T? {
if let obj: T = v {
NSUserDefaults.standardUserDefaults().setObject(obj, forKey: key)
NSUserDefaults.standardUserDefaults().synchronize()
} else {
return NSUserDefaults.standardUserDefaults().objectForKey(key) as T?
}
return v
}
}
Your syntax is going to wind up being very poor. This line can't work as written:
let s = NSUserDefaults("key1") // "value1" expected
Swift has to pick a type for s at compile time, not run time. So the only type it can assign here is Any (not even AnyObject is expansive enough if you want to return Double since Double is not AnyObject).
That means you have to explicitly call out let s : Any = ... (because Swift wisely won't let you create Any implicitly), and then you're going to wind up with an Any that you have to type-check somehow. When you're done, you're going to come full circle to objectForKey().
Even if you could get this syntax working, you shouldn't try to overload a single function syntax to do opposite things. That's very confusing. If you were going to build an extension like this, you should probably make it a subscript. That way you'd say defaults["key1"] and defaults["key2"] = 2.01. That's something may be able to build (though there will still be type annotation headaches required to deal with AnyObject?).