Object Lesson - Learn iOS 8 App Development, Second Edition (2014)

Learn iOS 8 App Development, Second Edition (2014)

Chapter 6. Object Lesson

I’d like to take a break from app development for a chapter. Good iOS development requires conceptual and design skills that go beyond just knowing how to write for loops or connect a button to an outlet. Software engineers call these design patterns and design principles. To appreciate these philosophies, I’ll start with the foundation for it all: the object.

“Hey!” you say, “I’ve been using objects; what’s to understand?” You’d be surprised at the number of programmers who can’t describe exactly what an object is. If you haven’t had any questions about the terms used in this book so far (class, object, instance, function, stored property, and so on) and you’re already familiar with design patterns and principles, feel free to skip or skim this chapter. If you have questions, keep reading.

In this chapter, I will do the following:

· Give a brief history of objects and object-oriented programming

· Explain exactly what classes, objects, and instances are

· Describe inheritance and encapsulation

· Explain delegation and a few other design patterns

· Touch on a few key design principles

To appreciate objects, it helps to know what came before them and why they’re such a big deal.

Two Houses, Both Alike in Dignity

There are two basic types of information rattling around inside a computer. Data consists of the binary values that represent values and quantities, such as your name, a URL, or the airspeed velocity of an unladen swallow. Code consists of the binary values that represent instructions that tell the computer to do things such as draw your name on the screen, load a URL, or choose your favorite color.

It’s easy to see this division in computer languages. The syntax of a programming language, like Swift, is largely divided between statements that define, retrieve, and store values and statements that change values, make decisions, and invoke other statements. Think of them as the nouns and verbs of the computer’s language.

Like the Montagues and the Capulets,1 these two aspects of programming stayed separate for a long time. As computers got bigger and faster and computer programs got longer and more complicated, a number of problems began to develop.

Programmers encountered more solutions where multiple pieces of information needed to be kept together. A person doesn’t just have a name; they also have a height, an age, a tax identification number, and so on. To keep these related facts together, they started combining multiple values into a single block of memory called a structure. In the Swift programming language, a structure looks like this:

struct Person {
var name: String
var female: Bool
var birthdate: Date
var height: Float
var taxNumber: Int
}

These structures became so handy that programmers started to use them as if the whole thing was a single value. They would pass a Person to a function or store a Person in a file. They would write functions that operated on a Person structure (as opposed to a single value, like a date). An example is a function that determines whether it is that person’s birthday, like IsBirthdayToday( Person person ) -> Bool.

Programmers also started to encounter situations where there were lots of structures that resembled one another. A Player structure has all the same properties that a Person structure does, except that it has more variables for things like the player’s high score. They quickly figured out that they could create structures from structures, like this:

struct Player {
var person: Person
var gamesPlayed: Int
var highScore: Int
}

What got programmers really excited was that they could now reuse the functions they wrote for the Person structure for the Player structure!2 They even gave this idea a name: subtype polymorphism. You’ll get extra credit if you work that into a conversation at your next party.

Things should have been swell, but they weren’t. The number of structures and functions grew at a dizzying pace. Projects would have thousands and thousands of individual functions and nearly as many different structures. Some functions would work with Person structures, but most wouldn’t.

The problem was that data structures and functions were still in separate families; they didn’t mix. Trying to figure out what functions should be used with what structures became unmanageable. Programs didn’t work. Large software projects were failing. Something needed to happen—and it did.

Romeo Meets Juliet

In the late 1960s something magical happened: structures and functions got together, and the object was born. An object is the fusion of property values (the data structure) and the methods that act on those values (the functions) into a single entity that owns both. It seems so simple, but it was a dramatic turning point in the evolution of computer languages.

Before objects, programmers spent their days writing and calling functions (also called procedures), passing them the correct data structures. Computer languages that work this way are called procedural languages. When the concept of an object was introduced, it turned the way programmers wrote and thought about programs inside-out. Now the center of the programmer’s world is the object; you take an object and invoke its methods. These new computer languages are called object-oriented languages.

Objects also created programs that felt “alive.” A data structure is an inert collection of values, and a function is an abstract sequence of instructions, but an object is both; it’s an entity that has both characteristics and can do things when told. In this sense, objects are much more analogous to the kinds of things you deal with in the real world.

Now that you know what an object is, I’m going to give you a short course in how objects are defined and created and what that looks like in Swift. Chapter 20 describes this in much more detail.

Classes and Cookies

