I want to convert a type defined as RealmOptional to either Int or Float, depending on the type assigned to the generic. However, when I tried distributing them using switch statement, it turned out that not RealmOptional<Int>() but RealmOptional<Float>() were classified to Int cases. For example,
switch value { // value is of type AnyObject?
case is String:
cell.valueLabel.text = value as? String
case is Int:
// RealmOptional<Float> are executed here
let v = value as! Int
cell.valueLabel.text = String(v) // Float is now treated as Int
case is Double:
print("double") // not printed at all
cell.valueLabel.text = String(value!)
default:
break
}
Why does RealmOptional<Float>() behave as Int here? And how can I set the text to the value correctly?
Lets assume your RealmOptional<Float>() variable is named myFloat. Then, use the getter for the RealmOptional:s (see this git entry for Realm) underlying value, .value, rather then checking the RealmOptional itself:
var value = myFloat.value // value variable now of type Float
Below follows and explanation of why the AnyObject? switches doesn't behave as you expect:
From Apples Language Guide - Type Casting:
Type Casting for Any and AnyObject
Swift provides two special type aliases for working with non-specific
types:
AnyObject can represent an instance of any class type.
Any can represent an instance of any type at all, including function types.
Hence, the AnyObject type can can hold instances of any class type, but the fundamental numeric types (Int, Double etc) in Swift are not of class type, but of structure type.
In the switch in your example, the AnyObject instance is not inferred but cast to the first possible successful downcast, which will be whatever case you put top-most that is of a numeric type. Hence, if you change the ordering of your case:s in the switch, the casting will change.
let value: AnyObject? = 30.0
// try change the order
switch value {
case is String: print("string");
case is Float: print("float"); // hits `Float` downcast first -> prints float
case is Int: print("int");
default: print("other")
}
Now, you could, however, cast your AnyObject to an NSNumber (class instance), and continue from there. From Working with Cocoa Data Types documentation:
Instances of the Swift numeric structure types, such as Int, UInt,
Float, Double, and Bool, cannot be represented by the AnyObject type,
because AnyObject only represents instances of a class type. However,
when bridging to Foundation is enabled, Swift numeric values can be
assigned to constants and variables of AnyObject type as bridged
instances of the NSNumber class.
Note however that NSNumber is fundamentally different from the Swift numeric types (Int, Double etc) in that the former hold any kind of number, and allows us to cast this number to different type of numeric types. However, we cannot easily infer which type a specific NSNumber instance should be cast to, but we could implement our own (not so pretty) specification as how to infer different NSNumber to different swift fundamental numeric types.
However, before proceeding with NSNumber hacking:
From above, the core problem is that your value property is of type AnyObject?. Could you please post the code that lead to value being of type AnyObject?. Possibly the casting to Int, Float and so on if not necessary if using the getter of the RealmOptional (hence no need to casting RealmOptional<T>() to AnyObject??).
I found that this is not related to Realm; it is related to Swift. In Swift when you pass a floating object in switch, it is not only caught up in is Float but also in is Int, because the floating number can be convertible to Int as well.
For example, the code below does not go through is Float condition, because it is caught up in is Int:
let value: AnyObject? = 30.0
switch value {
case is String: print("string");
case is Int: print("int");
case is Float: print("float");
default: print("other")
}
That is why RealmOptional<Float>() is caught up in is Int.
I still don't know how I can cope with the issue here, because an object of type Int is caught up in is Float and an object of type Float is caught up in is Int. Maybe someone can chime in...
Related
I tried for a long time to turn the text into an Int but it did not work. I tried it like this:
(AnzahlString is a textfield)
var AnzahlAInt = 0
if let AnzahlAString = AnzahlString.text {
let AnzahlAInt = Int(AnzahlAString)
}
But then I always get the error:
Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
Then I added a ! at the end of Int(AnzahlAString)! so I don't get a error, but now when I press on the button, the app crashes. It was predictable, but how can I change this now to an Int without the !?
At first glance, it looks like you have two things to check for:
is AnzahlString.text present, and
does it represent an Int
The first check is in fact not necessary, since .text will never return nil, even though it's marked as Optional. This means you can safely force-unwrap it.
The second check is easily done by using the ?? operator:
let AnzahlAInt = Int(AnzahlString.text!) ?? 0
PS, just as a stylistic hint: variable names in Swift ususally start with a lowercase letter, names starting with capital letters are used for types.
PPS: your code as written shadows AnzahlAInt - the value of your var is never changed.
The reason why the resulting Int is optional, is that parsing might or might not succeed. For example, if you try to parse the string "Fluffy Bunnies" into an Int, there is no reasonable Int that can be returned, therefore the result of parsing that string will be nil.
Furthermore, if you force the parser by using !, you're telling Swift that you know for sure that the string you pass will always result in a valid Int, and when it doesn't, the app crashes.
You need to handle the situation in which the parse result is nil. For example:
if let AnzahlAIntResult = Int(AnzahlAString) {
// We only get here if the parse was successful and we have an Int.
// AnzahlAIntResult is now an Int, so it can be assigned to AnzahlAInt.
AnzahlAInt = AnzahlAIntResult
}
You did a good job so far but missed out one thing.
This line tries to convert the String into an Int. However this can fail, since your String can be something like this "dfhuse".
let AnzahlAInt = Int(AnzahlAString)
This is why the result of Int(AnzahlAString) is an Optional (Int?). To use it as an real Int, you have to unwrap it.
First solution is the !, however, every time this does fail your app crashes. Not a good Idea to use so.
The best solution would be Optional Binding, as you already used to get the text of your text field.
if let AnzahlAString = AnzahlString.text {
if let safeInt = Int(AnzahlAString) {
// You can use safeInt as a real Int
} else {
print("Converting your String to an Int failed badly!")
}
}
Hope this helps you. Feel free to ask again if something is unclear.
For unwrapping you can also use guard like this
Simple and easy
guard let AnzahlAInt = Int(AnzahlString.text!) else {
return
}
print(AnzahlAInt)
let sum1 = {(a:Int, b:Int) -> Int in return a + b}
let sum2 = {(a:Float, b:Float) -> Float in return a + b}
var cl = [sum1, sum2]
Why sum1 and sum2 can't be added to an array?
I know sum1 takes two Ints return an Int, sum2 takes two Floats return a Float, but an array can be added two different type of object, for example let foo = [12, "12"] is valid.
The suggestions that you use [Any] for this will compile, but if you need Any (or AnyObject) in a property, you almost always have a design mistake. Any exists for rare cases where you really must circumvent the type system, but you will typically run into numerous headaches with it.
There are numerous type-safe solutions to these kinds of problems depending on what your underlying goal really is. The most likely solution, if your goal is to keep Int-methods separate from Float-methods, is to use an enum:
enum Operator {
case int((Int, Int) -> Int)
case float((Float, Float) -> Float)
}
let sum1 = Operator.int {(a, b) in return a + b }
let sum2 = Operator.float {(a, b) in return a + b}
let cl = [sumop1, sumop2]
Any means you can throw absolutely anything in it and it's your problem to figure out what to do with the random things you find in it. The compiler can't help you, and in some cases will actively fight you. You'll need to accept undefined behavior or add precondition all over the place to check at runtime that you didn't make a mistake.
Operator is an "OR" type. [Operator] is an array of functions that either operate on Ints or Floats. This seems what you mean, so let the compiler help you by telling it that's what you mean. The compiler will detect mistakes at compile time rather than crashing at runtime. Many unit tests become unnecessary because whole classes of bugs are impossible. With a proper type and the compiler's help, you can simplify cl to:
let cl: [Operator] = [.int(+), .float(+)]
That's pretty nice IMO.
On the other hand, if the goal is to accept both Ints and Floats, then you should likely wrap them up in NSNumber which can work on both. If you want to keep track of which were Ints and which were Floats so you can apply your math more carefully, you can create a struct:
struct Number {
enum Kind {
case Int
case Float
}
let value: NSNumber
let type: Kind
}
Reason is that each closure represents new type, and if your array is not of type Any or AnyObject than it can have inside only elements of one type. That's why you can't place them as easily inside. For Closures you need to use type Any, because closures is structure type.
I'm receiving a JSON and I want to parse it to get a value. I'm doing this
let ddd = oneJson["restaurantId"] as! Int
print("ddd = \(ddd)")
let restaurantId = oneJson["restaurantId"] as! Int64
as you see, I'm parsing the same field. Here's my JSON
"location":"location here location","restaurantId":0
The print statement works just fine, but I get an exception on oneJson["restaurantId"] as! Int64
I love this quirk in swift (NOT).
It's one of the least intuitive gotchas of the language I know of. So it turns out that when you get a Dictionary with type AnyObject, Ints, Doubles, Floats, ARE NOT stored as the Swift native types. They're stored as... surprise! NSNumber.
Which leads to a whole host of unintuitive behavior, for instance type checking AnyObjects to see whether you have a Double or an Int (it can't be done).
For the same reason, your code is failing. Change it to:
let ddd = oneJson["restaurantId"] as! Int
print("ddd = \(ddd)")
let restaurantId = (oneJson["restaurantId"] as? NSNumber)?.longLongValue
And remind yourself again and again that when it's an AnyObject you're casting from, Swift is hiding from you the fact that it does a cast from NSNumber to Swift base types, and that in truth, they're still just NSNumbers.
I would recommend, not to use Int64 (or Int32). Int will be working in most cases.
See this post about the different integers in Swift: https://stackoverflow.com/a/27440158/4687348
Yes, it's a known bug in Swift 3, which has been solved in Swift 4.
Now, you just write like this,
let n = NSNumber.init(value: 9223372036854775807) // 2^63 - 1
print(n, n as! Int64) // will print the right answer.
I need to cast an int to an object, in Objective-C I could do the following
[row.cellConfig setObject:#(UITextFieldViewModeAlways) forKey:#"textField.rightViewMode"];
What would be the Swift equivalent?
The Swift equivalent of UITextFieldViewModeAlways is
UITextFieldViewMode.Always, which is an enumeration value:
enum UITextFieldViewMode : Int {
case Never
case WhileEditing
case UnlessEditing
case Always
}
You get its underlying integer value with .rawValue.
Integers are automatically "bridged" to NSNumber when passed
to functions taking Objective-C parameters (and Swift strings
bridged to NSString).
So this should work:
row.cellConfig.setObject(UITextFieldViewMode.Always.rawValue,
forKey: "textField.rightViewMode")
For more information, see Using Swift with Cocoa and Objective-C.
Use
row.cellConfig.setObject(NSNumber(UITextFieldViewModeAlways), forKey:"textField.rightViewMode")
You need to create a NSNumber object for that int.
NSNumber *intObj = [NSNumber numberWithInt:num];
I have a Swift project that contains two UITableViewControllers. The second UITableViewController is linked to a MVC model called Model. According to the UITableViewCell I select in the first UITableViewController, I want to initialize some properties of Model with Ints or Strings. Therefore, I've decided to define those properties with Printable protocol type. In the same time, I want to perform Key Value Observing on one of these properties.
Right now, Model looks like this:
class Model: NSObject {
let title: String
let array: [Printable]
dynamic var selectedValue: Printable //error message
init(title: String, array: [Printable], selectedValue: Printable) {
self.title = title
self.array = array
self.selectedValue = selectedValue
}
}
The problem here is that the following error message appears on the selectedValue declaration line:
Property cannot be marked dynamic because its type cannot be
represented in Objective-C
If I go to the Xcode Issue Navigator, I can also read the following line:
Protocol 'Printable' is not '#objc'
Is there any workaround?
There is no way to do what you want. Non-#objc protocols cannot be represented in Objective-C. One reason is that Non-#objc protocols can represent non-class types (and indeed, you said that you wanted to use it for Int and String, both non-class types), and protocols in Objective-C are only for objects.
KVO is a feature designed for Objective-C, so you must think about what you expect it to see from the perspective of Objective-C. If you were doing this in Objective-C, you would not want to have a property that could either be an object like id or a non-object like int -- you can't even declare that. Instead, as you said in your comment, you probably want it to be just objects. And you want to be able to use Foundation's bridging to turn Int into NSNumber * and String into NSString *. These are regular Cocoa classes that inherit from NSObject, which implements Printable.
So it seems to me you should just use NSObject or NSObjectProtocol.
Unfortunately ObjC does not treat protocols as types, they are just a convenient way of grouping members. Under the covers they are of type Any, so regretfully you will have to make the property Any and cast to Printable.
The best I can thing of is:
dynamic var selectedValue: Any
var printableValue : Printable {
get {
return (Printable)selectedValue
}
set {
selectedValue = newValue
}
}