Swift's Memory Management

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 iOS memory usage increasing during runtime

Profile Memory Allocation

I made a test iOS application to show how you could profile memory allocation. After creating a new iOS project I added the following to the AppDelegate:

var test: [String] = []

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.

while true {
test.append("blasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdf")
}
return true
}

This should simulate your problem. Then we longpress the build button and choose profile.

Sample Image

We choose Allocations as profiling template.

Sample Image

We press the record button.

Sample Image

We once again select Allocations.

Sample Image

We select Call Trees.

Sample Image

And probably we already find the problematic stack trace on the right side.

Sample Image

Or you scan the call tree by yourself.

Sample Image

This should give you a starting point for further debugging to find the infinite loop :)

How does Swift memory management work?

Should we take this as a hint that Apple thinks we should be using structs as delegates? Or is this simply a bad example, and realistically, delegate protocols should be declared as class-only protocols so that the delegated object can hold a weak reference to its delegate?

Here's the thing. In real life Cocoa programming, the delegate is likely to be an existing class. It is a class because it exists for some other purpose that only a class can satisfy - because Cocoa demands it.

For example, very often, to take iOS as an example, one view controller needs to act as another view controller's delegate for purposes of arranging a message back and forth between them. Ownership of the view controllers is dictated by the view controller hierarchy and by nothing else. So, in Swift, just as in Objective-C, you had better make that delegate property weak, because it would be terrible if one view controller suddenly took memory management ownership of another view controller!

So, in the real world of the Cocoa framework, there is serious danger of incorrect ownership or of a retain cycle. And that is the problem that weak solves. But it only works, as you rightly say, with classes.

The example in the book, however, is about some objects living off in an abstract made-up artificial Swift-only world. In that world, as long as you aren't in danger of circularity (retain cycle), there's no reason not to use structs and there's no reason to worry about memory management. But that world is not the world you will usually be programming in! And that world is not the framework Cocoa world that your Objective-C delegate pattern comes from and belongs to.

Will struct in Swift cause memory issue if passed around a lot?

Theoretically there could be memory concerns if you pass around very large structs causing them to be copied. A couple of caveats/observations:

  1. In practice, this is rarely an issue, because we’re frequently using native “extensible” Swift properties, such as String, Array, Set, Dictionary, Data, etc., and those have “copy on write” (COW) behavior. This means that if you make a copy of the struct, the whole object is not necessarily copied, but rather they internally employ reference-like behavior to avoid unnecessary duplication while still preserving value-type semantics. But if you mutate the object in question, only then will a copy be made.

    This is the best of both worlds, where you enjoy value-semantics (no unintended sharing), without unnecessary duplication of data for these particular types.

    Consider:

    struct Foo {
    private var data = Data(repeating: 0, count: 8_000)

    mutating func update(at: Int, with value: UInt8) {
    data[at] = value
    }
    }

    The private Data in this example will employ COW behavior, so as you make copies of an instance of Foo, the large payload won’t be copied until you mutate it.

    Bottom line, you asked a hypothetical question and the answer actually depends upon what types are involved in your large payload. But for many native Swift types, it’s often not an issue.

  2. Let’s imagine, though, that you’re dealing with the edge case where (a) your combined payload is large; (b) your struct was composed of types that don’t employ COW (i.e., not one of the aforementioned extensible Swift types); and (c) you want to continue to enjoy value semantics (i.e. not shift to a reference type with risk of unintended sharing). In WWDC 2015 video Building Better Apps with Value Types they show us how to employ COW pattern ourselves, avoiding unnecessary copies while still enforcing true value-type behavior once the object mutates.

    Consider:

    struct Foo {
    var value0 = 0.0
    var value1 = 0.0
    var value2 = 0.0
    ...
    }

    You could move these into a private reference type:

    private class FooPayload {
    var value0 = 0.0
    var value1 = 0.0
    var value2 = 0.0
    ...
    }

    extension FooPayload: NSCopying {
    func copy(with zone: NSZone? = nil) -> Any {
    let object = FooPayload()
    object.value0 = value0
    ...
    return object
    }
    }

    You could then change your exposed value type to use this private reference type and then implement COW semantics in any of the mutating methods, e.g.:

    struct Foo {
    private var _payload: FooPayload

    init() {
    _payload = FooPayload()
    }

    mutating func updateSomeValue(to value: Double) {
    copyIfNeeded()

    _payload.value0 = value
    }

    private mutating func copyIfNeeded() {
    if !isKnownUniquelyReferenced(&_payload) {
    _payload = _payload.copy() as! FooPayload
    }
    }
    }

    The copyIfNeeded method does the COW semantics, using isKnownUniquelyReferenced to only copy if that payload isn’t uniquely referenced.

    That’s can be a bit much, but it illustrates how to achieve COW pattern on your own value types if your large payload doesn’t already employ COW. I’d only suggest doing this, though, if (a) your payload is large; (b) you know that the relevant payload properties don’t already support COW, and (c) you’ve determined you really need that behavior.

  3. If you happen to be dealing with protocols as types, Swift automatically employs COW, itself, behind the scenes, Swift will only make new copies of large value types when the value type is mutated. But, if your multiple instances are unchanged, it won’t create copies of the large payload.

    For more information, see WWDC 2017 video What’s New in Swift: COW Existential Buffers:

    To represent a value of unknown type, the compiler uses a data structure that we call an existential container. Inside the existential container there's an in-line buffer to hold small values. We’re currently reassessing the size of that buffer, but for Swift 4 it remains the same 3 words that it's been in the past.
    If the value is too big to fit in the in-line buffer, then it’s allocated on the heap.

    And heap storage can be really expensive. That’s what caused the performance cliff that we just saw. So, what can we do about it? The answer is cow buffers, existential COW buffers...

    ... COW is an acronym for “copy on write”.
    You may have heard us talk about this before because it’s a key to high performance with value semantics. With Swift 4, if a value is too big to fit in the inline buffer, it's allocated on the heap along with a reference count. Multiple existential containers can share the same buffer as long as they’re only reading from it.

    And that avoids a lot of expensive heap allocation. The buffer only needs to be copied with a separate allocation if it’s modified while there are multiple references to it. And Swift now manages the complexity of that for you completely automatically.

    For more information about existential containers and COW, I’d refer you to WWDC 2016 video Understanding Swift Performance.



Related Topics



Leave a reply



Submit