This chapter covers
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.
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).
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.
class Distance { }
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.
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, 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.
class Distance { static let kmPerMile = 1.60934 }You could then retrieve or set this type property directly on the type.
print ("2 miles = (Distance.kmPerMile * 2) km")
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.
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.
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.
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.
class Distance { static let kmPerMile = 1.60934 var miles:Double init(miles:Double) { 1 self.miles = miles 2 } }
var distance = Distance(miles: 60)
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.
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 } }
In case we need to calculate kilometers again, it may make sense to move this calculation to a method.
If all properties of a class have default values, Xcode will synthesize a default initializer automatically for you with no arguments.
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 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.
func displayMiles()->String { return "(Int(miles)) miles" }
var distance = Distance(miles: 60) print(distance.displayMiles()) //prints "60 miles" to consoleYou 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.
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 }
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 }
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.
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.
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.
init(miles:Double) { self.miles = miles self.km = Distance.toKm(miles:miles) }
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.
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.
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.
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 } }
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.
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 }
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.
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 } }
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.
distance.km = 90 1
var km:Double { get { 1 return Distance.toKm(miles:miles) } }
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.
var distance = Distance(km: 100) distance.km = 35 distance.miles = 90
print("Distance is (distance.miles) miles") print("Distance is (distance.km) km")
Mission complete!
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.
Confirm in the results sidebar that the distance object is instantiating, updating, and displaying correctly using miles or kilometers.
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.
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.
This example isn’t intended to be comprehensive. If I listed everything a smart phone could do, I’d be here all day!
You could model these relationships with classes. Subclasses indicate their superclass with a colon after their name, as shown in the following listing.
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 } } //...
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() }
Explore the rest of the code in the Telephone-ClassInheritance.playground.
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.
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.
override func makeCall() { super.makeCall() //make cellular call }
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.
The trend in pure Swift has moved away from class inheritance and toward implementation of 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 }
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 } }
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() }
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 } }
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.
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 } }
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.
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:
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.
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.
Explore the protocol relationships in code in the TelephoneProtocols.playground.
Add a Television type that shares a VideoPlayable protocol with iPhones, Androids, and Windows phones.
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
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()
Structures have three main differences from classes worth noting:
Each of these is explained in the following sections.
Structures can’t inherit other structures. They can indirectly inherit functionality, however, by adopting protocols, which, as you’ve seen, can inherit other protocols.
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)
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.
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).
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.
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.
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).
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
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.
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:
For a visual representation of this decision process, see figure 3.7.
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:
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.
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:
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.
extension Distance { }
static let feetPerMile:Double = 5280
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 }
var feet:Double { get { return Distance.toFeet(miles:miles) } set(newFeet) { miles = Distance.toMiles(feet: newFeet) } }
init(feet:Double) { self.miles = Distance.toMiles(feet:feet) } }
Your Distance structure can now be initialized with feet and updated by setting feet.
Compare your Distance extension with mine in the DistanceExtensions.playground.
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.
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!
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 }
var somelanguages = ["eng":"English","esp":"Spanish","ita":"Italian"] var moreLanguages = ["deu":"German","chi":"Chinese","fre":"French"]
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.
Compare your code in this section with mine in the Extensions playground.
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.
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!
var somelanguages = ["eng":"English","esp":"Spanish","ita":"Italian"] var moreLanguages = ["deu":"German","chi":"Chinese","fre":"French"]
var languages = somelanguages + moreLanguages
Overload the == operator to determine whether two Distance objects are equivalent. Tip: The == operator returns a Bool value.
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.
func +<Key,Value>(left: [Key:Value], right:[Key:Value]) -> [Key:Value] { var returnDictionary = left for (key,value) in right { returnDictionary[key] = value } return returnDictionary }
let somelanguages = ["eng":"English","esp":"Spanish","ita":"Italian"] let moreLanguages = ["deu":"German","chi":"Chinese","fre":"French"]
var languages = somelanguages + moreLanguagesGreat, it still works! But will it add dictionaries of another type?
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"]
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!
In this chapter, you learned the following: