Logo

The Techdegree Blog: Properties in Swift and what to do with them

A great amount of your code will contain variables and constants (from here on referred to as properties). In this post I would like to show you how to effectively create and use variables, and some features to make your life a little easier.

Creating properties

We all know how to create variables now. For everyone not sure here’s an example var users = [User]() This creates a variable called users which stores an array of User objects. We’re not going to cover what those users can do here, because it really doesn’t matter. This isn’t really interesting, is it? Well, here’s what might make this a little more interesting.

Inferring types

By default every property you create infers it’s type by looking up the data you assign to it. A variable var user = User() automatically knows that it can store User objects. Another way to define such a property would be to explicitly add the data type to the declaration like so var user: User = User(). As a matter of fact both declarations are valid and indeed identical. So, what’s the point of explicitly adding the data type to the property declaration, and shouldn’t we do it all the time? First of all, no. The only occasion where you should add the type is when the name doesn’t give a clear indication of said type. For example a height property that doesn’t store an Int or Double but a String. Instead of naming the property heightString you could add the data type to the declaration to make sure everyone knows what’s stored in there: var height: String = 185 //in cm. For all other occasions it’s our job to choose names that clearly indicate the data type stored, as discussed in my post Naming Conventions in Swift.

Lazily loaded properties

Lazily loaded properties are variables whose initial value is not calculated until the first time you use it. The declaration of such a variable only requires you to add the keyword lazy like so: lazy var users = [User](). Why we should care about this? Because it helps the system to determine what kind of data needs direct access, and what doesn’t. This can make a huge impact on performance if your app relies on a lot of different kinds of data or needs to execute a huge amount of work for the property to be initialized. To make this a little more interesting here’s an example that you can use inside every project. Let’s say you’ve got an app that lets the user take a picture. To add this feature we’re using the UIImagePickerController class that is part of UIKit. One way to set it up is to create a property inside your view controller and then add the configuration code inside viewDidLoad(). Another option would be to do it like that:

lazy var imagePicker: UIImagePickerController = {
  let imagePicker = UIImagePickerController()
  imagePicker.delegate = self        

  return imagePicker
}()

This sets up a basic camera control and returns it ready for you to use. If you don’t understand what exactly this does, basically it creates the image picker and inside the closure ({ }()) sets up everything related to the picker. Inside that same closure you can then do any additional setup, like for example limiting the picker to show the photo library. Then whenever you need to show it, you just need to call present(imagePicker, animated: true). Lastly, lazy properties need to be variables, because the initial value might not be retrieved until after initialization completes.

Stored properties

Stored properties are variables that belong to an instance of a type, e.g. a struct or a class. Stored properties don’t necessarily store any values until you initialize your type, but you can store default values in them. To declare stored properties you just define them as normal variables inside a type:

struct User {
  let name: String
  let age = 34
}

Computed properties

Computed properties don’t store any value. Instead they provide a way to set other properties and values indirectly. They are also able to execute small tasks for us. Let’s take a look at an example.

struct User {
	let firstName: String
	let middleName: String?
	let lastName: String
}

With this struct in place we want a method to generate the full name of the user. We could accomplish this in two different ways.

  1. Declare a variable at the call site that fetches the firstName, lastName and middleName, if applicable, and concatenate those.
  2. Declare a computed property which does all this for us and gives us direct access to the information we need.

According to the last example here’s the entire code:

struct User {
  let firstName: String
	var middleName: String?
	let lastName: String

	var fullName: String {
		guard let middleName = middleName else {
			return "\(firstName) \(lastName)"
		}

		return "\(firstName) \(middleName) \(lastName)"
	}
}

We need to safely unwrap the middle name here, because not everyone has a middle name. With this in place we can construct a user and ask for the middle name whenever we want to, like so:

var dennis = User(firstName: "Dennis", middleName: nil, lastName: "Parussini")
print(dennis.fullName)

But you aren’t limited to getting information from a computed property. The last example showed you a property that uses a “hidden” get keyword making this property read-only. You can also add a set keyword and then change the property’s value from outside like so:

var fullName: String {
  get {
		guard let middleName = middleName else {
			return "\(firstName) \(lastName)"
		}

		return "\(firstName) \(middleName) \(lastName)"
	}

	set {
		middleName = newValue
	}
}

With this in place we can go ahead and change the user we just created from anywhere in our app.

user.middleName = "The Great One"

Bonus information: Computed properties are also lazy properties under the hood, so adding a computed property in fact helps the system to stay performant while initializing your custom objects.

Type properties

Type properties are variables that belong to a certain type. Other than instance properties they are identical to every instance of a type. Let’s take a closer look at how to declare and use this type of property.

struct UserManager {
	static let allUsers = [User]()
}

This declares a struct that acts as a manager for all users of our app. Inside the type declaration we create a static constant that gives us access to all users. How do we use this constant? Simple: let allUsers = UserManager.allUsers. As you can see here, we don’t need to create an instance of the UserManager to get all users. This can be very useful if you need the same information in multiple places inside the same project. Type properties can also help when you need the same instance of a type across your app. This is called the “Singleton” pattern and is declared like so:

struct UserManager {
	static let shared = UserManager()
}

You might have already seen the shared or default keyword in conjunction with type declarations like so: NotificationCenter.default or UserDefaults.standard. Under the hood those types are singletons to make sure that we always refer to the exact same instance across our entire project.

Property Observers

Now we’re going to explore one of the most useful features related to properties. Property Observers work like the NotificationCenter API in that it gives you the ability to respond to property changes. Let’s take a look at an example: Imagine an app that fetches a list of posts from an external API and stores those in a property that belongs to a UITableViewController subclass like so: var posts = [Post](). Now every time you manipulate that array you want the table view to be reloaded. How can we achieve this? Of course you can call tableView.reloadData() every time somewhere in the view controller, after you add or remove an item from the posts array, or we add a property observer to the array like so:

var posts = [Post]() {
	didSet {
		tableView.reloadData()
	}
}

With this in place, whenever you change the array the table view will be updated automatically. Nice, isn’t it? Now, of course we don’t only have one method to update data. Let’s take a look at yet another example:

var posts = [Post]() {
	willSet {
		print(oldValue)
	}

	didSet {
		print(newValue)
	}
}

The difference between those two methods is simple, one will be executed before the array changes, the other one after the array has changed. And as a bonus we do get the old/new values completely for free. Thanks, Swift.

Conclusion

Properties are in fact one of the most important building blocks of every project. Knowing how and when to use what kind of variable will become very important in your future as a Swift developer. With this post I hope I could enlighten you at least a bit and, as always, if there’s anything I can help with don’t hesitate to contact me via the iOS Techdegree Slack channel or via Twitter.