I am trying to subclass MKPolyline. However the problem is, MKPolyline does not have any designated initializers. Here is what I tried to do:
import MapKit
class MyPolyline: MKPolyline {
let locations: [Location]
init(locations: [Location]) {
self.locations = locations
var coordinates: [CLLocationCoordinate2D] = []
for location in locations {
coordinates.append(location.coordinate)
}
super.init(coordinates: coordinates, count: coordinates.count)
}
}
But I am getting Must call a designated initializer of the superclass 'MKPolyline' error at the super.init call. To the best of my knowledge, MKPolyline does not have any designated initializers.
What should I do? How can I subclass a class which doesn't have any designated initializers?
Designated Initializers
As you seem to know, designated initializers provide a way of creating an object from a set of parameters. Every Swift object must have at least one designated initializer, although in certain cases Swift may provide them by default.
In the case of a class, a default initializer will be provided iff all stored properties of the class provide default values in their declarations. Otherwise, a designated initializer must be provided by the class creator. Even if a designated init is provided explicitly, however, it is not required to have an access control level that would make it accessible to you.
So MKPolyline surely has at least one designated init, but it may not be visible to you. This makes subclassing effectively impossible, however there are alternatives.
Two alternatives immediately come to mind:
Class Extensions and Convenience Initializers
One great feature of Swift is that classes may be extended even if the original class was defined in another module, even a third party one.
The issue you are running into is that MKPolyline defines convenience inits but no public designated ones. This is an issue because Swift has a rule that a designated init must call a designated init of its immediate superclass. Because of this rule, even a designated init will not work, even in an extension. Fortunately, Swift has convenience initializers.
Convenience inits are just initializers that work by eventually calling designated inits within the same class. They do not delegate up or down, but sideways, so to speak. Unlike a designated init, a convenience init may call either a designated init or another convenience init, as long as it is within the same class.
Using this knowledge, we could create an extension to MKPolyline that declares a convenience init which would call one of the other convenience inits. We can do this because inside of an extension it is just like you are in the original class itself, so this satisfies the same-class requirement of convenience inits.
Basically, you would just have an extension with a convenience init that would take an array of Location, convert them to coordinates, and pass them to the convenience init MKPolyline already defines.
If you still want to hold the array of locations as a stored property, we run into another problem because extensions may not declare stored properties. We can get around this by making locations a computed property that simply reads from the already-existing getCoordinates method.
Here's the code:
extension MKPolyline {
var locations: [Location] {
guard pointCount > 0 else { return [] }
let defaultCoordinate = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
var coordinates = [CLLocationCoordinate2D](repeating: defaultCoordinate, count: pointCount)
getCoordinates(&coordinates, range: NSRange(location: 0, length: pointCount))
// Assuming Location has an init that takes in a coordinate:
return coordinates.map({ Location(coordinate: $0) })
}
convenience init(locations: [Location]) {
let coordinates = locations.map({ $0.coordinate })
self.init(coordinates: coordinates, count: coordinates.count)
}
}
Here's what's going on. At the bottom we have a convenience init very similar to what you already did except it calls a convenience init on self since we're not in a subclass. I also used map as a simpler way of pulling the coordinates out of Location.
Lastly, we have a computed locations property that uses the getCoordinates method behind the scenes. The implementation I have provided may look odd, but it is necessary because the getCoordinates function is Objective-C–based and uses UnsafeMutablePointer when imported to Swift. You therefore need to first declare a mutable array of CLLocationCoordinate2D with exact length, and then pass it to getCoordinates, which will fill the passed array within the range specified by the range parameter. The & before the coordinates parameter tells Swift that it is an inout parameter and may be mutated by the function.
If, however, you need locations to be a stored property in order to accommodate a more complex Location object, you'll probably need to go with the second option described below, since extensions may not have stored properties.
Wrapper Class
This solution doesn't feel as 'Swifty' as the previous, but it is the only one I know of that would let you have a stored property. Basically, you would just define a new class that would hold an underlying MKPolyline instance:
class MyPolyline {
let underlyingPolyline: MKPolyline
let locations: [Location]
init(locations: [Location]) {
self.locations = locations
let coordinates = locations.map { $0.coordinate }
self.underlyingPolyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
}
}
The downside to this approach is that anytime you want to use MyPolyline as an MKPolyline, you will need to use myPolyline.underlyingPolyline to retrieve the instance. The only way around this that I know of is to use the method described by the accepted answer to this question in order to bridge your type to MKPolyline, but this uses a protocol that is undocumented and therefore may not be accepted by Apple.
I searched for hours on SO (and on other sites) about this problem, but finally I could rewrite a more generic solution. Thanks for the original author :)
Related
Basically, I have a class called UserData and multiple initializers for it. In particular, I have a copy initializer which looks like:
init (_ origin: UserData){
// copy over everything
}
And another initializer which is used when I need to read the data from a file:
convenience init (Read _: Bool) {
// read stuff and call another initializer
}
Then I always got a compiler error saying cannot convert Bool to UserData whenever I tried to do var something = UserData(true).
I tried adding label, but compiler said extroneous label since there is only one parameter. I could make a workaround by adding another random parameter to the second initializer. But why is the compiler always trying to interpret the call to something does not match the type while there is another that matches the type?
Swift has no problem distinguishing two initializers with one parameter. The error is because of how the second one is defined. It should be:
convenience init (_ read: Bool) {
// read stuff and call another initializer
}
I have swift class with various properties, some of which have optional types.
class UserObject: PFUser{
//optional property
var optionalPhotoURL:String? {
get {
if let optionalPhotoURL:String = objectForKey("optionalPhotoURL"){
return optionalPhotoURL
}
//not needed but just for emphasis
return nil
}
//I am unable to set UserObject.optionalPhotoURL = nil with this setters
set {
if let newPhotoURL:String = newValue! {
setObject(newPhotoURL, forKey: "optionalPhotoURL")
}else{
self.optionalPhotoURL = nil
}
}
}
}
I am unable to set optionalPhotoURL as nil, if it already had a previous value assigned to it.
I guess my question is, how do i "unset" an optional property with custom setter?
Update
These setters all crash
set {
if let newPhotoURL:String = newValue {
setObject(newPhotoURL, forKey: "optionalPhotoURL")
}else{
self.optionalPhotoURL = nil
}
}
and this
set {
if (newValue != nil) {
setObject(newValue!, forKey: "optionalPhotoURL")
}else{
self.optionalPhotoURL = nil
}
}
What you have here is a computed property.
Swift properties can either be computed or stored. We can observe value changes in our stored properties by using didSet and willSet but here we still have a stored property.
In your case, since you have overridden set and get*, you don't have a stored property, you have a computed property. If you want a computed property to have a backing storage, you must create that independently.
So you may want something like this:
class FooClass {
private var storageProperty: String?
var accessProperty: String? {
get {
return self.storageProperty
}
set {
self.storageProperty = newValue
// or whatever logic you may like here
}
}
}
*: You can't override set without also overriding get. You can however override get without overriding set--this makes a readonly computed value.
Moreover, it's important that we implement our storage properties in this way over relying on key-value coding.
For starters, setObject(forKey:) approach doesn't even work on pure Swift types. This will only work on objects which inherit from Objective-C types. It's an inherited method from NSObject's compliance to NSKeyValueCoding protocol. Why the base object of Objective-C conforms to so many protocols is beyond me... but it does and there's nothing we can do about it.
If we have a code base in which some of our objects are inheriting from Objective-C objects (which basically any project will have, UIViewController, etc), and some of our objects are pure Swift objects (which you will tend to have when you're creating your own Swift classes from scratch), then our Swift objects will not be able to implement this same pattern. If we have some objects of both types, we'll either have to implement the pattern I show above for all of them (and then we have consistency) or we'll have to implement one pattern for some types and another for other types (Swift structs would definitely have to implement the above pattern, you can't just make them inherit from NSObject) (and then we have inconsistency, which we don't like).
But what's far worse about setObject(forKey:) is that the first argument of this method always will be of type AnyObject. There is no type safety to the method at all. Where things are stored via setObject(forKey:) is based purely on the key which we use. When we use setObject(forKey:), we take a pile of type-safety advantages that Swift gives us and we throw them out the window. If we don't care to leverage the advantages Swift gives us over Objective-C, why are we writing it in Swift at all?
We can't even make the stored property private when we use setObject(forKey:). Anyone who knows the key can call setObject(forKey:) and set that object to whatever they want. And that includes objects which are not strings. All we have to do to introduce a crash to this codebase is write a class extension or subclass which has a key collision on a different type other than what you've used (so maybe an NSData value for the same key). And even if it doesn't happen to be a different type, simply duplicating the same key for multiple properties is going to introduce bugs... and bugs that are hard to debug.
Never set a value of a computed property in its set scope by calling itself !
This causes an infinite loop and the app will crash.
I don't know which API setObject:forKey belongs to, but in the case of nil you are supposed to remove the object
set {
if let newPhotoURL = newValue {
setObject(newPhotoURL, forKey: "optionalPhotoURL")
} else {
removeObjectForKey("optionalPhotoURL")
}
}
Your property optionalPhotoURL is a computed property, it does not store any values:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
You might want to create an additional property which actually stores the value. However, why do you want to set it to nil, since you are not deleting they object in case of nil.
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)
}
}
Does anybody know why I get BAD_ACCESS on getting & setting of my iVars with the following code ?
class myClass: NSObject {
var model = "Unspecified"
override init() {
super.init()
var key: NSString = "model"
var aClass : AnyClass? = self
var ivar: Ivar = class_getInstanceVariable(aClass, key.UTF8String)
// Set
object_setIvar(aClass, ivar, "R56")
// Get
var value: AnyObject = object_getIvar(aClass, ivar)
}
}
myClass()
You get a bad access because Swift classes do not have traditional iVars anymore (try to grab a Swift class' iVar layout and see for yourself). But Swift classes are also Objective-C objects to the runtime, and there don't appear to be any paths excluding Swift classes in it.
What winds up happening is the runtime hands you a bogus iVar that it truly thinks points to a proper offset in the class definition (probably 0 or thereabouts because it doesn't exist). When you try to get said iVar, the runtime literally derefs you some of the object's bytes and tries to wrap it back up in an Objective-C pointer. Often this data has the tagged bit set unintentionally, and so often maps to tagged classes (in my tests I was reliably getting back a tagged pointer for what the runtime thought was NSMutableData).
Long story short: you can't do it anymore.
Currently, I am seeing something strange behavior.
class DataManager1
{
let THE_ID = "SOME_ID_STRING"
let _con1 = CKContainer(identifier: THE_ID) // error
// error: 'DataManager1.Type' does not have a member named 'THE_ID'
}
class DataManager2
{
let THE_ID = "SOME_ID_STRING"
let _con1:CKContainer?
init()
{
_con1 = CKContainer(identifier: THE_ID) // no error.
}
}
In C++ we have a defined initialization order between instance member variables. I expected something similar, but actually I couldn't find a mention for that form the manual.
Does Swift has a defined initialization order of properties? If it does, what is the rule, and where can I find the rule?
This is due to the fact that you're using a Closure (a Function is just a special case of Closure that is unnamed) to initialize the _con1 property with a default value.
From the Apple provided iBook:
If you use a closure to initialize a property, remember that the rest
of the instance has not yet been initialized at the point that the
closure is executed. This means that you cannot access any other
property values from within your closure, even if those properties
have default values. You also cannot use the implicit self property,
or call any of the instance’s methods.
Even though the note above refers specifically to closures, it seems that trying to set the default value for a property to be that of another property directly also does not work.