Swift memory leak when iterating array of Errors

swift memory leak detection
how to solve memory leak in ios
javascript memory leak detector
closure memory leak javascript
settimeout memory leak
chrome memory leak detector
detect memory leaks in javascript
memory allocation in javascript

I'm relatively new to Swift, so I hope I'm not asking a stupid question.

I have some code that instantiates an array of type Error, which will later be iterated and printed to the console. When running this code through Instruments using the "Leaks" instrument, it shows a leak of _SwiftNativeNSError. If I change the array type from [Error] to [Any], the leak disappears, even though it is still actually holding an object conforming to Error. The leak is not reproducible with any other data types or protocols that I've tried.

Here's some sample code:

class myLeak {
  lazy var errors = [Error]()
    enum err: Error {
        case myFirstError
    }

    func doSomething() {

        errors.append(err.myFirstError)
        for error in errors {
            print(String(describing: error))
        }
    }
}
// call with let myleak = myLeak(); myleak.doSomething()

Calling the doSomething() function immediately creates a leak. Switching [Error]() to [Any]() resolves the leak, but I'm not happy with this as a solution without understanding the underlying problem. The problem is also solved by changing [Error]() to my enum implementing the Error protocol: [err](). I've also tried creating my own custom protocol just to prove if this is being caused specifically by Error, and I'm only able to reproduce the problem when using Error; my own custom protocol did not exhibit this behaviour.

Originally, my code used a forEach loop to iterate the array, but I then tried re-writing it to use a standard for loop in case the closure in forEach was causing the issue, but this didn't work.

I'm suspicious that this may be a Swift bug (in which case, I will open an issue for it), but there's also a chance that I'm missing a key piece of understanding. If what I'm doing is bad practice, I'd like to understand why.

Update:

After speaking with Joe Groff, an Apple engineer, this is the bug you could have encountered: https://bugs.swift.org/browse/SR-6536

Original Answer

I've played a bit with your code and I think the problem is due to Error type. In fact, taking the code by Josh, you can find a different behaviour if you use Error or MyError as the type of your array.

I guess the problem arises since the deinit call is not forwarded to CustomObject since Error is just a protocol and it's not aware of the underlying class. While, MyError is. We can wait for other people to have clarifications on this behaviour.

Just for simplicity, I'm using a Playground here. See that I'm not even trying to print the error value.

import UIKit

class ViewController: UIViewController {
    var errors: [Error] = [] // change to MyError to see it working

    enum MyError: Error {
        case test (CustomObject)
    }

    class CustomObject {
        deinit {
            print("deiniting")
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let testerror = MyError.test(CustomObject())
        errors.append(testerror)
        errors.removeAll()
    }    
}

do {
    let viewController = ViewController()
    // just for test purposes ;)
    viewController.viewDidLoad()
}

Hands-On Design Patterns with Swift: Master Swift best practices , Master Swift best practices to build modular applications for mobile, desktop, and reference counting Memory leaks 32 Dangling pointers ARC – what is that? and catching errors Container types Arrays Mutability and operations Iterating,  Other Memory Issues. We only discussed memory leaks in this post, which is inaccessible memory that has no more pointers to it. There are other memory issues that can plague your app that you should be conscious of. Abandoned Memory. This kind of memory still has references to it, but are wasted since they won’t ever be used again.

I tested your code and it looks like the String(describing) statement is causing the string to retain the error, which is just weird. Here is how I can tell: I created an associated object that prints out when its being deinitialized.

import UIKit

class ViewController: UIViewController {
    var errors = [Error]()
    override func viewDidLoad() {
        super.viewDidLoad()
        class CustomObject {
            deinit {
                print("deiniting")
            }
        }
        enum MyError: Error {
            case test (CustomObject)
        }
        let testerror = MyError.test(CustomObject())
        errors.append(testerror)
        for error in errors {
            //print(String(describing: error))
        }
        errors.removeAll()
    }

}

When the print doesn't run, sure enought he associated object is deinitialized at when the error is removed from the array and the output is:

deiniting

Uncomment the print and the output becomes:

test(CustomObject #1 in stack.ViewController.viewDidLoad() -> ())

At first I thought it was the print that's the problem but If I refactor to:

errors.forEach{print($0)}

I get the output:

test(CustomObject #1 in stack.ViewController.viewDidLoad() -> ())
deiniting

But if I change it to:

errors.map {String(describing:$0)}.forEach{print($0)}

Then deinit is no longer called:

test(CustomObject #1 in stack.ViewController.viewDidLoad() -> ())

Weirdness. Maybe file a radar?

Using unit tests to identify and avoid memory leaks in Swift, Memory leaks in Swift are often the product of a retain cycle, when one memory leaks, and also make it easier to avoid common mistakes that actually does retain all of its observers, by storing them in an array: A nice way to do that is to filter out all expired wrappers when we are already iterating over  How I stopped that memory leak and reclaimed 150MB of memory — Swift I have actually recently used these resources to fix a major memory leak in my app. Memory Management in Swift article by

This bug was Fixed in Xcode 9.3.

Memory Leaks in Swift - Flawless iOS, A memory leak is a portion of memory that is occupied forever and never used again. It is garbage that takes space and causes problems. No errors, no warnings, nothing. So now we have just 14 lines of code that can potentially save us from lots of unintentional memory leaks. This is what I love about Swift and about its type

4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them, Learn about memory leaks in JavaScript and what can be done to solve it! Leaks are the cause of whole class of problems: slowdowns, crashes an indirect reference to the big array, resulting in a sizable leak. This is expected if code is running in a loop performing allocations, which is the usual case. Swift Arrays Holding Elements With Weak References In iOS development there are moments where you ask yourself: “To weak, or not to weak, that is the question”. Let’s see how “to weak” with the arrays.

Online Preview: Advanced Swift · objc.io, Mapping a closure expression over an array compiles to the same assembly code as looping over a contiguous block of memory does. However, there are some  Specifically, you use the Array type to hold elements of a single type, the array’s Element type. An array can store any kind of elements—from integers to strings to classes. Swift makes it easy to create arrays in your code using an array literal: simply surround a comma-separated list of values with square brackets.

valgrind guide, The types of errors it can find include memory leaks, reading uninitialized c[5], which is beyond the end of array c, on the last iteration): As we can see, it first allocate an amount of memory, then, when it got filled by new elements, it doubles its space, and so on. That’s why we see a huge gap between 13,000 and 14,000 documents.

Comments
  • Copied your code and added deinit to myLeak class to check if deist getting called. Clearly its getting called so I dont see any leak because of myLeak
  • I've added an explanation for it. I guess the problem is due to Error.
  • I've added an update.
  • @LorenzoB Thanks - I did find that bug logged, but although it seems related, this seemed like a slightly different case, as that had to do specifically with an Optional Error. I agree it definitely looks related, and I cited that bug in the radar I filed. Thanks so much for following up.
  • Thanks Lorenzo. In your opinion, would you say this is a bug, or expected behaviour? Your explanation about the deinit call not being forwarded makes good sense.
  • @CPR I think you can open a radar. But, I'm not sure to say the truth.
  • Thanks Lorenzo. I've already filed a radar, so will await the response.
  • Thanks for the detailed response. I completely overlooked the String(describing:) statement as a possible source of the error. Good to get some confirmation that there's nothing inherently wrong with my code. I'll accept this as solution later, but will leave it open for now in case anyone is able to offer additional insight. Thanks a lot!
  • I've added an explanation for it. I guess the problem is due to Error.