An object is the tangible embodiment of a class. An object’s class defines what properties that object can have and what actions it can perform. Objects are the things you actually work with. Think of it this way: classes are the cookie cutters, and objects are the cookies. See Figure 6-1.

image

Figure 6-1. Classes and objects

In Swift, a class is defined using a class declaration.

class MyClass {
// Class definition goes here
}

A class doesn’t do much by itself. A class is simply the “shape” used to create new objects. When you create an object, you do so by specifying the class of object you want to create and then telling the class to create one. In Swift, that code looks like this:

let object = MyClass()

The result of that expression is a new instance of a class, which is synonymous with an object. The object includes its own storage (data structure) where all of its individual property values are kept; changing the properties of one object won’t change the properties of any other object in the system.

Each object is also associated with a number of functions that act only on that class of objects. The class defines those functions, and every object of that class is endowed with those actions. In Swift, the code for those functions appears in the definition of the class.

class MyClass {
func doSomething() {
// Do something here
}
}

Functions that do their thing on a particular object (instance) of a class are called instance functions, instance methods, or just methods. Methods always execute in the context of a single object. When the code in a method refers to a property value or the special self variable, it’s referring to the property of the specific object it was invoked for. In Swift, methods are always called “on” an object, like this:

object.doSomething()

You’ll write and use methods almost exclusively in this book and in your day-to-day programming. You can also define methods for structures, where they work exactly like they do for classes. You can also define global functions that don’t execute in the context of an object (or structure) and work just like plain old C functions—back in the days before objects. These are described in more detail in Chapter 20.

Classes and Objects and Methods, Oh My!

A continual source of confusion for new developers is the profusion, and confusion, of terms used in object-oriented programming. Every programming language seems to pick a slightly different set of terms to use. Computer scientists use yet another vocabulary. Terms are often mixed up, and even seasoned programmers will use terms incorrectly, saying “object” when they really mean “class.”

Table 6-1 will help you navigate the world of object-oriented programming terms. It lists common Swift programming terms, their meaning, and some synonyms that you’ll encounter. I’ll explain most in more detail later in this chapter.

Table 6-1. Common Swift Terms

Term

Meaning

Similar Terms

Class

The definition of a class of objects. It defines what property values those objects can store and what functions they implement.

Interface, type, definition, prototype

Object

An instance of a class.

Class instance, instance

Property

A value stored in, or produced by, an object. (See stored property and computed property.)

Attribute

Stored property

A value stored in an object.

Instance variable (sometimes ivar)

Computed property

A value calculated or generated by an object.

Synthetic property

Method

A function that executes in the context of a single object.

Instance method, function, instance function, procedure, business logic

Global function

A function that executes outside the context of any particular object.

Class function, static function

Override

Supplanting the implementation of an inherited method with a different one.

Selector

A value that chooses a particular method (instance function) of an object.

Message

Send

Using a selector to invoke an object’s method.

Perform a function

Responds

Having a function that executes when sent a particular selector.

Implements, conforms

Client code

The code outside of the class that is using the public interface of that class or its objects.

User, client

Abstract

A class, property, or function that is defined or declared but has no useful functionality. This is used to define a concept that subclasses will implement in a meaningful way.

Abstraction layer, placeholder, stub

Concrete

A class, property, or method that does something and is usable.

By now you should have a solid understanding of the relationship between a class, its objects, and its properties and methods.

Inheritance

Earlier I mentioned that programmers found many situations where a class or structure that they needed was similar, possibly with only minor additions, to another object or structure that already existed. Furthermore, the methods they’d written for the existing object/structure were all applicable to the new one. This idea is called inheritance and is a cornerstone of object-oriented languages.

The idea is that classes can be organized into a tree, with the more general classes at the top, working down to more specific classes at the bottom. This arrangement might look like something in Figure 6-2.

image

Figure 6-2. A class hierarchy

In Figure 6-2, the generic Toy class serves as the base class of all the other classes. Toy defines a set of properties and methods common to all Toy objects. The subclasses of Toy are Ball and Vehicle. The subclasses of Vehicle are Train and RaceCar.

Note In Swift, inheritance is the bright line that separates classes from structures. A class inherits from other classes. A structure cannot inherit from another structure.

The Toy class defines a minimumAge property that describes the youngest age the toy is appropriate for. All subclasses of Toy inherit this property. Therefore, a Ball, Vehicle, Train, and RaceCar all have a minimumAge property.

