… or where does the data go in the end
As part of my current devslopes ios academy I try to invest as much time as possible into learning swift. In theory. In reality I have a fulltime job as a freelance java developer in one of the rather complicated areas, which is german institutional reporting. And this project takes a rather big toll on me right now. Which is ok, since I really do like to work this way. But sitting down and more or less going to school at the end of an ordinary stressfull day is not always what I manage to do. Having two kids with their own learning difficulties, doing the cooking during the pandemic while additionally remodeling the house is in sum rather demanding for me and my wife, which works as a part time teacher.
Nevertheless at least at the weekends I normally find the time to dig into the course.
Not as fast as the other participants, but the great thing about this course is that you do it in your own speed. After almost a year, I managed to advance into week 4 of the course.
In the week 4 curriculum I came across the section 3.7 of the apple book “App development with swift”, which is about tab bar controllers. And to fully dive into this I also worked on the navigation controllers (chapter 3.6) with its lab, and then finally on the lab in chapter 3.7.
This lab is actually a rather simple one. Create an “About me” application with a couple of views, each representing a different aspect of your life. I started with the basic data. A picture of me, and some basic informations. Since I had successfully used the table view in a former part of the curriculum I decided to try to pack the informations into a table view. So my basic setup for the AbboutMe-view looked something like this:
The view was supposed to be as simple as possibe. An image view, containing a picture of me, followed by a table, containing basic informations about me.
This tables prototype cell shows a specific information with a label in each line. Like “First name: Gunnar”
And the code setup was as simple as possible:
- A struct “AboutMe” containing the informations to show in the table view
- A class “AboutMeDataSource”, which acts as a datasource for the table
- A struct “InformationWithLabel” which contains the informations for a single line.
The viewController linked all this together in its viewDidLoad-Method:
After firing up the simulator the app showed, well, nothing:
Retried it with the same result. Cleaned the project with a full rebuild, same result. Checked the settings of the tableview, nothing out of the ordinary here. Placed some breakpoints through in the viewDidLoad function and the AboutMeDataSource. They viewDidLoad breakpoints get hit as expected, but not the breakpoints in the AboutMeDataSource. Why? The datasource does not contain any magic:
I compared this simple application to the other application in which I had successfully used the table view. But everything seemed to be the same. This other app also uses a datasource, and in the same way connected the table view with the datasource in the viewDidLoad.
This sort of became one of my personal Mines of Moria situations, to paraphrase Lord of the rings. These situations appear rather frequently in my life. There is always a hard choice for me to make. Walk through the mines with the regular cuts and bruises, or take the easy way. Whenever I choose the easy way I will later on regret it. The easy way in this situation would be to just dump the table view and replace it with a concrete set of fields, laid out in a standard stack view. But then I would not have known what the reason for this problem was. So I decided to take the route through the mines.
As of now, I had to find the magic words which would open the hidden gateway into the mines. Speak “friend” and enter. But no matter, in which language I spoke “Friend”, the magic doors wouldn’t open.
I deleted the scene, and recreated it (tried turning it off and on today…). Recreated the view controller as a UITableViewController. Again, the same result. The table would not fill, the breakpoints would not hit. I asked the holy oracle, know as stackoverflow. Which only told me what I already knew. Take the instance of the class that fulfills the UITableViewDataSource protocol, and put it into the tableView.datasource property. Nothing fancy here.
Now I could decide to either give in to Saruman and avoid the mines of moria, never knowing what could have happened here, or not give up and try to find out. Since I absolutely hate to give up I decided to continue researching and finding a solution.
Two days later I finally had a breakthrough. The datasource object in viewDidLoad was created on the fly:
But when I took the aboutMeDataSource variable and placed it as an instance property, all of a sudden stuff started to work:
So what was the actual difference? Could it be that when leaving the scope of viewDidLoad the aboutMeDataSource instance is destroyed? Should not happen, since the aboutMeDataSource is registered as the datasource in the aboutMeTableView. But regardless of what could and should be or not be I decided to try to confirm this theory. I placed the aboutMeDataSource back into the method and added a simple “deinit” function to the datasource:
And the breakpoint in the deinit function was hit. But why? The tableView contained this instance in its datasource property. Could I misunderstand how Swift handles the lifecycle of objects?
As I’ve said, I am deep inside the Java world. And in Java you normally never care about the lifecycle of objects. You create them, you use them, and you don’t care for them when you don’t need them any longer. The above code would always work in Java, and there would not be any differences wheter the aboutMeDataSource is a class instance or a method instance. Since the tableview holds a reference to the aboutMeDataSource the datasource should just not be deleted. So obviously I was missing a point on how Swift handles the lifecycle of objects.
The question many programming languages face is when to delete objects that are no longer in use. And what does ‘in use’ actually mean?
In C a variable is normally created on the stack and deleted whenever its surrounding scope is closed. When you pass a variable to another function, this new function copies the original variable and works on this copy. And when this new scope is closed (e.g. the method that was called returns), this copy is deleted.
To avoid this copy / delete thing in C you can either pass references to your original variables when you call a method, or create a memory location on the heap and put your data in there. In your code you then have a thing called a pointer, which points to the memory location. It actually holds the memory address. And whenever you want to work with whatever is behind that pointer, you just dereference that pointer and work with its content. And unless you explicitly call “delete” on this reference, the memory location is never deleted. And in C-based languages this causes a lot of memory leaks. Because form a developers pont of view it is often unclear when a memory location is no longer needed.
In Java there is a thing called a garbage collector (gc). This gc frequently checks all the objects in memory, if there are other objects holding references to them. And if there are objects or clusters of objects found who noone claims any longer, these objects get deleted. In huge applications which create huge amounts of objects, the gc can consume a considerable amount of time. And when there is just one single reference to an otherwise unused cluster of objects, this cluster just never gets deleted. This happens rather seldom. But when it happens, the debugging gets ugly.
But how does Swift handle the lifecycle of objects? Swift uses a method called ARC, which stand for Automatic reference counter. In ARC the runtime applies a counter to each object. Whenever an object is assigned to a new property, this counter is increased. And when the property gets assigned nil or another object, the reference is decreased. And whenever the reference counter hits zero, the object is deleted.
Could there be a misunderstanding on my side? I started digging into the intricacies of the ARC, and actually learned a lot about it. Without realizing it, the doors to the mines of moria had opened, and I was already deep inside these mines.
In the end, the solution was simple. The ARC algorithm is great in rather simple situations. objectA is created, goes out of scope, gets destroyed. Nothing to see here, move along. But what if the situation gets a bit more complex?
There now is a property objectB which is an instance property and therefore outside the scope of the checkBehavior function. In the scope of the checkBehavior function objectA is created, and then objectB is assigned to the ‘b’ property of objectA.
When objectA goes out of scope it again gets destroyed, as expected. So, what happens if I introduce a circular dependency? Like here:
Both objectA and objectB hold references to each other. ARC can no longer safely delete objectA and objectB at the end of the scope, since the reference counter never goes to zero. A garbage collector would realize, that apart from holding references to each other no one holds a reference to either objectA or objectB.
In an ARC world, this is a classic memory leak.
And to deal with and avoid these memory leaks Swift has two keywords: “weak” and “unowned”. Both have their own specialties.
A weak property does not increase the reference count of an instance. Therefore the ARC mechanism can delete this instance. Which means that the property holding that instance can always be declared nil by the runtime. So a weak reference must always be an optional var.
Properties marked “unowned” in the same way do not increase the reference counter. Which means that the mechanics of ARC can declare the instance behind the property to be nil. Opposed to the weak properties, a unowned property can not be mutable and can never be nil. This requires that both objects (the one marked unowned and the one containing the unowned property) go out of scope in the same moment. So the unowned property never gets nil.
Back to this ObjectA / ObjectB scenario: To avoid the memory leak the instance members ObjectA.b and ObjectB.a both have to be declared “weak”. In this special case, where both instances are created in the same scope and get destroyed at the end of this same scope, “unowned” would work too. In case of weak this looks like:
Now both instances of objectA and objectB are getting destroyed when leaving the scope.
I realized I had almost left the mines of moria. All I had to do was to connect this knowledge to my initial problem. Why does the instance of the aboutMeDataSource gets destroyed when leaving the scope. The answer is rather trivial. The datasource property of the UITableView is marked as weak:
Which makes sense. You don’t want the datasource to be stuck in the memory just because the tableview is not willing to give up the resource.
Therefore in the situation where the aboutMeDataSource instance is created within the scope of viewDidLoad, it is destroyed when leaving this scope, since no more non-weak references to the aboutMeDataSource exist.
Fixing the problem therefore meant keeping the aboutMeDataSource as an instance property. Everything now works as expected. And I learned much about how swift actually deals with the lifecycle of objects.
In Java, this whole thing is rather trivial. You create objects, and you never actually worry about these objects ever again, since the garbage collector deals with them. In Swift, you cannot simply create webs of objects, without thinking about ownership. Since then you end up with lots of memory leaks.
But: Javas garbage collector can easily consume lots of cpu time. And it can hide bad programming. By having a web of objects with stateful classes you can as easily run into your memory leaks as in any other language. And I’ve seen situations in which people are severely losing the understanding of how object oriented programming works due to the fact that there is always a garbage collector cleaning up behind them. And I am not foolproof, too.
So in the end, I think, both approaches have their pros and cons. You just have to take your time to understand them. And actually, I do like the fact that I do gain more control over when objects are created / deleted.
I am happy I did not give up, did not go back. Going through the mines is sort of my way of learning. Thats where I learn the most.