Implementing as conversions in swift - swift

I'm making a class that is similar to CLLocation and I wanted to be able o do:
customClass as! CLLocaiton
Do I need to implement any special protocols or anything like that?

You cannot cast from CustomClass to CLLocation, because they are not related to one another. Thus, you won't be able to do it that way at all; you have no power over how the as operator works (you cannot customize its behavior).
You can, however, coerce from CustomClass to CLLocation, in just the same way as you would write e.g. Double(myInteger). What you need to do, in other words, is to write an extension to CLLocation that implements init(customClass:CustomClass). Thus you'll be able to hand an instance of your CustomClass over to this initializer and get back a new CLLocation instance based upon it.
So, let's pretend you custom class is something like this:
class CustomClass {
var lat : Double = 0
var long : Double = 0
}
Then you could extend CLLocation like this:
extension CLLocation {
convenience init(_ cc:CustomClass) {
self.init(latitude:cc.lat, longitude:cc.long)
}
}
And now you can make a CustomClass instance like this:
let cc = CustomClass()
cc.lat = 30
cc.long = 40
And later you can coerce it to a CLLocation like this:
let loc = CLLocation(cc) // ta-daa!
Thus you are talking just the way you would talk with Double(myInteger).
Alternatively, just write a method in CustomClass that returns an "equivalent" CLLocation. This approach is probably easier. So, let's pretend your class is something like this:
class CustomClass {
var lat : Double = 0
var long : Double = 0
func location() -> CLLocation {
return CLLocation(latitude: lat, longitude: long)
}
}
So now you can work with an instance of your CustomClass:
let cc = CustomClass()
cc.lat = 30
cc.long = 40
And then later you can "convert" it to a CLLocation:
let loc = cc.location()

You should not need to do anything special. Just make CustomClass a subclass of CLLocation and it will be implicitly upcast any time it is needed. Upcasting with the as keyword should also work.
class CustomClass: CLLocation {}
let customLocation = CustomClass()
let upcastCustomLocation = customLocation as CLLocation
If for some reason you are not able or willing to make CustomClass a relative of CLLocation through inheritance, you will not be able to use the as keyword (as pointed out by matt).

The as/as!/as? family of operators is not for conversion of one type to another; it's for casting.
Casting is when you tell the compiler that a value of one type can be safely interpreted as a value of another type. In Swift, this specifically (due to the "safe" part) means types that are related in the type hierarchy — subtypes and supertypes. If you have a SomeClass type, and a SomeSubclass type that extends it, or a SomeProtocol type and a SomeType (class, struct, or enum) that adopts that protocol, you can always safely upcast from the more specific type to the more general type (the superclass or protocol) with the as operator. And you can downcast a general type to a more specific type with the as! or as? operator — those involve some possibility of failure because the compiler can't know if you're going down the right branch of the type hierarchy.
If you have types that aren't related through the type hierarchy (that is, one is not a subclass of the other, or one is not a protocol adopted by the other), you can't cast. Instead, you convert.
In Swift, conversion is (almost) always explicit — you create a value of the converted-to type using one of its initializers that takes a value of the converted-from type. Examples:
// convert numeric types
let one: Int = 1
let onePointOh = Float(one)
// convert a sequence to an array
let oneToTen = Array(1...10)
So, if you're writing a class that's like CLLocation, but isn't actually a subclass of CLLocation, and you want to use it in a context that requires a CLLocation instance, you need to create the way to get a CLLocation instance from your class.
You could do that with a method or computed property:
extension MyLocationClass {
var asCLLocation: CLLocation {
return CLLocation(latitude: self.lat, longitude: self.long)
}
}
Or, if you like the construction/conversion syntax used in the standard library, by extending CLLocation to add a convenience initializer based on your class:
extension CLLocation {
convenience init(_: MyLocationClass) {
self.init(latitude: lat, longitude: long)
}
}

Related

Why does Swift BooleanLiteralConvertible require a boolean literal?

