Classes are a very important part of any Object Oriented language, and Swift is no exception. When I explain classes to people, I usually think of one to describe a car. That’s a bit cliché, and done in a WWDC video, so let’s do something different.
Creating A Class in Swift
Let’s say we are making a chat program in Swift. It primarily sends messages in text format, but occasionally, it will also send images. We will first create our more general Message class, which we will specialize into those different formats later:
class Message { //Message code goes here }
Simple enough, so now, we need to set up some properties to describe this message. Well, all messages in this app will have a sender and a recipient, so we can add those. A timestamp would also be useful to have. Once the message is sent, those won’t change, so let’s create them as constants. We should probably also have a property denoting what status the message is (sent, received, or read), and that will change as time goes on, so that will be a variable. You can read more about constants and variables in my previous post, Swift Variables and Constants. You create properties by simply declaring variables at the class scope (outside of methods), like so:
class Message { let sender: String let recipient: String let timeStamp: Date var status = "Sent" }
One note, the status really should be an enumeration with the 3 states I mentioned above. For simplicity’s sake, I am using a Swift String since we are not here to discuss enumerations in this post. If you are curious, you can see my previous post Enumerations in Swift.
Now, if you type this into your Swift Playground directly, you will get the nice error “Class ‘message’ has no initializers”, and since we did not type one that is correct! Well, technically Swift can generate a blank initializer for classes. The default blank initializer does nothing about the stored properties though. All properties must have a valid value when a class is initialized, and since we did not give all of these default values, the blank initializer is insufficient. You could write your own and give default values inside it if you wanted though, but the auto-generated one won’t work.
Anyway, we should probably write an initializer shouldn’t we? In this program, I’m thinking that these message objects will be created when the user sends the message. At that point, the content of the message is ready, and we create the object with that content, and set the aforementioned properties. If you want to learn more about initializers, check out the post Class Initializers. For this class, we’ll write the simple one below:
init(sender: String, recipient: String) { self.sender = sender self.recipient = recipient timeStamp = Date() }
We gave the status a default value in the class definition, so it was not mentioned here (since I said a Message object was created when it is being sent, we gave it a default value of such). It took parameters denoting the sender and recipient, and stored those to the internal versions. You may notice that I named the inputs to the initializer the same as the name of the properties in this class. Obviously I cannot just say sender=sender, so I use the keyword self, and then use dot-syntax to access that property of self, to refer to the fact that I want the internal “sender” to be set to the property of the instance of this class’s “sender” property. I then set timeStamp to the current time/date with Date’s default initializer. I could use self.timeStamp if I wanted, but since I had no competing variable name, it was unnecessary.
Inheriting From Classes
Now, I said that these messages could take text of images as the actual content of the message. We currently have a message that contains the sender, recipient, time stamp, and status, but no content. We’ll make two more classes TextMessage and ImageMessage, and inherit those other capabilities from our message base class:
class TextMessage: Message { let content: String init(content: String, sender: String, recipient: String) { self.content = content super.init(sender: sender, recipient: recipient) } } class ImageMessage: Message { let content: UIImage init(content: UIImage, sender: String, recipient: String) { self.content = content super.init(sender: sender, recipient: recipient) } }
So, TextMessage and ImageMessage now subclass the Message class, getting all of the previous functionality we needed. They also add what they need, in this case their content being a Swift String or UIImage respectively. Maybe using Generics here would be a better choice, but this is not to make a full fledged chat program, but to show how classes work.
So, when we initialize your TextMessage, that initializer takes the 2 values we needed previously (sender and recipient), but also a value for content. We set that content to the (self.content) to what was sent in as a parameter. We then call our superclass’s initializer (the one we wrote earlier), and initialize it with the sender and recipient the TextMessage initializer received as a parameter.
You may notice, in these initializers, I am setting the value of a constant (a let property). That may look unusual since in general, you assign to let values when they are declared and nowhere else. A let variable must be assigned to exactly once. It must be given a value when our class is created, since it is property of the class, which means the only other place (if not given a default value), is in the initializer. You can even assign to it multiple times in the initializer if you REALLY want, but it must be set to something by the end of the initializer. This is in contrast to normal use of a constant in other situations, where once it is given a value, then it cannot be written to again. I am not sure exactly how this difference is achieved. I would guess that it waits to actually assign it to a permanent place in memory until the initializer is complete. Nonetheless, that is one special aspect of Swift initializers.
Update June 16, 2015: Since Swift 1.2 (or Xcode 6.3 beta 1), this is no longer the case. A constant in an initializer can ONLY be written to once. Previously, it could be written multiple times as long as it had a valid value by the end of the initializer. I’m not sure why someone would want to do that, but nonetheless, at this point it is no longer possible. The compiler will give you an error if you attempt to write to it multiple times stating that “Immutable value ‘propertyName’ may only be initialized once.”
Functions and Methods are not the same thing?
Now, there is a finer point about syntax that I have glossed over for a while. I have used the words function and method a lot in this blog but not fully defined them. I did talk about functions in my previous post Functions in Swift: Parameters and Return Types, but what exactly are methods?
The answer is actually rather simple, methods are functions of a type (in Swift, this includes classes, structures, or enumerations). All methods are functions, but not all functions are methods.
It also sounds a bit cliché, but it is true. For instance, in Swift, you can write a function within the scope of another function. If we wrote one inside of a method (a function directly associated with a type), would it make sense to call that a nested method? I cannot call it from the type’s context, only from that method of the type, so it isn’t directly associated with the type its enclosing method is. Also, in the playground, you can write functions at top level code, outside of classes, and since they are not tied to a class, they also cannot be called methods.
Inheriting and Overriding Methods
Another note about inheritance in Swift, it doesn’t work for just properties, it works for methods too. Subclasses can inherit, or even override methods. Subclasses can also override properties as well, but let’s just talk about methods for now. Let’s add these methods to our Message class:
func basicInfo()->String { return "Time: \(timeStamp.description), Sender: \(sender), Recipient: \(recipient)" } func printInfo() { print(basicInfo()) }
Without any extra work, these are already inherited by our subclasses, so we can instantiate a class, and call either of these directly from our subclass:
let someMessage = TextMessage(content: "Hello World!", sender: "Your App", recipient: "The World") someMessage.printInfo()
Now, let’s say we want to specialize the printInfo method, and have it show the content for our TextMessage class along with that additional information, we would have to override the old behavior and write a new one, which we do with the override keyword like so (in our TextMessage class):
override func printInfo() { print("\(basicInfo()), Content: \(content)") }
You must use the override keyword when overriding something from a superclass. It helps make your code safer by first, making sure you accidentally don’t override something you didn’t even know existed (like if you wanted some custom description method, but did not know description was already a valid function in your superclass). It also tells the compiler to verify that you actually ARE overriding something, I would guess to check for typos (like like trying to override “description”, but you actually wrote “descirption” (I inverted the r and the i in the middle).
Now when we call printInfo, it will call our subclass’s version of the method, which calls the superclass’s basicInfo for the first part of the Swift String, and appended the new information to the end. We could call super.basicInfo(), but since we didn’t override it, there was no need to specify which one, since there is only one.
Obligatory discussion about Classes being Reference Types
I have touched on this in a few different posts, but now that we are officially talking about classes, I should mention it again here.
Classes are reference types.
That means a few things. Firstly, if we assign our someMessage variable to another variable, then we are pointing to the same instance of the class. Unlike structs (such as the Swift Array) or enumerations, the values are not copied to the new variable. That means that if you change something in the otherMessage variable that we assigned someMessage to, we change someMessage as well.
Secondly, it means that I can, somewhat, mutate our constants, because the constant aspect is not the class instance itself. This was touched on in Swift Variables and Constants, but basically, when you assign your class to a variable, the object is created and put in memory, and your variable or constant stores a reference to that object. You cannot assign your constant stored value to point to another instance of that class. You can however, modify the values inside the object that is referenced.
This will be a bit clearer with a few examples, as shown below:
let someMessage = TextMessage(content: "Hi!", sender: "Me", recipient: "You") print(someMessage.status) //Output: "Sent" let otherMessage = someMessage otherMessage.status = "Received" print(someMessage.status) //Output: "Received" //Changing otherMessage changed what someMessage pointed to. someMessage = TextMessage(content: "Won't Work", sender: "Compiler", recipient: "You") //error: cannot assign to value: 'someMessage' is a 'let' constant
Just for reference, here is the entire code for our sample classes, so you can see everything in context:
class Message { let sender: String let recipient: String let timeStamp: Date var status = "Sent" init(sender: String, recipient: String) { self.sender = sender self.recipient = recipient timeStamp = Date() } func basicInfo()->String { return "Time: \(timeStamp.description), Sender: \(sender), Recipient: \(recipient)" } func printInfo() { print(basicInfo()) } } class TextMessage: Message { let content: String init(content: String, sender: String, recipient: String) { self.content = content super.init(sender: sender, recipient: recipient) } override func printInfo() { print("\(basicInfo()), Content: \(content)") } } class ImageMessage: Message { let content: UIImage init(content: UIImage, sender: String, recipient: String) { self.content = content super.init(sender: sender, recipient: recipient) } }
Conclusion
Classes are all over programs in Swift and Objective-C. Some of this may be review, especially at this point, but I thought it would be useful to work through a few concepts via a non-trivial example to show how classes are written and how inheritance works. Stay tuned for some other aspects about how classes work, particularly about initializers.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!