Similarly, classes inherit methods too. The Vehicle class defines two functions: start() and stop(). All subclasses of Vehicle inherit these two functions, so you can call the start() function on a Train and the stop() function on a RaceCar. The bounce() function can be called only on a Ball.

Through inheritance, every object of type Vehicle and every object of every subclass of type Vehicle will have a start() and stop() function. This is what computer scientists call subtype polymorphism. It means that if you have an object, parameter, or variable of a specific type (say, Vehicle), you can use or substitute any object that’s a subclass of Vehicle. You can pass a function that expects a Vehicle parameter a Train or a RaceCar object, and that function can call start() on the more complex object just as effectively. A variable that refers to aToy can store a Toy, a Ball, or a Train. A variable that refers to a Vehicle, however, cannot be set to a Ball because a Ball is not a subclass of Vehicle.

You’ve already seen this in the apps you’ve written. NSResponder is the base class for all objects that respond to events. UIView is a subclass of NSResponder, so all view objects respond to events. The UIButton is a subclass of UIView, so it can appear in a view, and it responds to events. A UIButton object can be used in any situation that expects a UIButton object, a UIView object, or an NSResponder object.

Abstract and Concrete Classes

Programmers refer to the Toy and Vehicle classes as abstract classes. These classes don’t define usable objects; they define the properties and methods common to all subclasses. You’ll never find an instance of a Toy or Vehicle object in your program. The objects you’ll find in your program are Ball and Train objects, which inherit common properties and methods from the Toy and Vehicle classes. The classes of usable objects are called concrete classes.

Overriding Methods

Starting a train is a lot different than starting a car. A class can supply its own code for a specific function, replacing the implementation it inherited. This is called overriding a method.

As an example, all subclasses of UIViewController inherit a supportedInterfaceOrientation() function. This returns an Int describing which device orientations (portrait, landscape left, landscape right, or upside down) that view controller supports. The version ofsupportedInterfaceOrientations() supplied by UIViewController is generic and assumes your view controller supports all orientations on a regular device and all but upside down on a compact device. As a programmer, you can overridesupportedInterfaceOrientations() to describe exactly what orientations your view controller allows.

Sometimes a class—particularly abstract classes—will define a function that doesn’t do anything at all; it’s just a placeholder for subclasses to override. The Vehicle class methods start() and stop() don’t do anything. It’s up to the specific subclass to decide what it means to start and stop, as shown in Figure 6-3.

image

Figure 6-3. Overriding a function in subclasses

You’ve been doing this since Chapter 3. The UIViewController class defines the function viewDidLoad(). This function doesn’t do anything. It’s just a placeholder function that gets called just after the view controller’s view objects have been created. If your view controller subclass needs to do something else to set up your view objects, your class would override viewDidLoad() and perform whatever it is you need it to do.

If your class’s method also needs to invoke the method defined by its superclass, Swift has a special syntax for that. The super keyword means the same thing as self, but functions called on super go to the function defined by the superclass (ignoring the function defined in your class), as if that function had not been overridden.

super.viewDidLoad()

This is a common pattern for extending (rather than replacing) the behavior of a function. The overriding function calls the original function and then performs any additional tasks.

Caution Sometimes the superclass’s implementation does do something important, so your override function must call the superclass’s version before it returns. These cases are usually noted in the function’s documentation.

Design Patterns and Principles

With the newfound power of objects and inheritance, programmers discovered that they could build computer programs that were orders of magnitude more complex than what they had achieved in the past. They also discovered that if they designed their classes poorly, the result was a tangled mess, worse than the old way of writing programs. They began to ponder the question “What makes a good class?”

A huge amount of thought, theory, and experimentation has gone into trying to define what makes a good class and the best way to use objects in a program. This has resulted in a variety of concepts and philosophies, collectively known as design patterns and design principles. Designpatterns are reusable solutions to common problems—a kind of programming best practices. Design principles are guidelines and insights into what makes a good design. There are dozens of these patterns and principles, and you could spend years studying them. I’ll touch on a few of the more important ones.

Encapsulation

An object should hide, or encapsulate, its superfluous details from clients—the other classes that use and interact with that class. A well-designed class is kind of like a food truck. The outside of the truck is its interface; it consists of a menu and a window. Using the food truck is simple: you choose what you want, place your order, and receive your food through the window. What happens inside the truck is considerably more complicated. There are stoves, electricity, refrigerators, storage, inventory, recipes, cleaning procedures, and so on. But all of those details are encapsulated inside the truck.

