Array remove function - Memory Deallocation

Hi,

I have an example where 1 element is added to an array.

Removing element from the array doesn't seem to deallocate the memory of that element.

Example:

import Foundation

class Car {
    
    deinit {
        print("********** Deleted Car **********")
    }
}

var cars = [Car]()

cars.append(Car())

print("Step 1")

cars.remove(at: 0) //Doesn't deallocate here
//_ = cars.remove(at: 0) //Doesn't deallocate here
//let _ = cars.remove(at: 0) //Deallocates here

print("Step 2")

print("-----End Of Program------")

Output:

Step 1
Step 2
-----End Of Program------
********** Deleted Car **********

Note:

Tried on Swift Playgrounds.

Questions:

  1. Why doesn't remove cars.remove(at: 0) deallocate memory ?
  2. However why does let _ = cars.remove(at: 0) deallocate memory?
  3. Is the following a reasonable approach, or is there a better way to handle the following scenario:
  • Have an array containing a set of objects
  • Other pieces of code reference them with weak references or local variables which go out of scope.
  • When I want to delete an object, I simply remove them from the array.

Update:

  • When I tried the same program using a Command Line Mac app, it works fine, I suppose what @QuinceyMorris makes sense. Not sure if Swift Playground does things differently
2 Likes

A better way to handle what?

It is … um … impractical to try to reason about retain counts. If I read your example correctly, your Car object was deallocated eventually, so there was nothing being leaked.

The general problem is that you don't know what else might be holding an owning reference to the object, nor for how long. In this particular case, the timing you're seeing is probably the result of an autorelease pools being used (or not being used, depending on the details of the compilation).

Unless you're trying to solve a specific problem, you probably shouldn't spend time on this.

5 Likes

Thanks a lot @QuinceyMorris ,

I am not so particular about retain count. I am more concerned about memory leaks, because in my app there are situations where the deinit is not called even after removing from the array.

I am just trying to understand if the following approach is reasonable:

  • Have an array containing a set of objects
  • Other pieces of code reference them with weak references or local variables which go out of scope.
  • When I want to delete an object, I simply remove them from the array.

Update:

When I tried the same program on a command line Mac app, it got deallocated before the end of there program, not sure if Swift Playground does things differently

Objects are formally deallocated when the last remaining strong reference to them goes out of scope or is itself deallocated. In a playground, that scope might be the entire playground if it’s a top-level declaration.

When objects are actually destroyed is an implementation detail and can be changed by the optimiser. It’s certainly not something you should be concerned about as an app developer unless you’re looking at very low-level performance.

Memory leaks are only a problem if you have a retain cycle: two or more objects holding strong references to each other. Sometimes you work around this by making one weak or unowned, but often it’s a sign of a design issue, particularly if the retain cycle is e.g. between the model and control layers of an MVC app. The common case where you’d use weak or unowned is a bidirectional graph.

Your overall approach seems somewhat reasonable, but it would be helpful to see a more in-depth example (with the external weak references you mention) to get a better idea. One thing to note is you shouldn’t be using weak references unless you absolutely have to: reference counting overhead increases dramatically for objects with weak references to them.

3 Likes

Thanks a lot @Torust for clarifying that the approach is reasonable.

For some reason, some of the objects in my app are not getting deallocated.
I am trying to simplify and isolate the app code so that I can present the minimum code to reproduce the issue. Once I have the bare minimum code will post it here

It's certainly true that playgrounds have a different timing for top-level deallocations. It's not clear that playgrounds even have a well-defined concept of "lifetime" at the top level, especially given the partially re-runnable playground execution model that was introduced in Xcode 10.

Could you clarify this claim? My understanding is that Swift's reference counting implementation is not much worse for references held weakly than those held strongly.

In the Obj-C world variables holding references weakly have to be registered and unregistered globally, and this involves a thread-safe mechanism that can be a performance hit when a lot of weak variables are zeroed out all at once. I don't think Swift has this problem.

1 Like

Don't use Swift Playgrounds to test memory-related things

It grabs references to things for itself (to display them onscreen) and therefore messes up expected reference counts. The REPL does this as well, hanging on to the output of the most recently evaluated expression.

5 Likes

You can see here that in the current implementation, adding weak references forces Swift to switch to a side table for storing refcounts, which leads to an extra level of indirection on reference operations. Unowned references can be stored inline, however.

1 Like

I'm not sure that counts as "reference counting overhead increases dramatically", and I'm not sure that it's a good reason to advise against using weak properties in general.

When I say "unless you absolutely have to" I mean "don't use weak references when a strong reference or an unowned reference would do". If there's a case where a weak reference makes sense and you're not in a performance-critical scenario, then sure, use a weak reference. I was responding in particular to:

which sounded to me like weak references might be being used where strong references would be fine. As an example, I've seen frame-time improve from around 60ms to around 20ms in my game engine by removing a weak reference on one particular object that was being used within an inner loop.

In terms of actually solving the issue with the memory leaks: @somu, if you're using Xcode, have a look at the Visual Memory Debugger and Instruments' Leaks tool. Both are very useful in tracking down retain cycles.

It also still sounds to me like you might not have separation of responsibilities within your code; if you're using an MVC model, then generally the controller has strong references to both the view and the model layer, and while parts of the model layer might have references to other parts, those references usually only need to be unidirectional (unless you have a graph structure). If you could post more context then we might be able to help sort out any potential design issues in that regard.

As a side note, the strategy of using a single global array for all objects (or usually structs) of a particular type and just using handles to them (i.e. indices into the array) from elsewhere is a good approach for performance in cases where you're trying to optimise data accesses and remove reference counting overhead. However, in that case, it's guaranteed that only the global array holds strong references to its contents, so it doesn't sound like your design.

2 Likes

Thanks a lot @Torust @QuinceyMorris @TellowKrinkle

Thanks @Torust, Instruments > Leaks helped me spot the retain cycle.

The code responsible for the cycle is below but not sure why it happens.

Overview:

I was using Operation (NSOperation) to generate cars. The instance variable inside the operation was holding strong reference to the operation

Code:

class GenerateCars : Operation {
    
    //Input
    
    var generateCompletionBlock : (([Car], Error?) -> ())?
    
    //Processing
    
    private var cars       = [Car]()
    private var error       : Error?
    
    //MARK: Main
    
    override func main() {
        
        guard !isCancelled else {
            complete()
            return
        }
        
        let car = Car()
        
        cars = [car]
        
        complete()
    }
    
    private func complete() {
        
        generateCompletionBlock?(cars, error)
//        cars = [] //Fixes the issue
    }
}

Fix:

In GenerateCars set cars (ivar) to an empty array after the completion block is invoked. See commented code in the above code which contains the fix

Confusion:

  • I was thinking that the operation would complete and fall off the queue and the operation would get deallocated and hence the operation's ivar would also relinquish it's strong reference to the car objects.
  • GenerateCars.deinit never gets called