Introduction to Object-Oriented Programming

Oct 17 2023 · Swift 5.9, iOS 17, Xcode 15

Lesson 02: Classes & Structs

Demo

Episode complete

Play next episode

Next
Transcript

You’re about to define a MuseumObject type for your MetMuseum app. In this demo, I’m using an Xcode playground and writing in Swift. If you’re following along, start up Xcode and open the module1-lesson2 playground in the starter folder.

It has a MuseumObject struct that you’ll fill in and a showImage() method stub.

There’s a commented-out initializer that you’ll need later and a MuseumObjectView that showImage() will use to display the art object’s image.

First, declare the properties your app will use:

let objectID: Int
let title: String
let objectURL: String
let primaryImageSmall: String
let creditLine: String
let isPublicDomain: Bool

Each instance of MuseumObject will have values for these properties. As you develop your app, you might need to add more properties, but these are enough for now.

MuseumObject now has all the properties that MuseumObjectView uses, so go ahead and uncomment it.

A struct definition is just a template for objects. It doesn’t do anything on its own. You have to instantiate an object — initialize it with parameter values

or pass it in as a parameter — and then your app can use the properties of each object, and each object can call showImage().

So, instantiate two objects — one in the public domain and the other not in the public domain. Copy this code from the transcript below this video:

let object_pd =
MuseumObject(objectID: 436535,
             title: "Wheat Field with Cypresses",
             objectURL: "https://www.metmuseum.org/art/collection/search/436535",
             primaryImageSmall: "https://images.metmuseum.org/CRDImages/ep/original/DT1567.jpg",
             creditLine: "Purchase, The Annenberg Foundation Gift, 1993",
             isPublicDomain: true)
let object =
MuseumObject(objectID: 13061,
             title: "Cypress and Poppies",
             objectURL: "https://www.metmuseum.org/art/collection/search/13061",
             primaryImageSmall: "",
             creditLine: "Gift of Iola Stetson Haverstick, 1982",
             isPublicDomain: false)

You’ve created two instances of the MuseumObject type, each representing different art objects. These instances are initialized with specific property values, which will be used when working with the data of these art objects.

With this set of properties, struct is a good choice for MuseumObject: All the properties are constant, and you’re not currently planning to implement any method that changes any values. Swift even defines the init method for structs, so you don’t have to. If you typed all the previous code instead of copy-pasting, you’ve seen this in practice. If not, try it now — start typing a new object creation:

let obj = MuseumObject(

You get a suggested auto-completion with an argument for each property, in the same order that you declared them.

Delete this line.

Now, suppose you decide to change MuseumObject to a class. Do this now:

class MuseumObject {
  ...
}

The playground flags an error. If yours is thinking about it for too long, click run.

The ‘MuseumObject’ class has no initializers. For a class, you need to define the init() method, so uncomment this code and add all your parameters:

init(objectID: Int,
     title: String,
     objectURL: String,
     primaryImageSmall: String,
     creditLine: String,
     isPublicDomain: Bool) {
  self.objectID = objectID
  self.title = title
  self.objectURL = objectURL
  self.primaryImageSmall = primaryImageSmall
  self.creditLine = creditLine
  self.isPublicDomain = isPublicDomain
}

You use self to differentiate the object’s properties from the parameter values. The arguments are in the same order as the property declarations, but that’s only so you don’t have to change the instantiations you already wrote.

Now, implement showImage():

if isPublicDomain {
  PlaygroundPage.current.setLiveView(MuseumObjectView(object: self))
} else {
  guard let url = URL(string: objectURL) else { return }
  PlaygroundPage.current.liveView = SFSafariViewController(url: url)
}

In a playground, you can either set the live view to a view, or to a view controller.

MuseumObjectView uses AsyncImage(url:) to download and display primaryImagesmall. SFSafariViewController, which you get by importing SafariServices, loads objectURL into an embedded Safari browser.

Now finally, each object can call showImage(). First, show the public domain image:

object_pd.showImage()

Run the playground. If nothing appears in your playground, stop it and run it again.

object_pd is in the public domain, so showImage() sets the playground’s live view to MuseumObjectView.

Now, stop the playground, comment out the line object_pd.showImage() and add this one:

object.showImage()

Run the playground again:

This time, object is not in the public domain, so showImage() loads its web page in a Safari browser.

Structs and classes have other differences:

  1. You can create subclasses of a class. This is inheritance, and it’s a topic in the next lesson.
  2. Structs are value types; classes are reference types.
  3. If a struct method modifies a struct object, you must mark it as mutating.

To demonstrate the second and third differences, change title to be variable:

var title: String

Also, change MuseumObject back to a struct:

struct MuseumObject {

Then, scroll down and add these lines below the object_pd instantiation:

var object2 = object_pd
object2.title = "Sunflowers"

Comment out object.showImage() and uncomment object_pd.showImage(), then run the playground.

Because MuseumObject is a struct, MuseumObjectView still displays the title Wheat Field with Cypresses because object2 is a copy of object_pd: Changing object2.title doesn’t affect object_pd.title.

What happens when MuseumObject is a class?

class MuseumObject {

Run the playground again.

Now that MuseumObject is a class, MuseumObjectView displays the title Sunflowers because object2 is the same object as object_pd: Changing object2.title changes object_pd.title.

When MuseumObject is a class, changing object2’s title works even if you declare it as a constant class object:

let object2 = object_pd

because the constant value is its location in memory, not its contents.

Run the playground again to confirm this.

Now, add this method to MuseumObject:

func changeTitle(to newTitle: String) {
  title = newTitle
}

And change object2.title = "Sunflowers" to this:

object2.changeTitle(to: "Sunflowers")

When MuseumObject is a class, this works the same as before.

Change MuseumObject back to a struct.

you get this error on title = newTitle:

Cannot assign to property: ‘self’ is immutable

Fix this by marking changeTitle as mutating:

mutating func changeTitle(to newTitle: String) {

The error goes away. Why do you need to tell the compiler this method mutates the struct object?

Well, when you declare object2 as a constant struct object — let object2 = object_pd — then every property in that struct object is constant. You mark changeTitle(to:) as mutating so Swift knows that a constant struct object isn’t allowed to call it.

When you change let to var, the error goes away.

Now, change MuseumObject to a class again. You get an error on mutating: ‘mutating’ is not valid on instance methods in classes. So mutating really sets structs apart from classes.

Change MuseumObject back to a struct. You want object2 to be a copy of object_pd for this next line of code.

Add this line and comment out the other two showImage() lines:

object2 .showImage()

Run the playground:

And here’s the changed title for this copy of object_pd.

Sometimes, you want to hide some of your object’s properties so they’re only visible within the struct or class. This prevents “outside” code from using or modifying these values. For example, set isPublicDomain to be private:

private let isPublicDomain: Bool

Making this property private doesn’t prevent showImage() from using it. But try typing this outside the struct:

if object.isPublicDomain { }

The first thing you’ll notice: isPublicDomain doesn’t show up in the auto-completion suggestions. There’s also an error message (click the run button if you don’t see it):

‘isPublicDomain’ is inaccessible due to ‘private’ protection level

Delete the if ... line and run the playground: There’s no problem passing a value to isPublicDomain in the initializer.

Comment out the init method and run the playground — there’s an error message!

‘MuseumObject’ initializer is inaccessible due to ‘private’ protection level

The auto-generated struct initializer doesn’t allow access to the private property but if you write your own, it’s OK.

That ends this demo. Continue with the lesson for a summary.

See forum comments
Cinema mode Download course materials from Github
Previous: Instruction Next: Conclusion