We’ve so far looked at 2 of the 3 named types in Swift with their own dedicated posts. First was Enumerations in Swift, and later was Classes In Swift — An Introduction. Now we finally talk about the third one, Structures.
Swift Structures are Value Types
Let’s just get that out there right at the beginning. Much like the enumeration, structures are a value type in Swift. That means that each time a structure is assigned to a constant, assigned to a variable, or passed to a function (which really is just assigning it to a constant (usually)), it is copied. So if you set the same structure to multiple variables, and then modify one, all of the others will not change, just that one, because each new variable was a copy of the original, not a reference.
Writing a Structure
Much like classes and enumerations in Swift, this is pretty simple. Let’s use a simple example, a point structure that contains an X and a Y coordinate:
struct XYPoint { var x: Int var y: Int }
Nothing particularly special there, it is pretty much exactly the same as a class in how you write it, except that the struct keyword is used.
You can then modify these properties via dot syntax, like so:
var somePoint = XYPoint(x: 1, y: 2) somePoint.x = 5 print(somePoint.x) //Outputs: "5"
So when we changed the x coordinate to 5, then told the program to print the value of X which correctly showed the new value of 5. One thing to note here, in Objective-C, you could read from properties inside a structure instance if you wanted (like the width and height values of CGSize). However, if you tried to set them, you would get an error. To modify them, you had to create a completely new instance os CGSize with the new values you wanted. In Swift, now you can change the values of the properties directly with dot syntax.
Initializers for Structs
Now, if this is your first time seeing a struct, you my say to me, “Hey, you skipped a step in the code here, you didn’t write an initializer yet, and yet you called one. I thought you did things step-by-step?”
Well actually, I did do this step by step, I didn’t write an initializer, the Swift compiler provided one for me. This is one of the major differences between structs and classes. In classes, if you have no properties, or all default values specified for them, you got a free empty initializer to create your class. With structures, you get a free member-wise initializer. We can of course write our own other designated initializers if you wish, but you get this one for free. If you do choose to make your own though, you loose the free member-wise initializer. This appears to be done under the assumption that if you need to do your own custom initializer, there is probably some special setup that needs to be done, so it disallows somebody from circumventing that by using the default member-wise initializer (whether they tried to use it purposefully or by accident). If you do want to keep the member-wise initializer, but also have your own, Apple’s iBook suggests writing the initializer via an extension. You can read more about them in my earlier article: Swift Extensions Part 1 — Computed Properties and Initializers.
Also, one thing to note is that I said you could write your own designated initializers. Structs can only have designated initializers, structs cannot have convenience initializers. They can have a similar functionality to them by calling different designated initializers. None of them are marked with the convenience keyword, like classes, though. If you want to call another initializer from an initializer, you must refer to the other one via dot syntax from self, like so:
init(x: Int, y: Int) { self.x = x self.y = y } //Initializer to make a point on the Y-Axis init(y inputY: Int) { self.init(x: 0, y: inputY) }
See how I also had to rewrite the member-wise initializer too? Calling one initializer from another is called initializer delegation. Initializer delegation is done for classes as well, but their version is significantly more complex due to class inheritance. One more thing about initializer delegation for value types, you cannot call self.init from anywhere else in you code, ONLY from other initializers.
You can read more about initializers in my previous article Class Initializers. It was originally discussing class initializers, but most of it still applies to structures.
Differences between Classes and Structs
In Swift, structures are a lot more like classes than they are in C or Objective-C. Now that I look at the list, what you can do in functions now is basically everything you can add to a type with an extension, like properties, methods, subscripts, initializers, and adopt protocols. There are a few capabilities though, that structures and classes do not share.
Major differences between Structures and Classes:
- Structures cannot Inherit from other types.
- Classes use type-casting to treat a class as a superclass or subclass, as well as check protocol adoption. Structs can only use the protocol adoption part.
- Structures do not have deinitializers.
- Structures cannot have multiple references to the same instance
Considering I said that it was a value type at the beginning though, that last point should come as no surprise. You can type-cast your struct to be of the “type” of a protocol it adopts, but since the rest of type-casting relates to subclasses or superclass, those capabilities don’t apply to structs.
Mutating Methods in Structures
We did cover this some already in my previous post Instance Methods and Type Methods in Swift, but since we’re making this the struct reference, I figured it would be best to show it here as well, with a different example:
struct RemoteInformation { var stringData = "" mutating func updateDataFromSomeWebService() { stringData = pretendToTalkToWebService() } func pretendToTalkToWebService() -> String { return "I'm new data from a web service!" } }
There are a few things that I would do different here if it was a production app (not to mention not have it call “pretendToTalkToWebService”. For one, I would probably set stringData to be private(set) variables so that only our updater can write to them. The example is very simplified to just highlight the mutating behavior, and not to show how to use Swift’s access controls or actually talk to a web service. The pretendToTalkToWebService is written just as a normal function, nothing special because it doesn’t mutate any state of the structure, it just returns something. The method updateDataFromSomeWebService though, that does rewrite the stringData, so I had to add the mutating keyword.
Sometimes though, it might be helpful to just overwrite the entire struct with a new structure. We can do that by just setting to the self property with a new instance of a struct, like so:
mutating func clearData() { self = RemoteInformation() }
In the first snippet, you can see that the property was given a default value. Since it was, I could use the blank initializer, because it would just assign the default value. It effectively let me clear the data by just making a whole new one with an empty stringData. This does seem like it gets around the initializer delegation issue mentioned earlier, but I guess the implication of initializer delegation is that you are calling an initializer of the same instance of your struct, while doing this just creates a completely new struct, and just replaces the original one.
Conclusion
So, when should you use a struct vs a class in Swift? Well, the two major differences that affect this decision most are the value/reference type difference, and the other is that classes can inherit from other ones.
If you want all references to the instance to be talking to the same instance, where any of them can update the properties, and any of the other references will immediately see that change, then a class would be a good candidate. If you need to take advantage of inheritance and let types be subtypes, like my Message/TextMessage example from a few posts ago, then you would also want a class.
However, if you wanted each instance to be completely independent from the others, and where you would much rather have the internal data copied than just have a reference to it, then you would want a struct. There is one caveat to this though, many Cocoa APIs need their parameters to be subclasses of NSObject, so you will need a class for types that need to interoperate with the original Cocoa APIs directly.
Apple’s iBook also mentions that the properties should be value types themselves and thus that they would in turn be copied when the structure is copied (as opposed to copping the reference to an object when copying the struct). An earlier version of my RemoteInformation example had the value timestamped with an NSDate, which is a subclass of NSObject, so that broke that rule. I did some testing and couldn’t really find a mutating function in NSDate that would affect this. Every function that I found that looked like it mutated just returned an entirely new object created from the mutation that would be set as the new reference. This effectively works like the copy behavior, where while it is technically possible to mutate these classes, no mutating methods were defined and each mutation just spawns an entirely new instance. Nonetheless though, it wasn’t really necessary to the example, so I just left it with the stringData property to highlight the mutation.
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!