Similarly, a good class hides the details of what it does behind its public interface. Properties and methods that the clients of that object need should be declared normally. Everything else should be “hidden” using the private keyword. You can also “hide” functions in extensions. Access scope and extensions are explained in Chapter 20.

This isn’t just for simplicity, although that’s a big benefit. The more details a class exposes to its clients, the more entangled it becomes with the code that uses it. Computer engineers call this a dependency. The fewer dependencies, the easier it is to change the inner workings of a class without disrupting how that class is used. For example, the food truck can switch from using frozen French fries to slicing fresh potatoes and cooking them. That change would improve the quality of its French fries, but it wouldn’t require it to modify its menu or alter how customers place their order.

Singularity of Purpose

The best classes are those that have a single purpose. A well-designed class should represent exactly one thing or concept, encapsulate all of the information about that one thing, and nothing else. A method of a class should perform one task. Software engineers call this the single responsibility principle.

A button object that starts a timer has limited functionality. Sure, if you need a button that starts a timer, it would work great. But if you need a button that resets a score or a button that turns a page, it would be useless. On the other hand, a UIButton object is infinitely useful because it does only one thing: it presents a button the user can tap. When a user taps it, it sends a message to another object. That other object could start a timer, reset a score, or turn a page.

Great objects are like Lego blocks. Create objects that do simple, self-contained tasks and connect them to other objects to solve problems. Don’t create objects that solve whole problems. I’ll discuss this more in Chapter 8.

Stability

A ball should be usable all of the time. If you picked up a ball, you would expect it to bounce. It would be strange to find a ball that wouldn’t bounce until you first turned it over twice or had to paint it a color.

Strive to make your objects functional regardless of how they were created or what properties have been set. In the ball example, the bounce() function should work whether the minimumAge property has been set or not. Software engineers call these preconditions, and you should keep them to a minimum.

Open/Closed

There are two corollaries to the single responsibility principle. The first is the so-called open/closed principle: classes should be open to extension and closed to change. Are you thinking “Huh?” Well, you’re not alone; this is a strange one to grasp. It basically means that a class is well designed if it can be reused to solve other problems by extending the existing class or methods, rather than changing them.

Programmers abhor change, but it's the one constant in software development. The more things you have to change in a class, the more chance that it’s going to affect some other part of your project adversely. Software engineers call this coupling. It’s a polite way of saying that by changing one thing, you’ll create a bug somewhere else. The open/closed principle tries to avoid changing things by designing your classes and methods so you don’t have to change them in the future. This takes practice.

Using the toy classes again, let’s pretend your app has been getting great reviews and now you want to add two new vehicle toys: an electric dune buggy and an electric Transformer robot. Both will be subclasses of Vehicle, but both also need new properties common to electric vehicles, like a battery level.

It might be tempting to add a batteryLevel property to your Vehicle class, as shown on the left in Figure 6-4, and then create your new subclasses. This changes your existing Vehicle class. Because of inheritance, it indirectly changes your Train and RaceCar classes too. There’s now a real possibility that you’ve introduced a bug or other complication into three classes that were previously working just fine.

Instead, consider creating a new ElectricVehicle subclass with the new batteryLevel property and then make DuneBuggy and Transformer subclasses of this new intermediary class. See the right side of Figure 6-4. Notice that you’ve added the new batteryLevelproperty for all the subclasses that need it and created your new toy classes, but without making any changes to your existing Vehicle, RaceCar, and Train classes. If these classes were working correctly before, they should still be after the addition.

image

Figture 6-4. Imact of changing existing classes

The open/closed principle invites you to think about how your app will grow in the future. The trick is to design your classes so that new classes and features can be added without modifying the classes you’ve already written and debugged. So, as you design your classes, think a little beyond the code you’re writing today to the code you might want to add tomorrow. If you have a class that handles four different record types or twenty different shapes, ask yourself, “How would I add a fifth record type or nine new shapes without altering the class I just finished?”

Delegation

Another lesson of the single responsibility principle is to avoid mixing in knowledge or logic that’s beyond the scope of your object. A ball has a bounce() function. To know how high the ball will bounce, the function must know what kind of surface the ball is bouncing against. Since this calculation has to be made in the bounce() function, it’s tempting to include that logic in the Ball class. You might do this by adding a howHigh() function that calculates the height of a bounce.