I am trying to add BooleanLiteralConvertible support to my class so I can instantiate it with a boolean. The thing that's throwing me for a loop is the distinction between a boolean value and a boolean literal.
For example, after adding the protocol I attempted this:
func setSelected(value: Bool) {
var node: MyClass = value
}
But Swift complained that it cannot convert Bool to MyClass. It took me a while to realize it has to be a boolean literal. Oddly enough the following works fine:
func setSelected(value: Bool) {
var node: MyClass = value ? true : false
}
…which seems just absolutely silly to me. Is there a legitimate reason for this seemingly very bizarre requirement?
Types conforming to BooleanLiteralConvertible can be initialized with the Boolean literals true and false, e.g.
let mc : MyClass = true
This has nothing to do with initializing the type with a Boolean value:
let value : Bool = // ... some boolean value
let mc : MyClass = value // error: cannot convert value of type 'Bool' to specified type 'MyClass'
and there is – as far as I know – no way to make such an implicit
conversion work. You would have to write a custom init method
init(bool : Bool) {
// ...
}
and initialize the object as
let value : Bool = // ... some boolean value
let mc = MyClass(bool: value)
I like the question. Only the Swift team could definitively answer, but I can speculate as to why: converting a typed value into a variable of a different type without an explicit conversion or cast is very easy to confuse with a programmer error, and in many cases is something the compiler should warn about.
Example (and assume that Person is also a StringLiteralConvertible that can be initialized with a string variable as well as a literal as you pose in your question):
struct Person {
private static var idCounter = 1
var name:String
let id:Int
init(withName name:String) {
Person.idCounter += 1
self.name = name
self.id = Person.idCounter
}
}
var person = Person(withName:"Mary")
let name = "John"
person = name
The above code looks suspiciously like a mistake, where the programmer is assigning a value of the wrong type (String) to a variable of type Person. It may in fact be a mistake. Maybe the programmer only meant to change the name of the person (person.name = name) without creating a new Person with a new unique id. Or maybe the programmer intended to assign some other value to person but made a typo or code completion error. Hard to tell without either being the original programmer, or carefully studying all the context to see whether this conversion makes sense. And it gets harder the further the assignment is from the place where the variables are originally initialized Should the compiler warn here that a value of type String is being assigned to a variable of type Person?
The example would be far more clear, and more in line with Swift conventions as:
var person = Person(withName:"Mary")
let name = "John"
person = Person(withName:name)
The above version is completely unambiguous, both to the compiler and to any other programmers who read this later.

What is the syntax to store a Class as a value in a Dictionary in Swift?

Lets say I want to do this:
class foobar : NSObject {
//method declarations, etc.
}
Then later:
let myDictionary:Dictionary = ["returnMeAnAwesomeClass":foobar]
Does not work.
If I put in foobar.Type, it also doesn't work.
If I put in foobar.class as foobar.Type, it also doesn't work.
The reason I want this is because there's a method in a system API that takes a class as the argument, e.g.:
func enterState(_ stateClass: AnyClass) -> Bool
(in GKStateMachine)
I'd find it acceptable to be able to get a string and turn that into a class.
You can use foobar.self if you need to obtain the class type. And also you should add type safety to your dictionary:
let myDictionary: [String:AnyClass] = ["returnMeAnAwesomeClass": foobar.self]
If you're initializing the dictionary at the same place where you're declaring it, you can skip the type declaration, as the compiler will infer it:
let myDictionary = ["returnMeAnAwesomeClass": foobar.self]

Convert or cast object to string

