Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

24. Objects vs. Classes
Written by Eli Ganim

Time for something new. Up until now I’ve been calling almost everything an “object.” That’s not quite correct though. So, it’s time for you to brush up on your programming theory a bit more.

In this chapter, you will learn the following:

  • Classes: The difference between classes and objects.
  • Inheritance: What class inheritance is and how it works.
  • Overriding methods: Overriding methods in sub-classes to provide different functionality.
  • Casts: Casting an object from a subclass to its superclass — how (and why) you do it.

Classes

If you want to use the proper object-oriented programming vernacular, you have to make a distinction between an object and its class.

When you do this:

class ChecklistItem: NSObject {
  . . .
}

You’re really defining a class named ChecklistItem, not an object. An object is what you get when you instantiate a class:

let item = ChecklistItem()

The item variable now contains an object of the class ChecklistItem. You can also say: the item variable contains an instance of the class ChecklistItem. The terms object and instance mean the same thing.

In other words, “instance of class ChecklistItem” is the type of this item variable.

The Swift language and the iOS frameworks already come with a lot of types built-in, but you can also add types of your own by making new classes.

Let’s use an example to illustrate the difference between a class and an instance / object.

You and I are both hungry, so we decide to eat some ice cream (my favorite subject next to programming!). Ice cream is the class of food that we’re going to eat.

The ice cream class looks like this:

class IceCream: NSObject {
  var flavor: String
  var scoops: Int

  func eatIt() {
    // code goes in here
  }
}

You and I go on over to the ice cream stand and ask for two cones:

// one for you
let iceCreamForYou = IceCream()
iceCreamForYou.flavor = "Strawberry"
iceCreamForYou.scoops = 2

// and one for me
let iceCreamForMe = IceCream()
iceCreamForMe.flavor = "Pistachio"
iceCreamForMe.scoops = 3

Yep, I get more scoops, but that’s because I’m hungry from all this explaining.

Now the app has two instances of IceCream, one for you and one for me. There is just one class that describes what sort of food we’re eating — ice cream — but there are two distinct objects. Your object has strawberry flavor, mine pistachio.

The IceCream class is like a template that declares: objects of this type have two properties, flavor and scoops, and a method named eatIt().

The class is a template for making new instances
The class is a template for making new instances

Any new instance that is made from this template will have those instance variables and methods, but it lives in its own section of computer memory and therefore has its own values.

If you’re more into architecture than food, you can also think of a class as a blueprint for a building. It is the design of the building but not the building itself. One blueprint can make many buildings, and you could paint each one — each instance — a different color if you wanted to.

Inheritance

Sorry, this is not where I tell you that you’ve inherited a fortune. We’re talking about class inheritance here, one of the main principles of object-oriented programming.

Inheritance is a powerful feature that allows a class to be built on top of another class. The new class takes over all the data and functionality from that other class and adds its own specializations to it.

Take the IceCream class from the previous example. It is built on NSObject, the fundamental class for iOS frameworks. You can see that in the class line that defines IceCream:

class IceCream: NSObject {

This means that IceCream is actually the NSObject class with a few additions of its own, namely the flavor and scoops properties and the eatIt() method.

NSObject is the base class for almost all other classes in iOS frameworks. Most objects that you’ll encounter are made from a class that either directly inherits from NSObject, or from another class that is ultimately based on NSObject. You can’t escape it!

You’ve also seen class declarations that look like this:

class ChecklistViewController: UITableViewController

The ChecklistViewController class is really a UITableViewController class with your own additions. It does everything a UITableViewController does, plus whatever new data and functionality you’ve given it.

This inheritance thing is very handy because UITableViewController already does a lot of work for you behind the scenes. It has a table view, it knows how to deal with prototype cells and static cells, and it handles things like scrolling and a ton of other stuff. All you have to do is add your own customizations and you’re ready to go.

UITableViewController itself is built on top of UIViewController, which is built on top of something called UIResponder, and ultimately that class is built on NSObject.

This is called the inheritance tree.

All framework classes stand on the shoulders of NSObject
All framework classes stand on the shoulders of NSObject

The big idea here is that each object that is higher up performs a more specialized task than the one below it.

NSObject, the base class, only provides a few basic functions that are needed by all objects. For example, it contains an alloc method that is used to reserve memory space for the object’s instance variables, and a basic init method.

UIViewController is the base class for all view controllers. If you want to make your own view controller, you extend UIViewController. To extend means that you make a class that inherits from another one. Other commonly used terms are to derive from or to base on or to subclass. These phrases all mean the same thing.

UIViewController does way more than you’d think — you really don’t want to write all your own screen and view handling code. If you’d had to program each screen totally from scratch, you’d still be working on lesson #1!

Thank goodness that stuff has been taken care of by very smart people working at Apple and they’ve bundled it all into UIViewController. You simply make a class that inherits from UIViewController and you get all that functionality for free. You just add your own data and logic to that class and off you go! If your screen primarily deals with a table view, then you’d subclass UITableViewController instead. This class does everything UIViewController does — because it inherits from it — but is more specialized for dealing with table views. You could write all that code by yourself, but why would you, when it’s already available in a convenient package? Class inheritance lets you re-use existing code with minimal effort. It can save you a lot of time!

Superclasses and subclasses

When programmers talk about inheritance, they’ll often throw around the terms superclass and subclass.

In the example above, UITableViewController is the immediate superclass of ChecklistViewController, and conversely ChecklistViewController is a subclass of UITableViewController. The superclass is the class you derived from (or extended), while a subclass derives from your class.

Superclass and subclass
Superclass and subclass

A class in Swift can have many subclasses but only one immediate superclass. Of course, that superclass can have a superclass of its own. There are many different classes that inherit from UIViewController, for example:

A small portion of the UIKit inheritance tree
A small portion of the UIKit inheritance tree

Because nearly all classes extend from NSObject, they form a big hierarchy. It is important that you understand this class hierarchy so you can make your own objects inherit from the proper superclasses.

As you’ll see later on, there are many other types of hierarchies in programming. For some reason programmers seem to like hierarchies.

Do note that in Objective-C, all your classes must at least inherit from the NSObject class. This is not the case with Swift. You could also have written the IceCream class as follows:

class IceCream {
  . . .
}

Now IceCream does not have a base class at all. This is fine in pure Swift code, but you might run into troubled waters if you try to use IceCream instances in combination with iOS frameworks (which are written in Objective-C). So, sometimes you’ll have to use the NSObject base class, even if you’re writing the app in Swift only.

Inheriting properties (and methods)

Inheriting from a class means your new class gets to use the properties and methods from its superclass. If you create a new base class Snack:

class Snack {
  var flavor: String
  func eatIt() {
    // code goes in here
  }
}

And make IceCream inherit from that class:

class IceCream: Snack {
  var scoops: Int
}

Then elsewhere in your code you can do:

let iceCreamForMe = IceCream()
iceCreamForMe.flavor = "Chocolate"
iceCreamForMe.scoops = 1
iceCreamForMe.eatIt()

This works even though IceCream did not explicitly declare an eatIt() method or flavor instance variable. But Snack did! Because IceCream inherits from Snack, it automatically gets the method and instance variable for free.

Overriding methods

In the previous example, IceCream could use the eatIt() method implementation from Snack for free. But that’s not the full story! IceCream can also provide its own eatIt() method if it’s important for your app that eating ice cream is different from eating any other kind of snack (for example, you may want to eat it faster, before it melts):

class IceCream: Snack {
  var scoops: Int

  override func eatIt() {
    // code goes in here
  }
}

Now, when someone calls iceCreamForMe.eatIt(), this new version of the method in the IceCream class is invoked. Note that Swift requires you to use the override keyword in front of any methods that you provide that already exist in the superclass.

A possible implementation of this overridden version of eatIt() could look like this:

class IceCream: Snack {
  var scoops: Int
  var isMelted: Bool

