Swift 3 enums leak memory when the class contains an array - ios

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.
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

Related

Xcode12; New “Subclass MPMediaItem must implement -valueForProperty” error

Just updated to Xcode 12.0 from the last version of Xcode 11/iOS13 and am getting an error in AppDelegate: Thread 1: "Subclass MPMediaItem must implement -valueForProperty: defined in [MPMediaItem class]."
When the app starts, I MPMediaQuery the library for songs and store them to a #Published variable in an Observable Object like this:
#Published var songs = [MPMediaItem]()
init() {
self.songs = MPMediaQuery.songs().items
}
Later when I want to access a property I do so like this:
Text(self.observableObject.songs[0].title)
I’ve tried changing these to use .value(forProperty: "MPMediaItemPropertyTitle") but it doesn’t feel to be use a string over a property (and Xcode then pops up errors like Failed to produce diagnostic for expression; please file a bug report apple.) AFAIK, I’m not subclassing MPMediaItem anywhere and I’ve tried Googling the error above with no luck.
Anyone know what’s going on?
I'm not sure if this is useful to your situation, but I recently came across a simiar problem.
In my case I needed to change 2 things that triggered this crash:
I was initialising an empty MPMediaItem() as placeholder when no current song was loaded. The crash happened when trying to access a property (e.g. title) on this empty instance. Upon removing the empty MPMediaItem() and implementing a different placeholder approach, the problem went away.
To test music on the simlator I was using a DummyMediaQuery: MPMediaQuery which returned an array of DummyMediaItem: MPMediaItem. In the DummyMediaItem class I did:
final class DummyMediaItem: MPMediaItem {
private let _artist: String
override var artist: String { return _artist }
private let _title: String
override var title: String { return _title }
// and so on...
// fix: crash in iOS 14
override func value(forProperty property: String) -> Any? {
return nil
}
}

Can't create protocol that is restricted to Classes in Xcode 9.3 / Swift 4.1

I am trying to store a list of subscribers that are ThemeListeners (mostly UIViews or UIViewControllers) and I need these to be stored weakly, otherwise the UIViewControllers are never released and I get memory leaks.
I took the WeakRef class from
https://marcosantadev.com/swift-arrays-holding-elements-weak-references/
When I put this into my project and try and compile it I get an error on the last line in Xcode 9.3:
'WeakRef' requires that 'ThemeListener' be a class type
Compiling this in Xcode 9.2 works.
class WeakRef<T> where T:AnyObject
{
private(set) weak var value : T?
init( value:T?)
{
self.value = value
}
}
protocol ThemeListener : AnyObject
{
}
typealias WeakRefThemeListener = WeakRef<ThemeListener>
Does anyone have any suggestions on how to fix this. I also have the same problem when trying to use NSHashTable.
I think your protocol should be like this:
protocol ThemeListener : class
{}

Swift memory leak when iterating array of Errors

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()
}
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?
This bug was Fixed in Xcode 9.3.

Understanding difference in Swift properties for structs and classes in assignment

My question is in regards to an error that I kept on seeing while writing a function to initialize an optional array in a struct that I solved by just changing the struct to the class. I am hoping that someone can explain to me what I am not understanding about structs and classes that is causing this problem. Here is my code.
struct DataStorage {
//When I change this to class DataStorage this works
var listOfVariables = [VariableType]
var allDataPoints: [[DataPoint]]?
init() {
listOfVariables = VariableType.getAllVariables(managedObjectContext)
}
func initializeAllDataPoints() {
//THIS IS THE LINE IN QUESTION
allDataPoints = [[DataPoint]](count: listOfVariables.count, repeatedValue: [DataPoint]())
}
}
So, the function initializeAllDataPoints is what is causing the error and that is truly the relevant part of this question. The error that I get is Cannot assign to 'allDataPoints' in 'self'. I only get this error when DataStorage is a struct and I don't get it when DataStorage is a class. What am I not understanding about classes and structs that is causing this difference in behavior?
Whenever a method in a struct modifies one of its own properties, you have to use the mutating keyword.
I believe that if you write:
mutating func intializeAllDataPoints() { ... }
it should work for you.
This article gives a little more background information.

What is the cause of EXC_BAD_ACCESS crashes for classes declared in separate files?

I have come across this problem several times now, without ever finding the cause.
When attempting to access a property of ClassY in ClassX I get an EXC_BAD_ACCESS error. This happens even if I instantiate a clean copy and attempt to access the property immediately:
let classY = ClassY()
println(classY.someTextProperty)
EXC_BAD_ACCESS
As far as I am aware this error should only be caused by trying to access a deallocated instance, which presumably cannot be the case in the above code.
Where it gets strange, is that if I declare ClassY in the same file as ClassX then the problem disappears. (ClassY does have some private properties, but I am not trying to access these and anyway, making them all public does not solve the problem)
I am not including the source code here since there is more of it than is practical to isolate. I have not been able to reproduce this reliably in a standalone project because I am unable to find any kind of pattern.
Has anyone come across a similar kind of problem, and does anyone have an idea for a solution (obviously I can include the definition in the same file, but that is a workaround rather than a solution)?
I am currently running iOS 8.3 / Swift 1.2 but had this problem already in iOS 8.1.
UPDATE
Following lots of trial and error, it seems that the problem comes from lazy properties and protocol conformance. Again this is not something I can reproduce in a plain playground (perhaps the declarations need to be in different files).
My ClassY conforms to ProtocolY which has several properties:
protocol ProtocolY {
var number: Int { get }
var text: String { get }
}
When ClassY conforms to ProtocolY, if it implements either of the properties as a lazy property then accessing a property causes the EXC_BAD_ACCESS crash. If ClassY is not marked as conforming to ProtocolY or if none of the properties declared in ProtocolY as implemented by ClassY are lazy properties then no crash occurs.
Obviously this is just narrowing down but still does not give me an explanation. I know of no reason why implementations of protocol properties should not be lazy.
UPDATE Here is an example of properties on ClassY:
private(set) lazy var currentPage: Int = {
let subject: RACReplaySubject = RACReplaySubject(capacity: 1)
subject.sendNext(self.currentPage)
return subject
}()
var currentPage: Int {
didSet {
(self.currentPageSignal as? RACSubject)?.sendNext(self.currentPage)
}
}
currentPage is set in the init method.
If I use instead:
private(set) var currentPage: Int = {
let subject: RACReplaySubject = RACReplaySubject(capacity: 1)
return subject
}()
var currentPage: Int {
didSet {
(self.currentPageSignal as? RACSubject)?.sendNext(self.currentPage)
}
}
(and do a sendNext in the init method) then everything works fine.
I'm fairly confident that the fact that I'm using ReactiveCocoa is irrelevant - I have also tried lazy properties which are simple Strings, with the same result.

Resources