how can i convert any object type to a string?
let single_result = results[i]
var result = ""
result = single_result.valueForKey("Level")
now i get the error: could not assign a value of type any object to a value of type string.
and if i cast it:
result = single_result.valueForKey("Level") as! String
i get the error:
Could not cast value of type '__NSCFNumber' (0x103215cf0) to 'NSString' (0x1036a68e0).
How can i solve this issue?
You can't cast any random value to a string. A force cast (as!) will fail if the object can't be cast to a string.
If you know it will always contain an NSNumber then you need to add code that converts the NSNumber to a string. This code should work:
if let result_number = single_result.valueForKey("Level") as? NSNumber
{
let result_string = "\(result_number)"
}
If the object returned for the "Level" key can be different object types then you'll need to write more flexible code to deal with those other possible types.
Swift arrays and dictionaries are normally typed, which makes this kind of thing cleaner.
I'd say that #AirSpeedVelocity's answer (European or African?) is the best. Use the built-in toString function. It sounds like it works on ANY Swift type.
EDIT:
In Swift 3, the answer appears to have changed. Now, you want to use the String initializer
init(describing:)
Or, to use the code from the question:
result = single_result.valueForKey("Level")
let resultString = String(describing: result)
Note that usually you don't want valueForKey. That is a KVO method that will only work on NSObjects. Assuming single_result is a Dictionary, you probably want this syntax instead:
result = single_result["Level"]
This is the documentation for the String initializer provided here.
let s = String(describing: <AnyObject>)
Nothing else is needed. This works for a diverse range of objects.
The toString function accepts any type and will always produce a string.
If it’s a Swift type that implements the Printable protocol, or has overridden NSObject’s description property, you’ll get whatever the .description property returns. In the case of NSNumber, you’ll get a string representation of the number.
If it hasn’t, you’ll get a fairly unhelpful string of the class name plus the memory address. But most standard classes, including NSNumber, will produce something sensible.
import Foundation
class X: NSObject {
override var description: String {
return "Blah"
}
}
let x: AnyObject = X()
toString(x) // return "Blah"
"\(x)" // does the same thing but IMO is less clear
struct S: Printable {
var description: String {
return "asdf"
}
}
// doesn't matter if it's an Any or AnyObject
let s: Any = S()
toString(s) // reuturns "asdf"
let n = NSNumber(double: 123.45)
toString(n) // returns "123.45"
n.stringValue // also works, but is specific to NSNumber
(p.s. always use toString rather than testing for Printable. For one thing, String doesn’t conform to Printable...)
toString() doesn't seem to exist in Swift 3 anymore.
Looks like there's a failable initializer that will return the passed in value's description.
init?(_ description: String)
Docs here https://developer.apple.com/reference/swift/string/1540435-init

What function does "as" in the Swift syntax have?

