Chapter 3. Swift objects

This chapter covers

  • Exploring objects, methods, and parameters in Swift
  • Initializing properties
  • Comparing inheritance with protocols
  • Differentiating between classes and structs
  • Exploring ways to extend your code

It’s impossible to do anything in iOS development without using objects. Views are objects, view controllers are objects, models are objects—even basic data types such as String, Int, and Array are objects in Swift!

An object in Swift is a specific instance of a type of thing. In this chapter, we’ll look at different ways of building up and structuring these types of things in your code. From experience in other languages, you may know this “type of thing” (or type) as a class. While it’s true that types can be represented by classes in Swift, they’re not the only type of thing in Swift—other types called structures and enumerations also exist. We’ll come back to those, but first let’s look at classes.

Don’t forget, you can refer to the Swift cheat sheets in appendix B. This chapter is summarized on the last page of the cheat sheets.

3.1. Classes

One approach for creating objects in Swift is with a class. A class defines what a type does with methods. A method is a function defined within a type. Along with methods, a class defines what a type is with properties. Properties are variables or constants stored in a type.

Let’s say you’ve decided to build a distance converter app. Your app will accept distances in miles or kilometers, and will display the distance in either form of measurement, too.

You decide the best approach is to build a type that stores distances, regardless of the scale. You could create a distance with a miles or kilometers value, update the distance with a miles or kilometers value, or use the distance type to return its value as miles or kilometers (see figure 3.1).

Figure 3.1. Distance type

3.1.1. Defining a class

Let’s start by defining a simple Distance type with a class. In this chapter, you’ll build up this class to contain a distance using different measurement types.

  1. Create a new playground to follow along, and call it Distance. Classes are defined with the class keyword followed by the name of the class and the rest of the definition contained within curly brackets.
  2. Create a Distance class.
    class Distance {
    
    }
  3. Now that you have a class, you can create (or instantiate) your class with the name of the type, followed by parentheses, and assign this object to a variable:
    var distance = Distance()

You might recognize the parentheses syntax from the previous chapter as an alternative syntax for creating or instantiating simple data types.

Now that you have a class definition for Distance, you can add properties and methods to it.

3.1.2. Properties

Variables that we’ve looked at so far have been global variables—defined outside the context of a class or function. Variables that are defined within a class are called properties, and fall into two broad categories: type properties and instance properties.

Type properties

Type properties, also known as static properties, are relevant to all things of a certain type. It isn’t even necessary that an instance of a type exist to access type properties. Type properties are connected to the type rather than the object. You instantiate a type property with the static keyword followed by a normal declaration of a variable.

For example, maybe you’d like to store the number of kilometers in a mile in a type property in your Distance class. In this case, a constant would make more sense, because the number of kilometers in a mile won’t be changing any time soon. Use the keyword let instead of var to define a constant.

  1. Add a type property constant to your simple Distance class:
    class Distance {
        static let kmPerMile = 1.60934
    }
    You could then retrieve or set this type property directly on the type.
  2. Print to the console using the type property you created:
    print ("2 miles = (Distance.kmPerMile * 2) km")
Instance properties

Instance properties are relevant to specific objects or instances of a type.

Because the miles value will be relevant to specific instances of Distance, add miles as an instance property to your Distance class.

class Distance {
    static let kmPerMile = 1.60934
    var miles:Double
}

Whoops! If you’re following along in the playground, you’ll notice that this triggers a compiler error. Tap the red dot to see more information on the error (see figure 3.2). A pop-up appears below the line that describes the error along with Xcode’s suggested fix.

Figure 3.2. Non-optional variable can’t equal nil

As we explored in the previous chapter, non-optionals can never equal nil. The Distance class can’t contain a miles property that’s equal to nil.

You have three possible alternatives to get rid of that red dot.

  • One option is to give the property a default value. This is what Xcode suggests. If you tap Fix Button, Xcode will resolve the problem in this way for you. But a default value for the miles property doesn’t make sense. There’s no reason why 0 or any other value should be a default value for miles. Press Command-Z to undo this fix.
  • Another option is to make the miles property an optional. This is easy to do; all you need to do is add a question mark:
    var miles:Double?
    This removes the error, but isn’t appropriate for this example either. If you define a Distance object, you want it to have a value for miles! A distance with a miles value of nil doesn’t make sense. Undo this fix too.
  • You could pass a value to the miles property in an initializer. What’s an initializer?

3.1.3. Initializers

An initializer is a special type of function that sets up a type. You can use an initializer to pass in values when you instantiate the type.