  override func eatIt() {
    if isMelted {
      throwAway()
    } else {
      super.eatIt()
    }
  }
}

If the ice cream has melted, you want to throw it in the trash. But if it’s still edible, you’ll call Snack’s version of eatIt() using super.

Just like self refers to the current object, the super keyword refers to the object’s superclass. That is the reason you’ve been calling super in various places in your code, to let any superclasses do their thing.

Something that happens often in iOS frameworks is that methods are used for communicating between a class and its subclasses, so that the subclass can perform specific behavior in certain circumstances. That is what methods such as viewDidLoad() and viewWillAppear(_:) are for.

These methods are defined and implemented by UIViewController but your own view controller subclass can override them.

For example, when its screen is about to become visible, the UIViewController class will call viewWillAppear(true). Normally this will invoke the viewWillAppear(_:) method from UIViewController itself, but if you’ve provided your own version of this method in your subclass, then yours will be invoked instead.

By overriding viewWillAppear(_:), you get a chance to handle this event before the superclass does:

class MyViewController: UIViewController {
  override func viewWillAppear(_ animated: Bool) {
    // do your own stuff before super

    // don’t forget to call super!
    super.viewWillAppear(animated)

    // do your own stuff after super
  }
}

That’s how you can tap into the power of your superclass. A well-designed superclass provides such “hooks” that allow you to react to certain events.

Don’t forget to call super’s version of the method, though. If you neglect this, the superclass will not get its own notification and weird things may happen.

You’ve also seen override already in the table view data source methods:

override func tableView(_ tableView: UITableView,
           didSelectRowAt indexPath: IndexPath) {
  . . .
}

UITableViewController, the superclass, already implements these methods. So, if you want to provide your own implementation, you need to override the existing ones.

Note: Inside those table view delegate and data source methods, it’s usually not necessary to call super. The iOS API documentation can usually tell you whether you need to call super or not for an overridden method.

Subclass initialization

When making a subclass, the init methods require special care.

If you don’t want to change any of the init methods from your superclass or add any new init methods, then it’s easy: You don’t have to do anything. The subclass will automatically take over the init methods from the superclass.

Most of the time, however, you will want to override an init method or add your own. For example, to put values into the subclass’s new instance variables. In that case, you may have to override not just that one init method but all of them.

In the next app you’ll create a class named GradientView that extends UIView. That app uses init(frame:) to create and initialize a GradientView object. GradientView overrides this method to set the background color:

class GradientView: UIView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    backgroundColor = UIColor.black
  }
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }
  . . .
}

But because UIView also has another init method, init?(coder:), GradientView needs to implement that method too even if it doesn’t do anything but call super.

Also note that init(frame:) is marked as override, but init?(coder:) is required. The required keyword is used to enforce that every subclass always implements this particular init method.

Swift wants to make sure that subclasses don’t forget to add their own stuff to such required init methods, even if the app doesn’t actually use that particular init method, as in the case of GradientView — it can be a bit of an over-concerned parent, that Swift.

The rules for inheritance of init methods are somewhat complicated — the official Swift Programming Guide devotes many pages to it — but at least if you make a mistake, Xcode will tell you what’s wrong and what you should do to fix it.

Private parts

So… does a subclass get to use all the methods from its superclass? Not quite.

UIViewController and other UIKit classes have a lot more methods hidden away than you have access to. Often, these secret methods do cool things and it is tempting to use them. But they are not part of the official API, making them off-limits for mere mortals such as you and I.

If you ever hear other developers speak of “private APIs” in hushed tones and down dark alleys, then this is what they are talking about.

It is, in theory, possible to call such hidden methods if you know their names, but this is not recommended. It may even get your app rejected from the App Store, as Apple is known to scan apps for usage of these private APIs.

You’re not supposed to use private APIs for two reasons:

  1. These APIs may have unexpected side effects and not be as robust as their publicly available relatives.
  2. There is no guarantee these methods will exist from one version of iOS to the next. Using them is very risky, as your apps may suddenly stop working.

Sometimes, however, using a private API is the only way to access certain functionality on the device. If so, you’re out of luck. Fortunately, for most apps, the official public APIs are more than enough and you won’t need to resort to the private stuff.

So how do you mark your own methods as private, I hear you ask? This could get a bit complicated and is probably best left to a more detailed treatment of the subject. But in simple terms, similar to the @objc attribute you used in the previous chapter, there are other attributes that you can use to modify the access control level of Swift classes, methods, or properties.

Two of the most common are public and private. And hopefully, their names alone give you an understanding as to their intent. Since Swift 4.0, public is assumed by default. Which is why you have not had to prefix any of your classes or methods with this attribute.

private is what you need if you wanted to hide any of your classes, methods, or properties. But a discussion as to how private works in terms of what is hidden if you use the attribute and the advantages of doing so, might be a bit too broad a subject for now.