Recently I stumbled upon a syntax I cannot find a reference to: What does as mean in the Swift syntax?
Like in:
var touch = touches.anyObject() as UITouch!
Unfortunately, it's hard to search for a word like as, so I didn't find it in the Swift Programming Language handbook by Apple. Maybe someone can guide me to the right passage?
And why does the element after as always have an ! to denote to unwrap an Optional?
Thanks!
The as keyword is used for casting an object as another type of object. For this to work, the class must be convertible to that type.
For example, this works:
let myInt: Int = 0.5 as Int // Double is convertible to Int
This, however, doesn't:
let myStr String = 0.5 as String // Double is not convertible to String
You can also perform optional casting (commonly used in if-let statements) with the ? operator:
if let myStr: String = myDict.valueForKey("theString") as? String {
// Successful cast
} else {
// Unsuccessful cast
}
In your case, touches is (I'm assuming from the anyObject() call) an NSSet. Because NSSet.anyObject() returns an AnyObject?, you have to cast the result as a UITouch to be able to use it.
In that example, if anyObject() returns nil, the app will crash, because you are forcing a cast to UITouch! (explicitly unwrapping). A safer way would be something like this:
if let touch: UITouch = touches.anyObject() as? UITouch {
// Continue
}
A constant or variable of a certain class type may actually refer to
an instance of a subclass behind the scenes. Where you believe this is
the case, you can try to downcast to the subclass type with the type
cast operator (as).
from Swift Programming Language, Type Casting
And why does the element after as always have an ! to denote to unwrap an Optional?
It is not. It is trying to downcast to "Implicitly Unwrapped Optionals", see Swift Programming Language, Types
as is an operator that cast a value to a different type.
For example:
Suppose you have an NSSet instance with some elements that have a type Car.
Then if you want to get any object:Car from this set, you should call anyObject().
var someCar = set.anyObject() //here someCar is Optional with type AnyObject (:AnyObject?), because anyObject() -> AnyObject?
Let's imagine the situation when you need to get an object from the set with type Car.
var realCar: Car = someCar as Car //here realCar is Optional with type Car (:Car?)
Than if you exactly know that someCar is not an Optional ( someCar != nil) you can do follow:
var realCarAndNotAnOptional = someCar as Car! //here realCarAndNotAnOptional just have a type == Car
More info here: Swift: Type Casting

Swift: Casting collections, and creating custom convertible protocols

Consider this Person class, which simply implements StringLiteralConvertible and assigns the string literal to name:
class Person : StringLiteralConvertible {
var name : String?
typealias StringLiteralType = String
required init(stringLiteral value: StringLiteralType) {
println("stringLiteral \(value)")
name = value
}
typealias ExtendedGraphemeClusterLiteralType = String
required init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
println("extendedGraphemeClusterLiteral \(value)")
name = value
}
typealias UnicodeScalarLiteralType = Character
required init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
println("unicodeScalarLiteral \(value)")
name = "\(value)"
}
}
This allows me to create a Person instance using a string:
let aaron : Person = "Aaron"
I can even cast an array of Persons from an array of strings:
let names = ["John", "Jane"] as [Person]
However this only works with string literals. If I use a string variable, it fails:
let aaronString = "Aaron"
let aaron : Person = aaronString
// Error: 'NSString' is not a subtype of 'Person'
Similarly, trying to cast an array of non-literal strings fails:
let nameStrings = ["John", "Jane"]
let people : [Person] = nameStrings
// Error: 'String' is not identical to 'Person'
I have three questions:
Is there another protocol I can implement to cast a non-literal string to a Person? I'd like to do this so I can cast entire collections to convert the objects.
If no to #1, is map + an initializer the best way to perform the conversion myself?
let nameStrings = ["John", "Jane"]
let people = nameStrings.map{Person(name: $0)}
If yes to #1, is there a similar approach I can use to specify an approach to convert two objects which are unrelated in hierarchy? That is, can I work around this error without an initializer?
let rikerPerson : Person = "Riker"
let rikerEmployee = rikerPerson as Employee
// Error: 'Person' is not convertible to 'Employee'
What you are describing as “casting” isn’t really casting (in the way that, say, s = “fred”; ns = s as NSString is, or that casts in C++ are).
let names = ["John", "Jane"] as [Person]
is just another a way of writing:
let names: [Person] = ["John", "Jane"]
that is, a way of telling Swift which of the many possible versions of StringLiteralConvertible to use (and not the one for String, which is the default).
Put it another way – your as is fulfilling a similar function to the as in this snippet that disambiguates two overloaded functions that differ only by return type:
func f() -> String { return "foo" }
func f() -> Int { return 42 }
let i = f() as Int // i will be 42
let s = f() as String // s will be “foo"
No “conversion” is going on here – the as is just being used to disambiguate which f Swift calls. It’s the same with which init(stringLiteral:) is chosen.
Definitely (but only if you put a space between map and the { } ;-).
If you’re concerned about the waste of converting it all to an array just to do some other thing with it, check out lazy(a).map
Nope. In the betas, there used to be a __conversion() -> T method you could implement to do “casts” like this on your own classes – or more importantly, allowed you to pass your Person class into a function that took an Employee argument and have it be converted implicitly. But that got disappeared. Generally that kind of implicit conversion is antithetical to Swift’s style, except in rare cases (Obj-C and C interop, and implicit wrapping in optionals, being the main ones). You have to write an init for Employee that takes a Person (or some class or protocol that Person conforms to), and then call it.