You can create an initializer with the init keyword followed by any parameters you want to pass in to initialize the instance properties.

  1. Add an initializer to the Distance class to pass in a value to initialize the miles property.
    class Distance {
        static let kmPerMile = 1.60934
        var miles:Double
        init(miles:Double) {                              1
            self.miles = miles                            2
        }
    }

    • 1 Initializer
    • 2 Initializes the miles property
    As you can see, you can use the keyword self to differentiate between the instance property (self.miles) and the parameter (miles) that’s passed in to the initializer. Now that the miles property is set in the initializer, the requirement that all non-optionals should contain non-nil values is satisfied, and the red dot should go away.
  2. You can now instantiate a Distance object by passing in a value for miles.
    var distance = Distance(miles: 60)
    Note

    By default you need to pass in the names of the arguments in initializers and functions. We’ll look at this in more detail shortly.

  3. Now that you have a Distance class, you could introduce a km property if you like, and initialize it in the initializer calculated from the miles value and the kmPerMile type property.
    class Distance {
        static let kmPerMile = 1.60934
        var miles:Double
        var km:Double                                             1
        init(miles:Double) {
            self.miles = miles
            self.km = miles * Distance.kmPerMile                  2
        }
    }

    • 1 Adds km property
    • 2 Calculates km from miles

In case we need to calculate kilometers again, it may make sense to move this calculation to a method.

Note

If all properties of a class have default values, Xcode will synthesize a default initializer automatically for you with no arguments.

3.1.4. Methods

Functions defined inside a class are called methods. Like variables and properties, methods can be divided into instance methods or type methods.

Instance methods are methods that are relevant to an instance of a type, whereas type methods apply to the type itself.

Instance methods

Instance methods are relevant to each instance of a type.

In the future, you might want your Distance class to return a nicely formatted version of its data. Because the response will be different for each instance of Distance, this would be more relevant as an instance method.

  1. Add an instance method to your Distance class that returns a nicely formatted miles string.
    func displayMiles()->String {
        return "(Int(miles)) miles"
    }
  2. You can call your instance method now using a Distance object.
    var distance = Distance(miles: 60)
    print(distance.displayMiles())
    //prints "60 miles" to console
    You currently calculate kilometers from miles in the Distance initializer. Let’s refactor this calculation into a reusable method. You might be tempted to use an instance method, but you’ll find this approach causes an error.
  3. Add an instance method that calculates kilometers from miles, and call it from the initializer.
    class Distance {
        static let kmPerMile = 1.60934
        var miles:Double
        var km:Double
        init(miles:Double) {
            self.miles = miles
            self.km = toKm(miles:miles)              1
        }
        func toKm(miles:Double)->Double {            2
            return miles * Distance.kmPerMile        2
        }                                            2
    }

    • 1 Call instance method; error here
    • 2 Instance method

Curious! Why does calling an instance method in the initializer cause an error?

Until an initializer has fulfilled its duties to provide initial values for all non-optionals, the instance isn’t designated as safe and therefore its instance properties and methods can’t be accessed.

To solve this problem, one solution could be to ensure that all properties have values before using the instance method:

init(miles:Double) {
    self.miles = miles
    self.km = 0                                  1
    self.km = toKm(miles:miles)                  2
}

  • 1 Provides default value
  • 1 No error now!

But stepping back from the problem, converting miles to kilometers could be as easily set up as a useful utility method on the type. Let’s refactor our toKm method as a type method.

Type methods

Like type properties, type methods (also known as static methods) are methods that can be called directly on the type, rather than individual instances of the type.

  1. Use the static keyword to refactor the toKm method as a type method. Type methods have implicit access to type properties, so we can remove the class name Distance before kmPerMile:
    static func toKm(miles:Double)->Double {
        return miles * kmPerMile
    }
    Similar to the way you used type properties, call a type method by prefacing it with the type. For example, here’s how you could call the toKm method we set up on the Distance class:
    print(Distance.toKm(miles: 30))
    Because type methods are called on the type and don’t depend on an instance of a type, they can be used to initialize properties in the initializer.
  2. Call your new static method in the initializer for Distance.
    init(miles:Double) {
        self.miles = miles
        self.km = Distance.toKm(miles:miles)
    }
Overloading

It can be strange to developers new to Swift that it’s completely valid in Swift to have two functions with the same name, as long as the names or types of the parameters are distinct. This is called overloading a function. “Overloading a function”—even the name sounds a little scary! Don’t worry, this is standard practice in Swift and a useful tool.

At the moment, the Distance class has a static method called toKm that calculates kilometers from miles. What if later you find you need to calculate kilometers from another form of measurement, for example, feet? You’ll probably want to name that method toKm, too. Well, in Swift you can do this by overloading the function by defining two functions with different parameter names, as shown in the following listing.

Listing 3.1. Overloading a function with different parameter names
static let feetPerKm:Double = 5280

static func toKm(miles:Double)->Double {
    return miles * kmPerMile
}
static func toKm(feet:Double)->Double {
    return feet / feetPerKm
}

Which method you use depends on the parameter name you pass:

let km = Distance.toKm(miles:60)   //96.5604
let km2 = Distance.toKm(feet:100)  // 0.03048

Similarly, perhaps in the future you want your Distance class to accept an Int value for km in your toMiles method. This time, you could overload the function by defining two functions with the same name that expect different data types, as shown in the following listing.

Listing 3.2. Overloading a function with different parameter data types
static func toMiles(km:Double)->Double {
    return km / kmPerMile
}
static func toMiles(km:Int)->Double {
    return Double(km) / kmPerMile
}

Again, the method you use depends on the data type of the parameter you pass.

Initializers can be overloaded as well.

  1. Add a second initializer for the Distance class to initialize the object based on kilometers. You’ll need to add a type method to calculate miles from kilometers as well.
    class Distance {
        static let kmPerMile = 1.60934
        var miles:Double
        var km:Double
        init(miles:Double) {
            self.miles = miles
            self.km = Distance.toKm(miles:miles)
        }
        init(km:Double) {                                             1
            self.km = km
            self.miles = Distance.toMiles(km:km)
        }
        static func toKm(miles:Double)->Double {
            return miles * kmPerMile
        }
        static func toMiles(km:Double)->Double {                      2
            return km / kmPerMile
        }
    }

    • 1 Overloaded initializer
    • 2 New type method
  2. You can now use miles or kilometers to instantiate a Distance object:
    var distance1 = Distance(miles: 60)
    var distance2 = Distance(km: 100)

The Distance class is shaping up, but it has a bit of redundancy to it. Whether you store the distance in miles or kilometers, you’re storing the same distance twice using two different measurement units. Shortly, we’ll look at how to clean up that redundancy with computed properties.

Convenience initializers

The initializers we’ve looked at so far have been designated initializers—the main initializer for the class that ensures that all instance properties have their initial values. Convenience initializers are alternative initializers that add the keyword convenience, and, by definition, must ultimately call self’s designated initializer to complete the initialization process. Instead of overloading the initializer in the Distance class, we could have added a convenience initializer.

convenience init(km:Double) {                       1
  self.init(miles:Distance.toMiles(km:km))          2
}

  • 1 Convenience keyword
  • 2 Calls designated initializer

3.1.5. Computed properties

Computed properties are properties that calculate their values from other properties.

As you saw earlier, there might be a point in the future when you want to add additional measurements to your Distance class—centimeters, feet, inches, cubits, yards, furlongs, nautical miles, light years, you get the idea. Should you keep all these versions of the same distance in memory? Probably not.

One solution to avoid this redundancy is to decide on one core property that will store the distance—in our Distance class, this could be miles. Then the other properties, rather than storing values, will calculate their value from the miles property. These types of properties will be computed properties.

Computed properties lie somewhere between properties and methods—they’re methods implemented with the syntax of properties. They act similarly to getters and setters in other languages.

The computed property itself doesn’t store any data. Rather, when the property’s value is retrieved, the getter calculates a value to return. Calculations are performed in curly brackets {} and the value is returned using the return keyword.

  1. To avoid redundancy, convert the km property to a read-only computed property. The km property will no longer store data; rather, it will calculate kilometers from the miles property at the moment it’s requested. The initializers will no longer need to set the km property and will set the miles property directly.
    class Distance {
        static let kmPerMile = 1.60934
        var miles:Double
        var km:Double {
            return Distance.toKm(miles:miles)
        }
        init(miles:Double) {
            self.miles = miles
            self.km = Distance.toKm(miles:miles)
        }
        init(km:Double) {
            self.km = km
            self.miles = Distance.toMiles(km:km)
        }
        static func toKm(miles:Double)->Double {
            return miles * kmPerMile
        }
        static func toMiles(km:Double)->Double {
            return km / kmPerMile
        }
    }
  2. Confirm that the km property can continue to be retrieved like a normal property.
    var distance = Distance(km: 100)
    print ("(distance.km) km is (distance.miles) miles")
    This solves the redundancy, but unfortunately there’s a problem. You want to be able to update a distance object by setting the kilometer value.
  3. Check what happens when you update the km property.
    distance.km = 90                                      1

    • 1 Error
    Because km is a read-only property, attempting to update it causes an error. Computed properties can optionally also implement a setter. A setter is a block of code that’s called when a computed property is set. Because the computed property doesn’t store any data, the setter is used to set the same values that derive the computed property’s value in the getter. The getter approach used in the previous example uses shorthand syntax to implement the getter. The longhand syntax uses a get keyword followed by curly brackets {}.
  4. Convert the km computed property to use the longhand syntax.
    var km:Double {
        get {                                       1
            return Distance.toKm(miles:miles)
        }
    }

    • 1 Explicit getter syntax
    The set syntax is similar to the get syntax, with the exception that the set syntax receives a variable representing the new value.
  5. Convert the km computed property so that it now can be “set,” as per the following code snippet:
    class Distance {
        static let kmPerMile = 1.60934
        var miles:Double
        var km:Double {
            get {
                return Distance.toKm(miles:miles)
            }
            set(newKm) {
                miles = Distance.toMiles(km:newKm)
            }
        }
        init(miles:Double) {
            self.miles = miles
        }
        init(km:Double) {
            self.miles = Distance.toMiles(km:km)
        }
        static func toKm(miles:Double)->Double {
            return miles * kmPerMile
        }
        static func toMiles(km:Double)->Double {
            return km / kmPerMile
        }
    }
    As you can see, setting the km property doesn’t store the value of kilometers. Instead, it calculates and stores a value in the miles property.
  6. Confirm you can now update a distance object using either miles or kilometers:
    var distance = Distance(km: 100)
    distance.km = 35
    distance.miles = 90
  7. Confirm you can also retrieve the values of either miles or kilometers:
    print("Distance is (distance.miles) miles")
    print("Distance is (distance.km) km")