Casts

Often, your code will refer to an instance not by its own class but by one of its superclasses. That probably sounds very weird, so let’s look at an example.

MyLocations has a UITabBarController with three tabs, each of which is represented by a view controller. The view controller for the first tab is CurrentLocationViewController. Later on you’ll add two others, LocationsViewController for the second tab, and MapViewController for the third.

The designers of iOS obviously didn’t know anything about those three particular view controllers when they created UITabBarController. The only thing the tab bar controller can reliably depend on is that each tab has a view controller that inherits from UIViewController.

So, instead of talking to the CurrentLocationViewController class, the tab bar controller only sees its superclass part, UIViewController.

As far as the tab bar controller is concerned, it has three UIViewController instances and it doesn’t know or care about the additions that you’ve made to each one.

The UITabBarController does not see your subclasses
The UITabBarController does not see your subclasses

The same thing goes for UINavigationController. To the navigation controller, any new view controllers that get pushed on the navigation stack are all instances of UIViewController, nothing more, nothing less.

Sometimes that can be a little annoying. When you ask the navigation controller for one of the view controllers on its stack, it returns a reference to a UIViewController instance, even though that is not the full type of that object.

If you want to treat that object as your own view controller subclass instead, you need to cast it to the proper type.

Previously you did the following in prepare(for:sender:):

let controller = segue.destination as! 
                 ItemDetailViewController
controller.delegate = self

Here, you wanted to get the segue’s destination view controller — which is an instance of ItemDetailViewController — and set its delegate property.

However, the segue’s destination property won’t give you an object of type ItemDetailViewController. The value it returns is of the plain UIViewController type, which naturally doesn’t have your delegate property.

If you were write the above code without the as! ItemDetailViewController bit, like so:

let controller = segue.destination 

Then, Xcode would show an error for the line below it. Swift now infers the type of controller to be UIViewController, but UIViewController does not have a delegate property. That property is something you added to the subclass, ItemDetailViewController.

You know that destination refers to an ItemDetailViewController, but Swift doesn’t. Even though all ItemDetailViewControllers are UIViewControllers, not all UIViewControllers are ItemDetailViewControllers!

Just because your friend Chuck has no hair, that doesn’t mean all bald guys are named Chuck. Or, that all guys named Chuck have no hair, either!

To solve this problem, you have to cast the object to the proper type. You, as the developer, know this particular object is an ItemDetailViewController, so you use the as! cast operator to tell the compiler, “I want to treat this object as an ItemDetailViewController.”

With the cast, the code looks like this:

let controller = segue.destination as! ItemDetailViewController

Now, you can treat the value from controller as an ItemDetailViewController object. But… the compiler can’t check whether the thing you’re casting really is that kind of object. So, if you’re wrong and it’s not, your app will most likely crash.

Casts can fail for other reasons, too. For example, the value that you’re trying to cast may actually be nil. If that’s a possibility, it’s a good idea to use the as? operator to make it an optional cast. You must also store the result of the cast into an optional value or use if let to safely unwrap it.

Note that a cast doesn’t magically convert one type to another. You can’t cast an Int to a String, for example. You only use a cast to make a type more specific, and the two types have to be compatible for this to work.

Casting is very common in Swift programs because of the Objective-C heritage of the iOS frameworks. You’ll be doing a lot of it!

To summarize, there are three kinds of casts you can perform:

  1. as? for casts that are allowed to fail. This would happen if the object is nil or doesn’t have a type that is compatible with the one you’re trying to cast to. It will try to cast to the new type and if it fails, then no biggie. This cast returns an optional that you can unwrap with if let.
  2. as! for casts between a class and one of its subclasses. This is also known as a downcast. As with implicitly unwrapped optionals, this cast is potentially unsafe and you should only use as! when you are certain it cannot possibly go wrong. You often need to use this cast when dealing with objects coming from UIKit and other iOS frameworks. Better get used to all those exclamation marks!
  3. as for casts that can never possibly fail. Swift can sometimes guarantee that a type cast will always work, for example between NSString and String. In that case you can leave off the ? or the ! and just write as.

It can sometimes be confusing to decide which of these three cast operators you need. If so, just type “as” and Xcode will suggest the correct variant. You can rely on Xcode.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.