Swift Managing Memory

Swift memory management: creating views the right way and avoid memory leaks

So for those who end up here and wonder the same thing, CFGetRetainCount returns unreliable results as pointed out here.

To make sure memory is deallocated when using custom classes, you may call inside your class and test

deinit {
print("Memory deallocated")
}

If your object is not retained, the method above is automatically called. See documentation here: https://docs.swift.org/swift-book/LanguageGuide/Deinitialization.html

Other leaks may be tested using Instruments (in Xcode: Product > Profile > Leaks) or using the Debug Memory Graph tool

Sample Image

More explanations here: https://www.youtube.com/watch?v=1LnipXiSrSM

So answers would be as follows:

1+3) Do not use CFGetRetainCount. See explanation above.

2) This seems to be working fine and memory is deallocated when I test the app.

Swift Managing Memory

This question has been open long enough and I now feel confident enough to answer it.


Different Levels of MM:

Hardware Memory

In Swift with ARC we have no way to clean up the actual hardware ram. We can only make it possible for the OS to do that for us. One part is using the right code (optionals and weak) the other part is creating time for the OS to do it's job.

Imagine we have a function that runs on all threads indefinitely. It does one thing, load an image, convert to black/white and save.
All images max at a couple of mb’s and the function creates no Software Memory Leaks.
Because images don’t have a set size and might have different compression they don’t have the same footprint.
This function will always crash your app.

This “Hardware” Memory Leak is caused by the function always taking the next available slot of memory.

The OS does not step in to “actually clean the memory” because there is no idle time. Putting a delay between each pass completely fixes this.


Language specific MM

Casting

Some operations don’t have an impact on memory, others do:

let myInt : Int = 1
Float(myInt) // this creates a new instance

Try casting instead:

(myInt as Float) // this will not create a new instance.

Reference Types vs Value Types | Classes vs Structs

Both have their advantages and their dangers.

Structs are memory intensive because they are Value Types.
This means they copy their values when assigned to another instance, effectively doubling memory usage.
There is no fix / work around for this. It is what makes Structs Structs.

Classes don’t have this behaviour because they are Reference Types. They don’t copy when assigned.
Instead they create another reference to the same object. ARC or Automatic Reference Counting is what keeps track of these references.
Every Object has a reference counter. Each time you assign it, it goes up by one. Each time you set a reference to nil, the enclosing function ends, or the enclosing Object deinits, the counter goes down.

When the counter hits 0 the object is deinitialised.

There is a way to prevent an instance from deinitialising, and thus creating a leak. This is called a Strong Reference Cycle.

Good explanation of Weak

class MyClass {

var otherClass : MyOtherClass?

deinit {
print("deinit") // never gets called
}
}

class MyOtherClass {

var myclass : MyClass?

deinit {
print("deinit") // never gets called
}
}

var classA : MyClass? = MyClass()

// sorry about the force unwrapping, don't do it like this
classA!.otherClass = MyOtherClass()
classA!.otherClass!.myclass = classA // this looks silly but in some form this happens a lot

classA = nil
// neither the MyClass nor the MyOtherClass deinitialised and we no longer have a reference we can acces. Immortalitiy reached they have.

set one reference to weak

class MyOtherClass {

weak var myclass : MyClass?

deinit {
print("deinit") // gets called
}
}

inout

Functions capture the values passed to them. But it is also possible to mark those values as inout. This allows you to change a Struct passed to a function without copying the Struct. This might save memory, depending on what you pass and what you do in the function.

It is also a nice way of having multiple return values without using tuples.

var myInt : Int = 0

// return with inout
func inoutTest(inout number: Int) {

number += 5

}

inoutTest(&myInt)
print(myInt) // prints 5

// basic function with return creates a new instance which takes up it's own memory space
func addTest(number:Int) -> Int {

return number + 5

}

Functional Programming

State is Value over Time

Functional programming is the counter part of Object Oriented programming. Functional programming uses Immutable state.

More on this here

Object Oriented programming uses Objects that have changing/mutating states. Instead of creating a new value, the old values are updated.

Functional Programming can use more memory.

example on FP


Optionals

Optionals allow you to set thing to nil. This will lower the reference count of Classes or deinitialise Structs. Setting things to nil is the easiest way to clean up memory. This goes hand in hand with ARC. Once you have set all references of a Class to nil it will deinit and free up memory.

If you don’t create an instance as an optional, the data will remain in memory until the enclosing function ends or the enclosing class deinits. You might not know when this will happen. Optionals give you control over what stays alive for how long.


API MM

Many "memory leaks" are cause by Frameworks that have a “clean up” function that you might not have called.
A good example is UIGraphicsEndImageContext() The Context will stay in memory until this function is called. It does not clean up when the function that created the context ends, or when the image involved is set to nil.

Another good example is dismissing ViewControllers. It might make sense to segue to one VC and then segue back, but the segue actually creates a VC. A segue back does not destroy a VC. Call dismissViewControllerAnimated() to remove it from memory.

Read the Class References and double check there are no “clean up” functions.


If you do need Instruments to find a leak, check out the other answer on this question.

What is the correct way to manage memory in a media heavy swift app? Release it?

If your memory isn't decreasing even after the view controller disappears then you have a memory problem somewhere. Maybe something else is referencing that array with images and causing it to not be released?

For your general problem, you need some way to save the images to a cache (on disk). Keeping an ever-growing array of UIImage objects will mean your app will be terminated. If you try testing on a device you'll find that 2GB of memory being used will cause the system to terminate your app. For caching use a library. SDWebImage is very popular.

Finally, pay attention to the system telling you about memory pressure with UIViewController's didReceiveMemoryWarning and the UIApplicationDidReceiveMemoryWarningNotification notification. When you get one of those notifications, release some of the images. How you should do this is complex and depends on your own code.

Memory Management in Swift

Okay, so I solved it. The quick tip for everyone who might be having the same problem.
1)Have all the fields (except numbers and books as optionals). Nil them out in deinit.
2) Nil out everything that can reference your object in memory, a good example, which is easy to forget is delegation. If your view acts like a delegate, don't forget to break this connection.
3)If you are using maps in your apps, it is advisable to use one shared map and redraw and change its delegate every time, rather then instant instinct new maps on every view with them. Maps can be hard to clean from memory(I tried map applyMemoryFix, it didn't work)



Related Topics



Leave a reply



Submit