Accessor Gives the Wrong Value in Swift 1.2/2.0 Release Build Only

Accessor gives the wrong value in Swift 1.2/2.0 Release build only

I think you've found a bug. A really interesting bug.

And I have a workaround for you: make Attribute a class instead of a struct. It will still be a value class, so the overhead will be low. You will have to give it an initializer that does what the struct memberwise initializer does. You'll find that the whole problem goes away when you do this.

EDIT: I thought of an even better workaround: Instead of making Attribute a class, make Level an @objc enum.

EDIT: The OP reports that this bug is fixed in Swift 2.1.

Mysterious crashes in Swift 1.2 - in Release builds only

Amazingly, I did track this down, mostly by deleting code in large swatches until I was down to just this (it's a view controller):

class LessonListController: UIViewController {
var terms : [Term]
// var terms : NSArray
init(terms data:NSArray) {
let arr = data.sortedArrayUsingDescriptors([NSSortDescriptor(key: "lessonSection", ascending: true)])
self.terms = arr as! [Term]
// self.terms = arr
super.init(nibName:"LessonList", bundle:nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@IBAction func doDismiss(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}

If (in a Release build) we present this view controller and then dismiss it, we crash on dismiss - in dealloc, which proves my theory that it's a problem with memory management.

Having isolated the code, I was able to try various alternatives. It is clear that the problem is the property var terms : [Term] (because the only thing Swift is doing under the hood in dealloc is releasing this array). The value of this property, as you can see in my init, is an NSArray that has come from Cocoa (thru sortedArrayUsingDescriptors) and has been cast to a Swift array. By trial and error, I discovered:

  • If we change the implementation so that the property is an NSArray (see the commented-out alternative lines), we don't crash.

  • Or, if we don't sort (so that this NSArray doesn't come from Cocoa), we don't crash.

  • Or (wait for it), if we replace self.terms = arr as! [Term] with self.terms = arr as NSArray as! [Term], we don't crash!

But that third alternative is a workaround. I went through all my code in all my apps looking for as! [SomeType] casts and replaced them all with as NSArray as [SomeType], and all my crashes went away!!

My theory is that something is going wrong with Swift's memory management in the optimized Release build just in the very specific situation where an NSArray arrives from Cocoa and is bridged for us to an [AnyObject] before our code can get hold of it. Such an NSArray is not crossing the bridge properly. But by casting to NSArray and then back to down to the specific [SomeType] Swift array, the problem is solved.

Naturally, I assume that when Apple figures this out, they'll fix it and then we can stop using this workaround. But until then, my apps are running in a Release build once again.

Swift 3 enums leak memory when the class contains an array

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

Enum initialized with a non-existent rawValue does not fail and return nil

I filed a bug with Apple and this is the reply I received:

"Engineering has determined that this issue behaves as intended based on the following information:

Because C enums may have values added in future releases, or even have "private case" values used by the framework that are not included in the headers, there's no way to check whether a value provided in Swift is actually valid or invalid. Therefore, init(rawValue:) is obliged to produce a value just as a C cast would. There are discussions in the Swift Open Source project on how to improve this situation in later versions of Swift, but the initializer for MKMapType still won't return nil."

Thanks to Apple Engineering for this explanation.



Related Topics



Leave a reply



Submit