Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 3 enums leak memory when the class contains an array

I found a memory leak in Swift. It gave me nightmares because my code was full of small leaks everywhere, and then I managed to reduce it to this small example,

import UIKit

enum LeakingEnum {
    case
    LeakCase,
    AnotherLeakCase
}

class Primitive {
    var lightingType: LeakingEnum = .LeakCase
    var mysub : [Int] = []
    init() {
        mysub.append(80)
    }
}

class ViewController: UIViewController {
    var prim: Primitive?
    override func viewDidLoad() {
        super.viewDidLoad()
        prim = Primitive()
    }
}

If you run this program on an iPhone and Profile with Instruments, you'll find this leak in Array._copyToNewBuffer,

Memory leak detected with Instruments

If I remove the call to mysub.append, it stops leaking. If I remove the enum from Primitive, it stops leaking as well. All the classes where I have an enum leak like this. What's going on with Swift enums?

Reproduced in Swift 3, Xcode 8.2.1, and iOS 10.2, on both an iPhone6 and an iPad Pro. Can't reproduce in the Simulator, or in a device with iOS 9.3.2. You can download a minimal sample app here: https://github.com/endavid/SwiftLeaks

Is this a known bug? Is there any work around?

Edit:

Because this remind me of another enum bug, Accessor gives the wrong value in Swift 1.2/2.0 Release build only, I tried making the enum an @objc Int enum, but it still leaks. However, making lightingType directly an Int does fix the leak...

Edit2: After updating my iPhone to 10.3 and Xcode to 8.3, the leak is gone. It seems it was an issue of iOS 10.2...

like image 724
endavid Avatar asked Mar 04 '17 22:03

endavid


1 Answers

Hey @endavid managed to replicate the issue consistently. We spend a good time trying to figure out what was going on and your post helped a lot!

Here is the sample repo: https://github.com/Giphy/ios-memory-leak-sample

Radar: https://openradar.appspot.com/radar?id=4992108083544064

We are developing SDKs and same exact issue surfaced with a small difference. Since we wanted things to interop we added @objc to the enum definition and things started to leak exactly the way you described given your class has two properties, one enum and one mutable array.

Consistently reproduced the leak:

// Without @objc this enum won't leak
// however when this enum is included in a class
// which contains an array, it will leak
@objc enum leakingObjCMarkedEnum: Int {

    // Just some random cases.
    case apple, orange
}

// Wrapper class which contains an enum and Array
// The class needs to contain the the Array in order for
// the Enum to leak.
class WrapperClass {

  // Optional enums marked with @objc will leak.
  var leakyOptionalEnum: leakingObjCMarkedEnum?

  // Include an array to trigger this behaviour.
  // Empty arrays won't cause the leak, so lets add an arbitrary Int
  var myArray: [Int] = [80]
}

class ViewController: UIViewController {

  // Hang on to a reference to our Wrapper Class instance.
  var wc: WrapperClass?

  override func viewDidLoad() {
    super.viewDidLoad()

    // Allocate an instance of our class
    // and things will start leaking at this point.
    wc = WrapperClass()
  }
}

Work Around:

If we convert the optional enum class property to a non-optional, leak will disappear.

// Let's convert the optional property to a non-optional
var leakyOptionalEnum: leakingObjCMarkedEnum = .orange

Edit:

It is fixed by guys @ Apple: https://bugs.swift.org/browse/SR-5625 PR: https://github.com/apple/swift/pull/11341

like image 131
Cem Kozinoglu Avatar answered Nov 20 '22 06:11

Cem Kozinoglu