Mission complete!

Download

You can check your Distance class with mine in the Distance.playground. Download all the code for this chapter by selecting Source Code > Clone and entering the repository location: https://github.com/iOSAppDevelopmentwithSwiftinAction/Chapter3.

Challenge

Confirm in the results sidebar that the distance object is instantiating, updating, and displaying correctly using miles or kilometers.

3.1.6. Class inheritance

If you’re experienced in object-oriented programming (OOP), class inheritance and subtyping will most likely be a familiar topic. In Swift, multiple classes can inherit the implementation of one class through subclassing, forming an is-a relationship.

Note

If you’re familiar with class inheritance, you can skim through to the section called “Pros and cons.”

Classes and subclasses form a hierarchy of relationships that looks like an upside-down tree. At the top of the tree is the base class from which all classes inherit, and every subclass inherits the methods and properties of its superclass and can add on implementation.

Let’s explore inheritance by building up a class structure representing telephones. Different types of telephones exist—from older rotary phones to the latest iPhones, but they all share common functionalities: to make calls and to hang up.

See figure 3.3 for a simplified representation of the hierarchy of relationships of different types of telephones. At the base (top) of the tree is an abstract telephone, which can initiate and terminate calls. This branches into landline and cellular phones. Both landlines and cellular phones inherit the telephone’s ability to initiate and terminate calls, but the cellular phone adds the ability to send an SMS. The various types of phones that inherit from landlines and cellular phones add (among other things) different input techniques. The various types of smartphones add their own implementation of an operating system.

Note

This example isn’t intended to be comprehensive. If I listed everything a smart phone could do, I’d be here all day!

Figure 3.3. Telephone inheritance

You could model these relationships with classes. Subclasses indicate their superclass with a colon after their name, as shown in the following listing.

Listing 3.3. Class inheritance
class Telephone {
    func makeCall() {
        //make a call here
    }
    func hangUp() {
        //hang up here
    }

}
class landline:Telephone {                 1

}
class Cellular:Telephone {                 2
    func sendSMS() {                       3
        //send SMS here
    }
}
//...

  • 1 Landline subclasses Telephone
  • 2 Cellular subclasses Telephone
  • 3 Cellular adds functionality

After modeling this hierarchy, a method could receive a Telephone parameter, and regardless of whether the parameter passed is an Android, iOS, or even a rotary phone, the method knows that it can tell the telephone to makeCall() or hangUp():

func hangUpAndRedial(telephone:Telephone) {
    telephone.hangUp()
    telephone.makeCall()
}
Open

Explore the rest of the code in the Telephone-ClassInheritance.playground.

Overriding

In addition to inheriting the implementation of a superclass, a subclass can override this implementation.

The Cellular class probably wants to implement its own version of making a call on cellular networks. It can do this by overriding the makeCall method, as shown in the following listing.

Listing 3.4. Override method
class Cellular:Telephone {
    override func makeCall() {
        //make cellular call
    }
    func sendSMS() {
        //send SMS here
    }
}

Overriding a method will, by default, prevent the superclass’s implementation of that method from running. Sometimes, a subclass might want to add to the superclass’s implementation rather than replace it. In this case, the subclass can use the super keyword to first call the method on the superclass, as shown in the following listing.

Listing 3.5. Call super
override func makeCall() {
    super.makeCall()
    //make cellular call
}
Pros and cons

Class inheritance is used extensively throughout Apple frameworks. For example, as you saw in chapter 1, the UIButton class subclasses the UIControl class, which, in turn, subclasses UIView.

Inheritance is a powerful technique for expressing relationships and sharing implementation between classes and lies at the heart of object-oriented programming.

