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

00:01You’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.

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

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

00:33First, 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

00:51Each 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.

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

01:10A 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

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

01:31So, 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)

01:47You’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.

02:02With 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(

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

02:40Delete this line.

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

class MuseumObject {
  ...
}

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

02:56The ‘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
}

03:42You 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.

03:55Now, 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)
}

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

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

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

object_pd.showImage()

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

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

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

object.showImage()

05:19Run the playground again:

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

05:34Structs and classes have other differences:

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

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

var title: String

05:58Also, change MuseumObject back to a struct:

struct MuseumObject {

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

var object2 = object_pd
object2.title = "Sunflowers"

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

06:28Because 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.

06:48What happens when MuseumObject is a class?

class MuseumObject {

05:19Run the playground again.

06:59Now 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.

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

let object2 = object_pd

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

05:19Run the playground again to confirm this.

07:36Now, add this method to MuseumObject:

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

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

object2.changeTitle(to: "Sunflowers")

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

08:10Change MuseumObject back to a struct.

08:21you get this error on title = newTitle:

10:54Cannot assign to property: ‘self’ is immutable

08:24Fix this by marking changeTitle as mutating:

mutating func changeTitle(to newTitle: String) {

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

08:36Well, 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.

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

08:56Now, 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.

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

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

object2 .showImage()

04:54Run the playground:

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

09:38Sometimes, 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

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

if object.isPublicDomain { }

10:12The 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):

10:34‘isPublicDomain’ is inaccessible due to ‘private’ protection level

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

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

07:02‘MuseumObject’ initializer is inaccessible due to ‘private’ protection level

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

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

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