Hallo vrienden, Leo hier. Today we will talk about structs, classes, and actors in the context of iOS developer interviews.
I’m very happy to announce that this is my first on the new platform and I have a lot of plans to expand this blog throughout the years. Thanks to everyone that supported me until now and let’s see where we go from here.
We will discuss a very common iOS interview question. I think this is one of the most basic interview questions but also one of the trickiest ones. Usually, the interviewer will ask you to say the differences between structs, classes, and actors, and for most positions is good enough to know about the value and reference semantics. This article assumes that you know about reference and value types, we will not explain those today.
But what if the interviewer comes up with follow-up questions? This post is about those follow-up questions and everything that could go wrong when trying to handle yourself in a stressful interview occasion. Today we will climb up a few more steps in our knowledge about structs, classes, and actors in iOS interviews.
But before we start…
Painting of The Day
The painting today is called A Serious Question by the painter Sergey Solomko. Was a Russian painter, watercolorist, illustrator, and designer. He was the son of Major-General Sergey Solomko (1835-1897), who was employed in the service of Grand Duke Konstantin Nikolayevich, and he grew up in the Konstantinovsky Palace.
After the Revolution, he fell out of favor with the new government, became a self-imposed exile, and began to work with those who had recently left Russia. Drawing on his earlier costume design experience, he created costumes for the dancers Mathilde Kschessinska and Anna Pavlova. He also created illustrations for books by French authors, including Émile Gebhart, Ernest Renan, and Albert Samain.
I chose this painting because of the name, when we are doing interviews all the questions seems so serious, right? The situation is stressful and the best way to deal with interviews, in my option, is just to take it as a learning opportunity. If you are there to learn, there’s no bad outcome, because you can always learn something even if is learning what you don’t know yet.
The Problem – Structs, Classes, and Actors in iOS Interviews
You are doing an iOS interview and the interviewer asks follow-up questions about structs, classes, and actors.
Before we start the follow-up questions let’s see some characteristics of each one of the types. First, let’s begin with Structs, our beloved first new structure introduced by Swift.
Structs in iOS Interviews
Well first of all structs are value types, which means that every time you use the struct reference in your code, it is potentially a new struct. I said potentially because it is only a new reference if you modify the struct. When you modify the struct the compiler uses a pattern called copy-on-write to combine a struct with a class, in which a struct wraps around a class. If you want to read more about copy-on-write you can check this article.
Check the example below:
human1 name Pepijn
human2 name Pepijn
human1 name Mike
human2 name Pepijn
To make you understand better the value semantics of a Struct value type, the below statements are equivalent :
human1.name = "Mike" // This
human1 = Human(name: "Mike") // Is equivalent to this
The last thing to say about value types is that structs cannot cause memory leaks, and by now you should be already thinking about why. Because of the value nature of structs, you can’t cause a dependency between two struct objects because, in the end, they are just… values. You are not referencing a memory address that depends on another memory address, they are just values passing around and they can be always safely deallocated because you don’t create a dependency between structs and other objects. This is why structs are recommended by Apple to be the default structure to start work with when creating new types.
Also, when working with structs you can’t have any inheritance. That means if you need to reutilize code from structs in other types, probably you should go with a class.
Structs can conform to protocols and because of that, you could use a combo of protocol + default implementations + structs to emulate the inheritance behavior.
To emulate inheritance in
structs you could do something like this:
Other structs characteristics are: can define subscripts, can be used with Generics and they have memberwise initializers. Memberwise initializer is a default initializer that Swift gives to you when you are using Structs. It creates an initializer function for you with all the non-initialized properties of the struct. In our example, Swift compiler created the initializer
init(name:String) automagically for us.
And talking about memory allocation they are saved into the Stack. Let’s go with the first follow-up question.
Stack Memory Allocation of Structs
The first follow-up question we will is: can you talk more about the stack memory allocation of structs? Of course Mr. Interviewer.
The stack is LIFO ( Last In First Out), meaning that the compiler only needs to take care of the pointer to the last object that was inserted in it. Needless to say that this is a surprisingly fast data structure to insert and remove items. In Swift each thread has its exclusive stack which means your objects there are thread-safe, no other thread can access that stack leading to the safeness of structs within its enclosure context.
For example, a struct created or passed on/to a function is added to the stack of the thread running that function, this leads to safeness when dealing with its values of it because the compiler can in compile time know how much memory it would be required to handle that context because you have everything in the stack allocation.
Because Swift uses a stack as its data structure for structs, the allocation and deallocation are cheap, you only need to move the stack pointer which is one instruction.
Classes in iOS Interviews
This is an old companion of iOS developers since Objective-C developers need to deal with the class types. Class is a reference type, which means every time you are dealing with a class object every context is pointing to the same address of that object. This is the reason that copy-on-write is only for value types because you don’t need to copy anything since when you change something in the class object, all references are updated immediately, which is not desired in value types.
Because we are dealing with references in the memory, classes can cause memory leaks in your code leading to a bigger memory consumption that should be needed, so be careful passing classes around your code.
Let’s recreate the struct example but now with classes and check the differences:
The first thing you can notice is that Swift doesn’t create the memberwise initializer in classes for us. It requires us to add one ourselves.
Now talking about the results just changing from Struct to Class cause the result tally different as you can see above. With structs when you pass your reference to another context and change the first object the second object isn’t modified, but working with classes both of the objects are modified because both point to the same reference in the memory.
Another feature of classes is having a deinitializer function (the
deinit), that is called before every time a class object will be removed from memory. This is good to make any final operations on that class. A good way to know if you have a memory leak is by using the
deinit of classes to check if they are being called when they should be.
Classes support inheritance, so a class can have children classes that can extend the capabilities of the root classes.
Child class has everything that
Human has, and can also override things, when possible, that humans do. I’ll not extend explaining this inheritance topic but maybe is something to dive into someday in the future.
Actors in iOS interviews
Actors in swift have a lot of tiny interesting new features. Actors are a new type in our beloved Swift language. They provide the same features as all of the named types in Swift. They can have methods, initializers, properties, subscripts, etc. They can conform to protocols to extend their types and be improved with extensions to help you organize your code.
The reason it was created was to avoid data races. Even though actors are reference types, by default Swift Runtime enforces thread control when accessing and mutating his properties.
Actors are by default thread safety and eliminate all kinds of racing conditions that your code could generate when manipulating your data types. When you chose actors you don’t need to use dispatch barriers or thread locks anymore, Swift solved that for you.
Now let’s talk about memory and discuss the memory allocation of a class or any other reference type, the heap.
Heap Memory Allocation of Classes
The heap is used when the calculation of the memory can’t be done in compile time. But why is that? Because as heap will deal with all the reference types you can’t guarantee how many references an object will have during its lifetime in the application. Alternatively from the value types stack memory, the heap doesn’t know if or how many times a user will reference a class during the application usage. This is why to clean the memory Swift utilizes ARC to remove unused objects automatically for us.
Heap is a global memory, different from the stack memory of value types in that each thread has its own exclusive stack therefore no global access, the heap is accessible throughout all the app objects. This leads to thread unsafety because you can have many threads accessing your heap objects at the same time. And remember, if any of the threads change the state of the object in the heap, that change will propagate to every other thread using it because is a reference type.
Heap is comparatively harder to manage on the Swift Runtime side because is a way more complex data structure than a stack and is slower than a stack. Usually, heaps have time complexity for insertion and deletion in O(log n) and peak min/max if you are using a min/max heap of O(1). A way different time complexity than stacks that have insertion and deletion in O(1) and peak O(1) also.
All this talk about memory leads to the next follow-up interview question, that’s about memory allocation of structs and classes.
What happens, in memory, if a struct is a property of a class?
As we studied above structs are stored in the stack, but classes are stored in the heap. So what happens now?
Well, the struct will be stored in the heap. Because even though the struct is stored in the stack by default it is a member of a class that has to live in the heap. So, in the end, the class saves a property that is a struct, and all those objects will be stored in the heap.
This doesn’t mean that the struct property will lose its value type semantics, it is more complex than that Swift doesn’t guarantee the destination of all objects because of their type, as you read above a struct could be stored in the heap if is inside a class. The only thing Swift guarantee is that a value type will always have the value type features, and the reference type will always have reference type features. The exact place in memory is not guaranteed, only its intrinsic features.
Talking about intrinsic features, now you know a lot about memory and the reference and value types, let’s discuss thread safety.
Which is thread-safe? Class, Struct, or Actor?
As you can imagine by now Struct and Actor are thread-safe.
The struct is because the threads have exclusive access to their stacks, therefore it is impossible to happen a race condition here.
Even though actors are reference types, Swift under the hood controls the access to their properties and this guarantees the thread safety. This is done by keeping a track of mutable and immutable properties of the actor.
This leads to the next question, what is the method dispatch type of each class and struct?
What is the Method Dispatch used by Struct and Class?
In a few words method dispatch is how a program selects which instructions to execute when looking up a function.
Structs use mostly static dispatch. That means that the compiler already knows the location of the function in the memory that it is looking for in the method dispatch process. This is the fastest one and can only happen in scenarios where the function can’t be changed, in other words, only possible when there’s no possibility of inheritance. I said mostly because, in some situations, structs would be forced to use dynamic dispatch, for example, structs being used inside escaping closures, used as a generic argument for functions, etc. I’ll not cover this topic deeply but keep in mind those things.
Classes use dynamic dispatch, which means that the compiler has to maintain a witness table( a table with function pointers) of all possible override/not override of that function and execute the right one. When a function is invoked, it has to search in runtime what is the right function for that function. When a class is subclassed the compiler creates a copy of that witness table for the subclass and appends all the new subclass functions and if a function overrides a superclass function, the new witness table is updated with the new memory address of the override function.
Clearly, dynamic dispatch is slower than static dispatch. Apple has a great article about increasing performance by reducing dynamic dispatch.
Ok so we talked a lot about classes and structs, but when should you use classes? Wouldn’t just be better only to use structs?
When should you use Class in Swift?
There are some reasons that you would want to use classes. Let’s enumerate them:
- You need to control the object’s identity. This means that you need to be sure that the object you send to do something, is exactly the same one in the memory that is returning. This is important when a shared mutable state is needed, which is point two.
- You need a shared mutable state feature in your app. For example, imagine that you have an app that is an e-commerce front end and you have a shopping cart with items. This cart of items should be the same throughout all the apps, right? This is a real-life scenario where the class is preferred.
- When copying doesn’t make sense. Imagine the e-commerce scenario above, you never want to copy the shopping cart because it will be always one in the app memory.
- You need object-c interoperability. As Objective-c doesn’t have value types, you are obligated to work with classes when bridging code from swift to objective-c and vice-versa.
- Inheritance is needed. The final reason is when you would like to reutilize code from one object and don’t want to use workarounds like protocols + default function implementations.
Wrap-up of Types Features
The table below shows the summary of the three types we studied today.
Check it now:
I couldn’t find how method dispatch works in actors. If anyone knows hows that work, please reach out!
Other Senior Interview Questions
If you are interested in this topic you can check the question about the point of synchronization in Swift. Maybe that is kinda outdated because I wrote that before
async/await feature was launched, but still is a valid answer. I want to explore more interview questions in the future.
Summary – Structs, Classes, and Actors in iOS Interviews
Today we studied a really common iOS interview question. If you think about it is a really basic concept of a Swift developer’s life, and I think it is important for everyone to know a little better what is going on under the hood.
We saw what are reference and value types, and what are the new actor types. Also, we described some reasons to use classes over structs and what is dynamic and static methods dispatch in Swift. We discussed thread safety using types in Swift and how you can expand your studies about them.
That’s all my people, today we finished the Architecture and iOS Tooling article closing the series about the beginning of iOS development. I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or if you are a company you can sponsor this blog. You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.
Thanks for reading and… That’s all folks.