Inheritance has issues, however, that are worth noting.

  • Swift only permits inheritance from one class. iPhones aren’t simply telephones any more. They’re game consoles, e-readers, video players, compasses, GPS devices, step counters, heart rate monitors, fingerprint readers, earthquake detectors, and the list goes on. How can an iPhone share common functionality and implementation with these other devices? According to the simple inheritance model, they can’t.
  • Sharing code can only happen between subclasses and superclasses. Non-smart phones and push-button phones both have push-button input, but neither of them inherits from each other. iPads have iOS too, but they aren’t telephones. These common implementations couldn’t be shared, according to the pure inheritance model.
  • Sometimes it’s not so clear which identity is the most relevant to subclass. Should you have subclassed smartphones by operating system or by manufacturer? Both are important and could potentially contain different functionality or properties.

The trend in pure Swift has moved away from class inheritance and toward implementation of protocols.

3.1.7. Protocols

Protocols are similar to interfaces in other languages. They specify the methods and properties that a type that adopts the protocol will need to implement.

Protocol methods only indicate the definition of the method and not the actual body of the method, for example:

func makeCall()

If you rewrote the abstract Telephone class as a protocol, it would look like the following code snippet:

protocol Telephone {
    func makeCall()            1
    func hangUp()              1
}

  • 1 Protocol methods

A type adopts a protocol with syntax similar to inheritance—a colon after the type name. As the methods in a protocol don’t contain any implementation, a class that adopts the protocol must explicitly implement these methods. If you rewrote the Landline class to adopt the Telephone protocol, it would look like the following code snippet:

class Landline:Telephone {                     1
    func makeCall() {                          2
        //make a landline call here
    }
    func hangUp() {                            2
        //hang up a landline call here
    }
}

  • 1 Adopts the Telephone protocol
  • 2 Implements the protocol methods

Protocol properties only indicate whether a property can be retrieved or set. For example, if you add a phone number property to Telephone, it looks like the following code snippet:

protocol Telephone {
    var phoneNo:Int { get set }              1
    func makeCall()
    func hangUp()
}

  • 1 Protocol property

The protocol only specifies that the phoneNo property needs to exist in an adopting type, and that the property needs to get or set. Implementing the property is left to the adopting class.

class Landline:Telephone {
    var phoneNo:Int                          1
    init(phoneNo:Int) {                      2
        self.phoneNo = phoneNo               2
    }                                        2
    func makeCall() {
        //make a landline call here
    }
    func hangUp() {
        //hang up a landline call here
    }
}

  • 1 Adopts the protocol property
  • 2 Initializes the property
Protocol extensions

Okay. I have a confession to make.

I’ve been suggesting that protocols don’t contain implementation, and that’s not entirely true. Protocols are blessed with the magical ability to be extended to add actual functionality, which types that adopt the protocol will have access to.

In the previous example, the functionality of making a call and hanging up could be implemented in the Telephone protocol through use of an extension, as shown in the following listing.

Listing 3.6. Extending a protocol
protocol Telephone {
    var phoneNo:Int { get set }
    func makeCall()
    func hangUp()
}
extension Telephone {                    1
    func makeCall() {                    2
        print("Make call")               2
    }                                    2
    func hangUp() {                      2
        print("Hang up")                 2
    }                                    2
}
class Landline:Telephone {
    var phoneNo:Int
    init(phoneNo:Int) {
        self.phoneNo = phoneNo
    }
}

  • 1 Extension of protocol
  • 2 Implementation of methods in protocol

Because these methods are now implemented in the Telephone protocol, they no longer need to be implemented in a class that adopts that protocol. Note that the Landline class no longer implements the makeCall or hangUp methods.

Extended protocols still can’t store properties, but because computed properties don’t store properties, computed properties can be implemented in extended protocols.

Protocol relationships

This integration of protocols and protocol extensions into the Swift language made different and complex approaches possible for structuring relationships between types. This is due to several factors:

  • Like classes, protocols can inherit other protocols.
  • Types can adopt multiple protocols.
  • Protocols can represent different types of relationships.

Class inheritance places the emphasis on is-a relationships. As you’ve seen, protocols can represent this relationship as well. When protocols represent an is-a relationship, the convention is to use a noun. In our example, Landline is-a Telephone.

But protocols aren’t limited to identity or is-a relationships. Another common relationship that is represented is capabilities, or can-do. A common convention for protocols that represent a can-do relationship is to suffix its name with “able,” “ible,” or “ing.”

Relationships in the real world are often not as simple as a pure inheritance model can handle. Complexity and nuance need to be addressed, and protocols and protocol extensions are useful for this.

Let’s look again at telephones, converting subclasses to is-a and can-do protocols. Figure 3.4 illustrates one way you could redraw their relationships.

Figure 3.4. Telephone using protocols

In this example, a protocol called PushButtonable could be written to handle the capability of button input. This protocol could then be adopted by both the push-button landline and the non-smart cellular phone. Despite not having an inheritance relationship, the two classes could still share implementation through the Push-Buttonable protocol extension.