That design decision, unfortunately, leads you down a crazy path. Since the bounce calculation varies depending on the environment, the only way to modify the calculation is to override the bounce() method in subclasses. This forces you to create subclasses such as BallOnWood,BallOnConcrete, BallOnCarpet, and so on. If you then want to create different kinds of balls, such as a basketball and a beach ball, you end up subclassing all of those subclasses (BeachBallOnWood, BasketBallOnWood, BeachBallOnCarpet, and on and on). Your classes are spiraling out of control, as shown in Figure 6-5.

image

Figure 6-5. Subclassing “solution”

A design pattern that avoids this mess is the delegate pattern. As you’ve seen, the delegate pattern is used extensively in the Cocoa Touch framework. The delegate pattern defers—delegates—key decisions to another object so that logic doesn’t distract from the single purpose of the class.

Using the delegate pattern, you would create a surface property for the ball. The surface property would connect to an object that implements a bounceHeight(ball:) function. When the ball wants to know how high it should bounce, it calls the delegate functionbounceHeight(ball:), passing itself as the ball in question. The Surface object would perform the calculation and return the answer. Subclasses of Surface (ConcreteSurface, WoodSurface, CarpetSurface, GrassSurface) would overridebounceHeight(ball:) to adjust its behavior, as shown in Figure 6-6.

image

Figure 6-6. Delegate solution

Now you have a simple, and flexible, class hierarchy. The abstract Ball class has BasketBall and BeachBall subclasses. Any of these can be connected to any of the Surface subclasses (ConcreteSurface, WoodSurface, CarpetSurface, GrassSurface) to provide the correct physics. This arrangement also preserves the open/closed principle: you can extend Ball or Surface to create new balls or new surfaces, without changing any of the existing classes.

Other Patterns

There are many, many other design patterns and principles. I don’t expect you to memorize them—just be aware of them. With an awareness of design patterns, you’ll begin to notice them as you see how classes in the Cocoa Touch framework and elsewhere are designed; iOS is a very well-designed system.

Here are other common patterns you’ll encounter:

· Singleton pattern: A class that maintains a single instance of an object for use by the entire program. The UIApplication.sharedApplication() function returns a singleton.

· Lazy initialization pattern: Waiting until you need an object (or a property) before creating it. Lazy initialization makes some things more efficient and reduces preconditions. UITableView lazily creates table cell objects; it waits until the moment it needs to draw a row before asking the data source delegate to provide a cell for that row.

· Factory pattern and class clusters: A method that creates objects for you (instead of you creating and configuring them yourself). Often, your code won’t know what object, or even what class of objects, needs to be created. A factory method handles (encapsulates) those details for you. The NSURL.URLWithString(_:) function is a factory method. The class of the NSURL object returned will be different, depending on what kind of URL the string describes.

· Decorator pattern: Dress up an object using another object. A UIBarButtonItem is not, ironically, a button object. It’s a decorator that may cause a button, cause a special control item, or even change the positioning of controls in a toolbar.

There are, of course, many others.

The first major book of design patterns (Design Patterns: Elements of Reusable Object-Oriented Software) was published in 1994 by the so-called “Gang of Four”: Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Those patterns are still applicable today, and design patterns have become a “must-know” topic for any serious programmer. The original book is not specific to any particular computer language; you could apply these principles to any language, even non-object-oriented ones. Many authors have since reapplied, and refined, these patterns to specific languages. So if you’re interested in learning these skills primarily for Swift, for example, look for a book on design patterns for Swift.

Note An interesting offshoot of design patterns has been the emergence of anti-patterns: programming pitfalls that developers repeatedly fall into. Many anti-patterns have entertaining names like “God object” (an object that does too much) and “Lasagna code” (a software design with too many layers). See http://en.wikipedia.org/wiki/Anti-patterns for their history and other examples.

Summary

That was a lot of theory, but it’s important to learn these basic concepts. Understanding design pattern and principles will help you become a better software designer, and you’ll also appreciate the design of the iOS classes. Observe how iOS and other experienced developers solve problems, identify the principles they used, and then try to emulate that in your own development.

Theory is fun, but do you know what’s even more fun? Cameras!

____________________

1The Montagues and the Capulets were the two alienated families in the play Romeo and Juliet. I mention this in case your reading list is skewed toward Jules Verne and not William Shakespeare.

2You can’t pass a Player structure to a function that expects a Person structure in Swift, even though the Player structure begins with a Person structure. You can do that in more primitive languages, like C, which is where many of these early programming ideas germinated.