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
,
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...
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.
// 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()
}
}
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
It is fixed by guys @ Apple: https://bugs.swift.org/browse/SR-5625 PR: https://github.com/apple/swift/pull/11341
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With