The iPhone no longer inherits all its smart characteristics through the Smart class. Rather, it adopts specific capabilities through protocols such as Touchable or I-nternetable. In this way, it could go beyond traditional telephone capabilities and adopt protocols and share implementation through protocol extensions with completely different devices. Maybe it could share VideoPlayable along with Television, Navigable along with GPSDevice, or GamePlayable along with Game-Console.

Using protocols to structure the relationships in your code has been coined protocol-oriented programming. Sure, you could continue to program in Swift using familiar object-oriented programming techniques, but it’s worth exploring the possibilities with protocols.

Open

Explore the protocol relationships in code in the TelephoneProtocols.playground.

Challenge

Add a Television type that shares a VideoPlayable protocol with iPhones, Androids, and Windows phones.

3.2. Structures

Classes aren’t the only “type of thing” in Swift. An alternative approach to creating objects in Swift is with a structure.

Structures have many similarities to classes. For example, they can

  • Have properties
  • Have methods
  • Have initializers
  • Adopt protocols

Define a structure with the struct keyword, for example:

struct Telephone {

}

Instantiation of a structure is identical to that of a class:

var telephone = Telephone()

3.2.1. Structures vs. classes

Structures have three main differences from classes worth noting:

  • Structures can’t inherit.
  • Structures can have memberwise initializers.
  • Structures are value types.

Each of these is explained in the following sections.

Structures can’t inherit

Structures can’t inherit other structures. They can indirectly inherit functionality, however, by adopting protocols, which, as you’ve seen, can inherit other protocols.

Memberwise initializers

If you don’t set up an initializer for a structure, an initializer that accepts all the structure’s properties as parameters will automatically be generated for you. This automated initializer is called a memberwise initializer.

As you saw earlier in the chapter, when the Distance class didn’t initialize its miles property, an error appeared. If you change the definition of this class to a struct, a memberwise initializer is automatically generated and the error disappears:

struct Distance {
    var miles:Double
}

You can now instantiate this structure using the memberwise initializer:

var distance = Distance(miles: 100)
Structures are value types

An important distinction between structures and classes is how they’re treated when they’re assigned to variables or passed to functions. Classes are assigned as references, and structures are assigned as values.

Look at the following listing. Predict the value of color1.name that will be printed to the console.

Listing 3.7. Changes to reference types
class Color {
    var name = "red"
}
var color1 = Color()
var color2 = color1
color2.name = "blue"
print(color1.name)

If you predicted "blue", pat yourself on the back! Because classes are reference types, when color1 was assigned to the color2 variable, color2 was assigned the reference to the underlying Color object (see figure 3.5).

Figure 3.5. Reference types

In the end, both color1 and color2 refer to the same object, and any changes to color2 are reflected in color1 (and vice versa).

In Swift, core data types such as String are value types. Look at the following listing and predict the value of letter1 that will be printed to the console.

Listing 3.8. Changes to value types
var letter1 = "A"
var letter2 = letter1
letter2 = "B"
print(letter1)

If you went with "A", you’re right. This time, when letter2 was assigned to the letter1 variable, letter2 was assigned the value of letter1, instantiating a new String object. You’re left with two String objects, as in figure 3.6.

Figure 3.6. Value types

Because you now have two separate String objects, making a change to one of them doesn’t affect the other.

Like Strings, when a structure is assigned to a new variable, it’s copied. Let’s look at the Color example again, but tweak one thing—it’s now a structure rather than a class (to be clear, let’s also rename it ColorStruct). Now, what is the value of color1.name that will be printed to the console in the following?

struct ColorStruct {
    var name = "red"
}
var color1 = ColorStruct()
var color2 = color1
color2.name = "blue"
print(color1.name)

If you predicted "red", you’re paying attention! Because structures are value types, when color2 was assigned color1, only the value of color1 was copied, two ColorStruct objects now exist, and any changes to color2 aren’t reflected in color1. Try it out in a playground and see for yourself!

Since Swift went open source, it’s been fascinating to explore how the language looks “under the hood.” One thing you’ll discover if you look at the source of Swift is that many of the core data types are implemented as structs, explaining why types such as String are value types. Incidentally, this represents a change in direction from Objective-C, where many types are implemented as classes (though references are implemented differently).

Constants

We’ve looked at constants in brief, but now’s a good time to look at them a little closer.

You undoubtedly are familiar with constants—they’re a special type of variable that will never be reassigned. In Swift, a constant is declared using the let keyword instead of var.

For example, if you assign an instance of a Person type to a constant, you can’t later assign another instance of the Person type to the same constant:

let person = Person(name: "Sandra")
person = Person(name: "Ian")                    1

  • 1 Error—can’t reassign constant
Tip

If a variable is never reassigned, for performance reasons you should declare it a constant.

Here’s a tricky question for you: is it permissible to modify a property of a constant of the Person type? For example:

person.name = "Ian"

If your answer was a confused expression and a shrug of the shoulders, you’re right!

Whether a property of a constant can be modified depends on whether you have a value type or a reference type, and I wasn’t clear in the question about whether Person was defined as a class or a structure. I did warn you it was going to be tricky!

For value types, the identity of the constant is tied up with the properties it contains. If you change a property, the variable is no longer the same value. For value types such as structures, it isn’t permissible to modify a constant’s properties.

For reference types, the identity of the constant is a reference to an object. There could be other constants or variables that point to that same object. For reference types such as classes, it’s permissible to modify a constant’s properties.

Which object type?

After learning the differences between classes and structures, the next question most people want the answer to is this: which should I use, and when?

To arrive at an answer of that complex question I find it helps to break it down into smaller questions:

  • Does the type need to subclass? The choice may be clear—sometimes your type needs to subclass; therefore, you need a class.
  • Should instances of this type be one of a kind? If you’re storing data in a type, and want any changes to that data to be reflected elsewhere, it might make sense to use a class.
  • Is the value tied to the identity of this type? Consider a Point type that stores an x and a y value. If you have two points that are both equal to (x:0, y:0), would they be equivalent? I suggest that they would. Therefore, the value is tied to its identity and it should probably be implemented as a structure. Now, consider an AngryFrog type that among other properties also contains an x and a y value. If you have two angry frogs that both are positioned at (x:0, y:0), would they be equivalent? I suggest probably not, because they’re probably two distinct entities, maybe traveling in different directions, or may be controlled by different players. The identity of an AngryFrog would be tied to a reference to a specific instance rather than the current values of its properties, and therefore it should probably be implemented as a class.

For a visual representation of this decision process, see figure 3.7.

Figure 3.7. Structure or class decision

A complex codebase may have additional factors to consider, but I find these three questions a handy guide to arrive at an answer to the structure or class decision.

Let’s practice this decision process with the Distance type you worked with earlier in the chapter:

  • Does the Distance type need to subclass? No, it doesn’t.
  • Should there be only one Distance object? No, there can be more than one.
  • Is the value equivalent to its identity? If you had two 100 km Distance objects, they should be treated as equivalent, so yes, the value is equivalent to identity.

Therefore, the Distance type should probably be implemented as a structure. Fortunately, changing a class to a structure or vice versa is straightforward. Swap the class keyword over for struct, and that’s often all that’s necessary. Go ahead and change the Distance class to a structure now.

We still haven’t looked at all the object types available in Swift. To make things even more interesting, you have yet another alternative to classes and structures, called enums. We’ll cover enums in chapter 10.

3.3. Extensions

We’ve looked at protocol extensions to add functionality to protocols. Extensions can also be used to add functionality to classes and structures.

There’s much that extensions can do, but they do have limitations:

  • Extensions can’t override functionality.
  • Extensions can add computed properties, but can’t add stored properties.
  • Extensions of classes can’t add designated initializers.

3.3.1. Extensions of your type

When we looked at the Distance class earlier in the chapter, we considered that at a later point we may want to add additional measurements. Well, the time has come! Let’s add feet to the Distance structure.

  1. Open your Distance playground again.
  2. Create an extension of your Distance structure.
    extension Distance {
    }
  3. Add a feet computed property.
    static let feetPerMile:Double = 5280
  4. Add type methods to your extension to convert to miles and kilometers from feet, or back again to feet from miles.
    static func toMiles(feet:Double)->Double {
        return feet / feetPerMile
    }
    static func toKm(feet:Double)->Double {
        return toKm(miles:toMiles(feet:feet))
    }
    static func toFeet(miles:Double)->Double {
        return miles * feetPerMile
    }
  5. You can set up a computed property now for feet.
    var feet:Double {
        get {
            return Distance.toFeet(miles:miles)
        }
        set(newFeet) {
            miles = Distance.toMiles(feet: newFeet)
        }
    }
  6. Finally, create an initializer for the Distance structure.
        init(feet:Double) {
            self.miles = Distance.toMiles(feet:feet)
        }
    }

Your Distance structure can now be initialized with feet and updated by setting feet.

Open

Compare your Distance extension with mine in the DistanceExtensions.playground.

Challenge

To confirm it’s now possible, create a new instance of Distance using feet, update this value, and then print this value to the console. Then extend the DistanceExtensions playground to include another form of measuring distance.

3.3.2. Extensions of their type

You aren’t limited to extending your own code. You can also extend classes, structures, or protocols of third-party code, or even of Apple frameworks or the Swift language!

