I am looking for best way how can I make this code to one line:
if (dictionary["Amoumt"] is Double) {
amount = dictionary["Amount"] as Double
} else {
amount = NSString(string: dictionary["Amount"] as String).doubleValue
}
I have Dictionary<String, AnyObject> and I would like to parse values from it. I am using code like above but it's too many lines. I would like to make it to one line. Something like:
dictionary["Amount"].parseDouble()
There is no problem to create extension with this method:
func parseDouble() -> Double {
if (self is Double) {
return self as Double
} else {
return NSString(string:(self as String)).doubleValue
}
}
But which type should I extend? Next could you help me with generic method? So I could call something like this:
dictionary["Amount"].parse(Double)
And is this good way how to do this or should I do it another way?
You can use AnyObject as is. Try:
var dictionary:[String:AnyObject] = [
"foo": 4.21, // NSNumber
"bar": "42.5", // NSString
"baz": [1,2,3], // NSArray
]
let foo = dictionary["foo"]?.doubleValue ?? 0 // -> 4.21
let bar = dictionary["bar"]?.doubleValue ?? 0 // -> 42.5
let baz = dictionary["baz"]?.doubleValue ?? 0 // -> 0.0
This works because both NSNumber and NSString have .doubleValue property.
On the other hand, NSArray does not have that property, in this case it returns nil.
As described in the document:
You can also call any Objective-C method and access any property without casting to a more specific class type. This includes Objective-C compatible methods marked with the #objc attribute.
Maybe you are looking for this kind of extenison:
extension Dictionary{
func parseDouble(key:Key) -> Double {
let result = self[key]
if (result is Double) {
return result as Double
} else {
return NSString(string:(result as String)).doubleValue
}
}
}
And getting value for key by using this:
var doubleValue = dictionary.parseDouble("amount")
And a generic function
extension Dictionary{
func parse<T>(key:Key) -> T? {
let result = self[key]
if (result is T) {
return result as? T
}
return nil
}
}
var doubleValue:Double? = myDistionary.parse("someKeyForDouble")
var stringValue:String? = myDistionary.parse("someKeyForString")
The easiest way to condense this is to use the nil coalescing operator and an optional double. This attempts to cast the value as a double. If it succeeds, it unwraps it, if it fails, it defaults to the second value, which comes from converting `dict[amount]' to a string and taking its double value.
var amount: Double? = nil
let dict: [String: AnyObject] = ["amount" : "1.2"]
amount = (dict["amount"] as? Double) ?? NSString(string: dict["amount"] as! String).doubleValue
And to make your function:
func getDouble(obj: AnyObject) -> Double {
return (obj as? Double) ?? NSString(string: obj as! String).doubleValue
}
and you call it using getDouble(dict["amount"]). An extension for something like this is probably overkill in my opinion.
If you wanted an extension, the best place to put it in theory would be AnyObject – because it is an AnyObject that you want to convert.
But AnyObject is actually a protocol, so you can't extend it.
I wouldn't recommend putting it as an extension to Dictionary – the coercing of a type into a double is not really anything to do with dictionaries.
So the best approach is to do it as a free function not as an extension:
func getDouble(obj: AnyObject?) -> Double {
return (obj as? Double) ?? (obj as? NSString)?.doubleValue ?? 0
}
asDouble(dictionary["Amount"])
Note, this function is safe if you pass it a function that is neither a double nor a string. Other solutions using as or ! instead of as? will crash at runtime if you ever pass something else in.
You could argue it should return an optional, with nil if the value was not convertible to a double. This is what String.toInt() does. Unfortunately, the limitation of NSString.doubleValue is that it doesn't do this – it defaults to zero instead – so you can't combine these two approaches.
Related
I am trying to associated a property to an Array Extension:
private var AssociatedObjectHandle: String = "BlaBLabla"
extension Array {
var emptyIndex:Int {
mutating get {
if let object = objc_getAssociatedObject(self, &AssociatedObjectHandle) {
return object as! Int
}
let index = self.searchEmptyIndex()
self.emptyIndex = index
return index
}
set {
let new = (newValue as NSInteger)
objc_setAssociatedObject(self, &AssociatedObjectHandle, new, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
func searchEmptyIndex() -> Int {
if let arr = self as? [Int] {
return arr.index(of: -1)!
}
return -1
}
}
The objc_getAssociatedObject call always returns nil!!
Anyone has any idea why?
I am banging my head for the last hour about this...
You cannot add associated objects to a Swift Array (or any Swift value type).
objc_setAssociatedObject() and objc_getAssociatedObject() are from the
Objective-C runtime, they expect an instance of NSObject as first
argument.
Your code compiles and runs only because any Swift value is automatically
bridged to an object if necessary.
When you call objc_setAssociatedObject(self, ...) then self
is bridged to a (temporary) instance of NSArray, and the association
is made on that object.
Later, when objc_getAssociatedObject(self, ...) is called,
another (temporary) instance of NSArray is created, and that
has no associated object.
That's why you get nil as the result.
Everyone knows that ios making support for "performSelector" in swift2.0
I want to know how can get the performSelector return value?
let returnValue:Unmanaged<AnyObject> = (self.dataSource?.performSelector("myselector:", withObject: self))!
let value : Int? = returnValue.takeRetainedValue() as? Int
There is a crash at the last line, How can I get the value from "performSelector" in swift2.0?
I think currently the performSelector in Swift only supports return value conforming to AnyObject, in your case you have Int which does not. In this case, performSelector will return nil, and your code is explicitly unwrapping the Optional making the program crash.
So one way to work around this is to make a method that returns NSNumber.
You can try this code in playground:
#objc class B: NSObject {
func myselector(arg: A) -> NSNumber {
return 0
}
}
#objc class A: NSObject {
var dataSource: B? = B()
func a() -> Int {
let returnValue = self.dataSource?.performSelector("myselector:", withObject: self)
let value = returnValue?.takeRetainedValue() as! NSNumber
return value.integerValue
}
}
A().a()
Use takeUnretainedValue:
let foo = Foo()
let value = foo.performSelector(Selector("bar")) //type of value is Unmanaged<AnyObject?>
let uvalue = value.takeUnretainedValue() //type of uvalue is now AnyObject?
let result = value as? String //type of result is now String
Updated for Swift 5:
also works with NSSelectorFromString: say you have a UIViewController called aViewController.
let value = aViewController.perform(NSSelectorFromString("view")) //type of value is Unmanaged<AnyObject?>
let uvalue = value.takeUnretainedValue() //type of uvalue is now AnyObject?
let myView = value as? UIView //type of result is now UIView
You can get NSInteger result of performSelector by the following way
let returnValue = dataSource?.perform(#selector(myselector:), withObject: self)!
let value = Int(bitPattern: returnValue.toOpaque())
So I'm writing an app that uses the Argo library to parse our JSON. Works pretty well. However, I'm trying to find a way to cache the parsed types we get back. They're all implemented as structs. I tried something like this:
struct CachedItem <T: Decodable where T == T.DecodedType> {
let value: T
let cachedTime: NSTimeInterval
init(value: T) {
self.value = value
cachedTime = NSDate().timeIntervalSince1970
}
func isExpired() -> Bool {
let currentTime = NSDate().timeIntervalSince1970
return ((currentTime - cachedTime) > 20.minutes)
}
}
However, attempting to create a cache like this:
var cache: [NSURL : CachedItem]
fails with the error: "reference to generic type 'CachedItem' requires arguments in <...>", which I understand to mean I need to do something like this:
var cache: [NSURL : CachedItem<Item>]
Is there any way I can get what I want here? Or any other suggestions for caching generic types that aren't Objective-C based classes.
Edit:
For posterity, here's the Cache and CacheItem types I came up with after Rob's answer.
struct Cache {
private var cache: [String : CachedValue] = [:]
private let queue = dispatch_queue_create("Cache Queue", DISPATCH_QUEUE_SERIAL)
mutating func setValue(value: Any?, forType type: String) {
dispatch_sync(queue) {
guard let value = value else {
return
}
self.cache[type] = CachedValue(value: value)
}
}
func valueForType<T>(type: String) -> T? {
var result: T?
dispatch_sync(queue) {
guard let cachedValue = self.cache[type] where !cachedValue.isExpired() else {
result = .None
return
}
result = cachedValue.value as? T
}
return result
}
}
struct CachedValue {
let value: Any
private let cachedTime: NSTimeInterval
init(value: Any) {
self.value = value
cachedTime = NSDate().timeIntervalSince1970
}
func isExpired() -> Bool {
let currentTime = NSDate().timeIntervalSince1970
return ((currentTime - cachedTime) > 1.minutes)
}
}
Basically, all you can say about the types in this cache are that they're Decodable, and that really isn't telling you anything useful since they're already decoded. That leaves AnyObject. If this really is a "cache of anything", then [NSURL: AnyObject] or even [NSURL: Any] is appropriate. This puts a lot of ugliness on the consuming side to figure out its type, but that's basically the identical ugliness that you had to have used to parse the JSON (which fundamentally works with AnyObject).
Seldom do I suggest AnyObject, but in this case, it's probably appropriate if you really want one big cache (rather than a cache for each type, which would be preferable if possible).
With the following code I try to define a simple model class and it's failable initializer, which takes a (json-) dictionary as parameter. The initializer should return nil if the user name is not defined in the original json.
1.
Why doesn't the code compile? The error message says:
All stored properties of a class instance must be initialized before returning nil from an initializer.
That doesn't make sense. Why should I initialize those properties when I plan to return nil?
2.
Is my approach the right one or would there be other ideas or common patterns to achieve my goal?
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
if let value: String = dictionary["user_name"] as? String {
userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
}
}
That doesn't make sense. Why should I initialize those properties when
I plan to return nil?
According to Chris Lattner this is a bug. Here is what he says:
This is an implementation limitation in the swift 1.1 compiler,
documented in the release notes. The compiler is currently unable to
destroy partially initialized classes in all cases, so it disallows
formation of a situation where it would have to. We consider this a
bug to be fixed in future releases, not a feature.
Source
EDIT:
So swift is now open source and according to this changelog it is fixed now in snapshots of swift 2.2
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
Update: From the Swift 2.2 Change Log (released March 21, 2016):
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
For Swift 2.1 and earlier:
According to Apple's documentation (and your compiler error), a class must initialize all its stored properties before returning nil from a failable initializer:
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Note: It actually works fine for structures and enumerations, just not classes.
The suggested way to handle stored properties that can't be initialized before the initializer fails is to declare them as implicitly unwrapped optionals.
Example from the docs:
class Product {
let name: String!
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
In the example above, the name property of the Product class is
defined as having an implicitly unwrapped optional string type
(String!). Because it is of an optional type, this means that the name
property has a default value of nil before it is assigned a specific
value during initialization. This default value of nil in turn means
that all of the properties introduced by the Product class have a
valid initial value. As a result, the failable initializer for Product
can trigger an initialization failure at the start of the initializer
if it is passed an empty string, before assigning a specific value to
the name property within the initializer.
In your case, however, simply defining userName as a String! does not fix the compile error because you still need to worry about initializing the properties on your base class, NSObject. Luckily, with userName defined as a String!, you can actually call super.init() before you return nil which will init your NSObject base class and fix the compile error.
class User: NSObject {
let userName: String!
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
super.init()
if let value = dictionary["user_name"] as? String {
self.userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
self.isSuperUser = value
}
self.someDetails = dictionary["some_details"] as? Array
}
}
I accept that Mike S's answer is Apple's recommendation, but I don't think it's best practice. The whole point of a strong type system is to move runtime errors to compile time. This "solution" defeats that purpose. IMHO, better would be to go ahead and initialize the username to "" and then check it after the super.init(). If blank userNames are allowed, then set a flag.
class User: NSObject {
let userName: String = ""
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: [String: AnyObject]) {
if let user_name = dictionary["user_name"] as? String {
userName = user_name
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
if userName.isEmpty {
return nil
}
}
}
Another way to circumvent the limitation is to work with a class-functions to do the initialisation.
You might even want to move that function to an extension:
class User: NSObject {
let username: String
let isSuperUser: Bool
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
super.init()
}
}
extension User {
class func fromDictionary(dictionary: NSDictionary) -> User? {
if let username: String = dictionary["user_name"] as? String {
let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
let someDetails = dictionary["some_details"] as? [String]
return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
}
return nil
}
}
Using it would become:
if let user = User.fromDictionary(someDict) {
// Party hard
}
Although Swift 2.2 has been released and you no longer have to fully initialize the object before failing the initializer, you need to hold your horses until https://bugs.swift.org/browse/SR-704 is fixed.
I found out this can be done in Swift 1.2
There are some conditions:
Required properties should be declared as implicitly unwrapped optionals
Assign a value to your required properties exactly once. This value may be nil.
Then call super.init() if your class is inheriting from another class.
After all your required properties have been assigned a value, check if their value is as expected. If not, return nil.
Example:
class ClassName: NSObject {
let property: String!
init?(propertyValue: String?) {
self.property = propertyValue
super.init()
if self.property == nil {
return nil
}
}
}
A failable initializer for a value type (that is, a structure or
enumeration) can trigger an initialization failure at any point within
its initializer implementation
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/sg/jEUH0.l
You can use convenience init:
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
}
convenience init? (dict: NSDictionary) {
guard let userName = dictionary["user_name"] as? String else { return nil }
guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
guard let someDetails = dictionary["some_details"] as? [String] else { return nil }
self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
}
}
Code:
class User
{
class var BoolProperty: Bool
{
get {
var anyObject: AnyObject? = getValue("BoolProperty")
if let value = anyObject as? Bool {
return value
}
else {
return false
}
}
set(value) {
setValue("BoolProperty", value: value)
}
}
private class func getValue(key: String) -> AnyObject?
{
var store = NSUserDefaults.standardUserDefaults();
return store.objectForKey(key) as AnyObject?
}
}
passes the test:
class UserTests: XCTestCase
{
func testFields()
{
User.BoolProperty = true
var result = User.BoolProperty
XCTAssertEqual(true, result)
}
}
but the following code doesn't pass the same test, which uses T instead of Bool for casting:
class User
{
class var BoolProperty: Bool
{
get {
return get("BoolProperty", defaultValue: false)
}
set(value) {
setValue("BoolProperty", value: value)
}
}
private class func getValue(key: String) -> AnyObject?
{
var store = NSUserDefaults.standardUserDefaults();
return store.objectForKey(key) as AnyObject?
}
private class func get<T>(key: String, defaultValue: T) -> T
{
var anyObject: AnyObject? = getValue(key)
if let value = anyObject as? T {
return value
}
else {
return defaultValue
}
}
}
it seems, that for some reason if let value = anyObject as? T always returns false when casting to T.
In C# this is a classic example of working with untyped collections and I was wondering what's the right approach to achieve the same in Swift.
The problem is that an NSNumber is not a Bool. It looks like a Bool, and it can be converted to a Bool, but it's really a different type. Your anyObject is really an NSNumber, and asking for it to be as? T where T is Bool is too far. That's still likely a compiler bug, because it should be the same as a generic as without, but it's where the problem is happening.
You need to specialize the generic function to NSNumber:
get {
return Bool(get("BoolProperty", defaultValue: NSNumber(bool: false)))
}
It's still probably worth opening a radar. It shouldn't behave differently with and without the generic.
That said, part of the trouble is the use of AnyObject. A Bool is not an AnyObject. It's an Any. When you try to assign it to an AnyObject, it gets converted into an NSNumber. I don't know of any documentation of this; I've worked it out empirically in playgrounds. Compare the results of let a:AnyObject = false and let a:Any = false.
In theory, this should fix it all:
var anyObject: Any? = getValue(key)
But it currently crashes the compiler.