Jeef
Jeef

Reputation: 27275

Implementing as conversions in 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?

Upvotes: 2

Views: 82

Answers (3)

rickster
rickster

Reputation: 126127

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)
    }
}

Upvotes: 0

aganders3
aganders3

Reputation: 5945

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).

Upvotes: 0

matt
matt

Reputation: 535229

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()

Upvotes: 2

Related Questions