As you saw in the previous chapter, the dictionary doesn’t contain a method to join with another dictionary. Let’s rectify this situation!

  1. Create a new playground, and call it Extensions.
  2. Add an extension to Dictionary so that it can add to another dictionary.
    extension Dictionary {                              1
        func add(other:Dictionary)->Dictionary {        2
            var returnDictionary:Dictionary = self      2
            for (key,value) in other {                  2
                returnDictionary[key] = value           2
            }                                           2
            return returnDictionary                     2
        }                                               2
    }

    • 1 Extends Dictionary
    • 2 Defines new method to extend Dictionary
  3. To confirm your new extension works, create two sample dictionaries ready to add together:
    var somelanguages = ["eng":"English","esp":"Spanish","ita":"Italian"]
    var moreLanguages = ["deu":"German","chi":"Chinese","fre":"French"]
  4. Now use your new method to join the two dictionaries:
    var languages = somelanguages.add(other:moreLanguages)

From now on, whenever you want to join two dictionaries in a project that contains this extension, the add method is available to you. Because this method is defined directly on the Dictionary structure, you didn’t need to define the datatypes of the key and value, making this method available for all Dictionary types.

Open

Compare your code in this section with mine in the Extensions playground.

3.3.3. Operator overloading

I’m not completely happy with the add method. It’s not intuitive that you’re returning the union of the two dictionaries, rather than adding one dictionary directly to the other. I think it would be clearer if you’d used the add (+) operator, the way you can with Arrays. Fortunately, Swift makes it possible to define or redefine operators! Redefining functionality for an operator is called operator overloading.

The + operator function receives a left and right parameter and returns a value of the same type.

  1. Redefine the add method in a Dictionary extension as an overloading of the + operator.
    func +(left: [String:String], right:[String:String]) -> [String:String] {
        var returnDictionary = left
        for (key,value) in right {
            returnDictionary[key] = value
        }
        return returnDictionary
    }
    Apart from how it’s defined, not much has changed from the body of the method. The data types of the key and value need to be specified because you’re no longer defining a generic Dictionary inside a Dictionary extension. Apart from that tweak, the code is similar, and you now can add two -Dictionarys (with key/value String/String) with the plus (+) operator, which is much more intuitive!
  2. You’ll still need two sample dictionaries to add together:
    var somelanguages = ["eng":"English","esp":"Spanish","ita":"Italian"]
    var moreLanguages = ["deu":"German","chi":"Chinese","fre":"French"]
  3. Add the two dictionaries together again, but this time use your overloaded add operator:
    var languages = somelanguages + moreLanguages
Challenge

Overload the == operator to determine whether two Distance objects are equivalent. Tip: The == operator returns a Bool value.

3.3.4. Generics

It’s a shame, however, that this new overloaded operator will only “operate” on a specific type of Dictionary—one with a key that’s a String, and a value that’s a String. What if you had another Dictionary with a key/value of Int/String? You’d need to define an overloaded operator again, for each combination of keys/values! How tiresome.

This is where a concept called generics is super useful. A generic can be substituted in a function for any type, but must consistently represent the same type. It turns a function that deals with a specific data type to a generic function that can work with any data type.

Pass in a list of generics between angle brackets <>, after the function or operator name. Like function parameters, generics can be given any name you like.

  1. Make the overloaded + operator for adding Dictionarys generic for any datatype for key or value.
    func +<Key,Value>(left: [Key:Value], right:[Key:Value]) -> [Key:Value]
    {    var returnDictionary = left
        for (key,value) in right {
            returnDictionary[key] = value
        }
        return returnDictionary
    }
  2. Again, you’ll need two sample dictionaries to add together.
    let somelanguages = ["eng":"English","esp":"Spanish","ita":"Italian"]
    let moreLanguages = ["deu":"German","chi":"Chinese","fre":"French"]
  3. Check your generic method still adds these dictionaries of with a String key and String value.
    var languages = somelanguages + moreLanguages
    Great, it still works! But will it add dictionaries of another type?
  4. Create two sample dictionaries of another type to check. Let’s try dictionaries with an Int key and String value:
    let someRomanNumerals =
     [1:"I",5:"V",10:"X",50:"L",100:"C",500:"D",1000:"M"]
    let moreRomanNumberals = [1:"I",2:"II",3:"III",4:"IV",5:"V"]
  5. Confirm your overloaded operator can now join this different type of Dictionary.
    var romanNumerals = someRomanNumerals + moreRomanNumberals

Generics are another powerful tool to add to your programmer’s arsenal. The Swift team themselves use them to define Arrays and Dictionarys, which is why you didn’t need to define the data type of the Dictionary when you extended it. You were already using this powerful feature!

3.4. Summary

In this chapter, you learned the following:

  • Use classes or structures to represent types.
  • Classes are reference types; structures are value types.
  • Use initializers to initialize values.
  • Use computed properties as getters and setters.
  • Consider protocols to share functionality between classes or structures.
  • Use extensions to add functionality to classes and structures.
  • Use operator overloading to redefine operators.
  • Use generics to make functions more flexible.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset