Learning Swift — Day 104

Hacking with Swift — Learning Project 30

This project will be about learning how to use Instruments for the first time and to debug performance issues. Let’s dive in.

Setting up

Today we are going to learn about profiling. Profiling is the term used when we monitor performance, memory usage and other information of an app, with the aim of improving efficiency.

What are we working with?

For sure this is a crazily buggy app. Building it and playing a bit with it made the interface of my iPhone crash and reload! Woah! It seems it used the whole RAM in a single spree!

What can Instruments tell us?

Woah! This session was neat! I’m not sure I should rewrite what Paul said or the steps we made as this is just the same thing that is explained in the video or in the article. Also, iOS crashed again and again while testing on the real device so it’s probably best to leave it there.

Running on the Simulator we have, in the Debug menu, some nice options:

  • Color Blended Layers which shows views that are opaque in green and views that are translucent in red. If there are multiple transparent views inside each other, you’ll see more and more red.
  • Color Offscreen-Rendered shows views that require an extra drawing pass in yellow. Some special drawing work must be drawn individually off screen and then drawn again onto the screen, which means a lot more work.

It seems the app is making things way more complicated than what they should be. Let’s see how to solve them.

Fixing the bugs: slow shadows

This was a truly intense session and even if it was not really a matter of understanding or not (the subject was quite clear) repeating all the steps here, including the wrong ones, would be against the scope of this article. Instead, we are just going to see how to save the boat.

In cellForRowAt, find the renderer constant and, just above it, create a new CGRect with the origina at .zero and the size of 90 x 90. This will make iOS render the huge images we have in our supporting files to the proper amount of space needed, saving a lot of calculation to the CPU.

Then, pass this newly created rectangle’s size to the size initialiser of the image renderer. Inside the closure, modify the addEllipse(in:) to get the renderRect and also the following draw code.

Finally, below where the shadow is being set, tell iOS the exact path where to draw the shadow, using the .shadowPath property and setting it equal to a UIBezierPath(ovalIn: renderRect).cgPath.

This teaches us a great lesson: if you can tell the computer what to do it will just increase the performance immensely!

Fixing the bugs: wasted allocations

Back when we were learning about table views for the first time we have been used to the act of dequeuing cells so that iOS doesn’t have to keep all those objects in memory. In this app, using the Allocation Instrument, we notice how this practice has not been used. To solve this, and being this app not created from the storyboard, we need to call the .register method on the table view inside viewDidLoad and pass it UITableViewCell.self and “Cell” as parameters. Back in cellForRowAt we can say

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 

This is the code we are used to and, quite obviously, we have reduce the amount of memory allocated by a very significant amount. Now the app is smooth as it should be.

Fixing the bugs: running out of memory

This session was really great and I would like to thank Paul for being able to put together an app that is willingly flawed. I know the feeling when I have to show some students the wrong thing and it is so hard!

In this case we needed to deal with an image cache that was not needed, so we accessed the path of the desired image inside the bundle and used the contentsOfFile initialiser instead of the named one. But this is not solving things enough, our app still crashes after a while.

In SelectionViewController we are still creating a cache of view controllers but we are never using it so also that thing can go away and, in ImageViewController, we should add weak in front of the property that references back to the main view controller. This will avoid strong reference cycles.

Last but not least (because yes, we are not done yet!), we need to implement the viewWillDisappear method with a call to invalidate the timer that manages the animation of the image, otherwise it will keep a strong reference to the view controller (which already owns the timer strongly) and then make our device run out of RAM.

Wrap up

So that’s it for this project. I don’t think I need to create a repository for this as I have really done nothing personal inside! Let’s now review what was learnt before heading off to the challenges!

  1. Dequeuing reusable table view cells is a performance optimisation. It means iOS doesn’t have to keep on creating and destroying them as the user scrolls around.
  2. Clipping paths allow us to restrict where drawing takes place in a Core Graphics context. They let us draw things in specific shapes, creating a cut-out effect.
  3. When Color Blended Layers is turned on, opaque views are coloured green. Translucent views are coloured varying shades of red, with darker red shades meaning that more translucent views are stacked there.
  4. A transient allocation is one that was created and subsequently destroyed. In contrast, a persistent allocation is one that was created and stayed alive.
  5. Strong reference cycles can cause view controllers to never be destroyed. If view controllers are kept alive, their contents might be kept alive too – it’s very wasteful.
  6. Apps that use too much memory will crash. Apple doesn’t publish the limits of how much memory we’re allowed to use, but there is a hard limit.
  7. We can add a simple shadow to a UIView by using the shadow properties of its layer. While these shadows are easy to create, it’s helpful to also provide a shadow path to avoid performance problems.
  8. We can create timers by passing them a closure to execute. Sometimes passing a closure rather than a selector makes our code easier to read.
  9. Calling invalidate() on a timer stops it from working, and frees up any references it was holding. It’s generally a good idea to invalidate timers as soon as you’re done with them.
  10. UIImage(named:) keeps images it loads in a special cache. This cache is managed by UIKit and will be cleared automatically.
  11. Instruments can detect which objects are using memory. It tells us the exact class names that were created and destroyed, which helps us find problematic code.
  12. Instruments comes as part of Xcode. Xcode, Instruments, and the iOS Simulator all come bundled together.

Challenges

Challenge 1: go through project 30 and remove all the force unwraps. Note: implicitly unwrapped optionals are not the same things as force unwraps — you’re welcome to fix the implicitly unwrapped optionals too, but that’s a bonus task.

First step:

After line 26, add this line:

if let path = Bundle.main.resourcePath {

…and wrap all the rest of the following statement inside the braces. We can thus remove the force unwrapping from the file extraction.

Second step:

Inside cellForRowAt put an if in front of the let path […] statement and then wrap everything up to and including the shadow creation code inside the braces of this statement. This allows to remove the force unwrapping from the path constant, as before.

And that’s it for the SelectionViewController file.

Third step:

Moving to the other swift file, inside viewDidLoad, a similar operation must be performed: the resulting code is this:

if let path = Bundle.main.path(forResource: image, ofType: nil) {
    if let original = UIImage(contentsOfFile: path) {
        let renderer = UIGraphicsImageRenderer(size: original.size)
        
        let rounded = renderer.image { ctx in
            ctx.cgContext.addEllipse(in: CGRect(origin: CGPoint.zero, size: original.size))
            ctx.cgContext.closePath()
            
            original.draw(at: CGPoint.zero)
        }
        
        imageView.image = rounded
    }
}

And that’s it for this challenge.

Paul offered bonus points for also removing the implicitly unwrapped optionals so let’s try to do just that!

I guess to fix this we need to replace the ! with an ? and fix the errors that appear. For the var image: String? I had to fix viewDidLoad by adding an if let image = image { to contain all the rest of the code of the method and touchesBegan using a guard let image = image else { return } at the top of the method.

The second was slightly easier. Inside viewWillDisappear I added a guard let statement to solve the issue. It feels nice to realise how this code can look nicer and safer, it means that something is really starting to sink in.

The third one was significantly harder and I probably made a mess but I will now check with my new friend, Instruments, if I made some blatantly evident mistakes. First, inside loadView I wrapped all the code except for the imageView = UIImageView() inside an if let imageView = imageView { statement. Inside the timer closure I removed all references to self and left only the imageView. Then, inside viewDidLoad I added a guard let imageView = imageView else { return } and put everything else inside, fixing things quite easily this time. The same thing was done in viewDidAppear and that was it. Let’s now check it with Instruments.

Well, I can really be proud of myself: the app’s RAM average consumption went down to 15MB and stayed stable until loading an image and returned back to those same 15MB afterwards!

Victory!

Challenge 2: pick any of the previous 29 projects that interests you and try exploring it using the Allocations instrument. Can you find any object that are persistent when they should have been destroyed?

Choosing a project for this challenge was maybe the toughest thing as since we started using SpriteKit I cannot say I really enjoyed a particular project. To me the first one stay the best! I therefore chose Whitehouse Petitions, Project 7.

Mmm… I even got a crash there but I could not reproduce it afterwards.

Memory usage is pretty low here as you can see from the image:

The maximum is about 24MB but I do notice something that I don’t like. The consumption is, nevertheless, rising. It started at 15MB and went up.

While analysing Instruments crashed … nice for a debugging tool.

After a second run I am filtering for UITableView object calls and they are all persistent but occupying something like 58KB of memory … I am not sure this should be corrected.

Bof… I don’t know… things seem to be working as expected. I am sure Paul has disseminated the code with traps as every good teacher should but well … I don’t really feel like hunting them right now.

Challenge 3: for a tougher challenge, take the image generation code out of cellForRowAt: generate all images when the app first launches, and use those smaller versions instead. For bonus points, combine the getDocumentsDirectory() method I introduced in project 10 so that you save the resulting cache to make sure it never happens again.

I tried a few things but, sincerely, I had not idea what I was doing. I tried moving some code out, changing something else but, sincerely, I’m not really sure of what I am doing. I wonder how long should I insist with it.


I have proceeded for let’s say 20 minutes more and gotten let’s say somewhere but it is now a bit of a mess! Now looking at some code around I got completely confused … Now I do not understand what is what and which code was belonging where. I could start over from the beginning but … I really have no energy anymore…


Tried more … nothing … it is getting more and more messy… I will now start over.

Ok … now the app works again, just the counter does not! ROFL!

Fine … no … everything works … I just had to read the code. This app counts how many times you tapped on the image in the detail view controller … what a stupid thing but fine!

Please give a look at my code to see how I made it, nothing extraordinary, but I cannot write it now, I’m exploding! It is in branch “Challenge30-3”.


The last thing I should do would be to implement the getDocumentsDirectory method but my brain is burning … aaaah! A bit more, come on! …

No… I cannot … damn it … I cannot see how to make this work… I mean, I cannot save all those images in the user defaults … that’s not what they are there for. Looking at Project 10 I cannot see how to integrate that method in a working way.

… Fine… I asked for some hint around one hour ago and gave it all my thoughts but really I cannot see how I should make this work. I’m sorry but I need someone to explain this to me otherwise there is just no point in just writing some gibberish code that makes no sense.

We all have just one life and it is already too short for losing time guessing in the darkness… Overall this was a good project but this last part ruined the experience, sincerely. I have already said this, I do not like being let alone in the darkness looking for a solution that I cannot find alone and this last challenge makes no exception.

A pity… I was feeling really good before.


Please don’t forget to drop a hello and a thank you to Paul for all his great work (you can find him on Twitter) and be sure to visit the 100 Days Of Swift initiative page. We are learning so much thanks to him and he deserves to know of our gratitude.

He has written about 20 great books on Swift, all of which you can check about here.

The 100 Days of Swift initiative is based on the Hacking with Swift book, which you should definitely check out.


If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).

If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s books from this Affiliate Link.

Anyways, thank you so much for reading!

Till the next one!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: