Swift Memory Leak on NSArray - ios

I'm developing an app using the Singleton Pattern and Swift programming language. When I Profile the app with Instruments, I noticed that there's a memory leak pointing to an NSArray. Instruments is pointing to the following line of code (Please check screenshot). Can anyone find why the leak is happening? I tried to initialize the array Workout as:
workout = []
The leak wasn't reported. Maybe it has something to do with the unarchiving?

Checkout this answer here: Swift Decode Array Custom Class Memory Leak It seems to be a bug. I had the same problem too. So instead of directly assign the value to workout, you can do:
if let wo = NSKeyedUnarchiver.unarchiveObjectWithFile(Utilities.getFileURL("workout")) as? [ExceciseObject] {
workout = wo.map { $0 }
}

Related

What does this Swift iPad crash log mean? [duplicate]

This question already has an answer here:
`save(to:for:completionHandler:)` of `UIDocument` crashes
(1 answer)
Closed 5 years ago.
App runs fine on every device that qualifies for deployment target of 9.3+ except iPad2. The url is good. Works on every iPhone and every other iPad. The crash is on a physical iPad2 and Simulator iPad2 iOS 9.3.
doc.save(to: target, for: .forCreating, completionHandler: {(success) in
if (success) {
print("Save succeeded")
}
} else {
print("Save failed")
}
})
This is where it crashes. Get to a breakpoint at this line, and do not get to breakpoints in completion handler or either print. Again, just the one model of just iPad.
The crash log is over my head. Does it make sense to any of you? Thank you.
Edit: expanded the crash log
tl;dr: It's a bug in the Swift Data type. Solution: In your UIDocument contents(forType:) implementation, change this:
return data
to this:
return NSData(data:data)
More info: Note that merely casting data to NSData won't help:
return data as NSData
That doesn't get us any further because it's the bridging that's the problem. We're already bridging to NSData and it isn't helping. You have to create a completely new object that is not a Swift Data object.
Even more info: For future generations who come along and want to test this, the crash can be reliably reproduced as follows. Make a new Single View project, with an app delegate and a ViewController as usual. In the ViewController.swift file, put this:
import UIKit
class WhatsUpDoc: UIDocument {
var array = [String]()
override func load(fromContents contents: Any, ofType typeName: String?) throws {}
override func contents(forType typeName: String) throws -> Any {
let data = NSKeyedArchiver.archivedData(withRootObject: array)
return data // comment out this line to avoid the crash
return NSData(data:data)
}
}
class ViewController: UIViewController {
var doc : WhatsUpDoc?
override func viewDidLoad() {
super.viewDidLoad()
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let baseURL = documentsDirectory.appendingPathComponent("Untitled")
let doc = WhatsUpDoc(fileURL: baseURL)
self.doc = doc
self.doc!.save(to:self.doc!.fileURL, for: .forCreating)
}
}
Configure your project to have a deployment target of 9.0, and make sure you've got a 9.0 simulator SDK on hand. In Window > Devices, give yourself an iPad 2 simulator. Set that simulator as the project's destination. Run, and crash. Comment out the line that says to comment it out, and don't crash.
Post-analysis Q&A:
Wait, so what exactly _is_ the bug? You're not saying you can't write a Swift Data object to disk, are you?
No, it's got something to do with the peculiar threaded nature of writing during a UIDocument file save. Note that we are crashing in a background thread (thread 10 or 11 in the OP's screen shots). And what we are crashing in is a method of SwiftNSData, the NSData wrapped by a Swift Data. We're trying to enumerate the data's bytes on this background thread, and we can't; presumably this isn't thread-safe on certain device types, i.e. 32-bit devices. My solution is to get Swift Data out of the picture completely.
Okay, so it turns out this bug is known; the answer is given here: https://stackoverflow.com/a/41163395/341994 How about that?
Okay, but I figured it out independently in response to the current question. I didn't find out about the other question and answer until later, when it occurred to me to do a search. So I've marked the current question as a duplicate, but I'm leaving my answer for historical purposes, esp. as it gives a clear test case (which the other question does not).
By the way, I do not know why using NSMutableData(data:) would be better than my solution of using NSData(data:), but that's what Apple says to do, so let's just take it as gospel.

Memory Leak using Property List / NSMutableDictionary

I'm using a Property List in my main bundle to store information about the levels and player. The pList gets copied over to the phone the first time the app is launched, then I access the information as needed from that local copy. I'm running Leaks in Instruments, and I keep coming across memory leaks that I believe are related to creating the dictionary and other data types stored in the pList.
Here's where the dictionary is created - after finding the path to the pList:
if fileManager.fileExists(atPath: path.path) {
if let dictionaryForPlist = NSMutableDictionary(contentsOf: path) {
return(dictionaryForPlist)
}
else {
print("pList not found")
}
let levelInstanceData = LevelData() //this class searches the main bundle for the plist and stores the pList as an NSMutableDictionary
let currentLevel = levelInstanceData.localDataFile["Level1"] as! Int //localDataFile is the NSMutableDictionary storing the information
let levelName = levelInstanceData.localDataFile["Level1Name"] as! String
I forcefully cast each piece of data to the correct data type and use it throughout the level.
Here is a screenshot of the leaked objects in Instruments. Has anyone else had this issue or have any ideas how to stop the leaks?
Providing all the leaked objects isn't overly helpful. What you will need to do is look at the call tree related to each leaked object. This will show you where in code the leak has originated from. And from there you can start to deduce what needs to be done to remedy it.
You should read this. It is dated, but it discusses the call tree.
https://www.raywenderlich.com/2696/instruments-tutorial-for-ios-how-to-debug-memory-leaks

Swift Dictionary Absurd Memory Usage

I ran into an interesting problem in one of my applications. When accessing a Dictionary many times, the memory usage of my application skyrockets to over a gigabyte in seconds. Here is some sample code to show the problem.
override func viewDidLoad() {
let dictionary = ["key1":"value1"]
let nsKey: NSString = "key1"
let swiftKey = nsKey as String
for _ in 0 ... 10000000 {
dictionary[swiftKey]
}
}
Repeatedly accessing the dictionary causes memory to climb until the loop finishes. I looked at instruments and saw tons of string allocations. Turns out using an NSString is the issue.
Changing the nsKey to a swift String like so fixes the issue:
let nsKey = "key1"
Also changing the dictionary to an NSDictionary fixes the issue:
let dictionary: NSDictionary = ["key1":"value1"]
Does anyone know why accessing the dictionary using a casted NSString causes so much heap allocation, and are there any other fixes besides the ones described above?
Here are some pictures. It looks like behind-the-scenes strings are being allocated and set to autorelease (or am I reading the data below wrong?) Could this be why memory usage continuously allocates and then drains at a later point? If this is true, should this be considered a "bug"? This issue occurs on OS X as well as iOS.
The best solution is to not bridge to NSString here. Just use Swift types. Or, as you discovered, you can just use Foundation types (NSString and NSDictionary). Bridging can require making temporary copies.
In any case, though, in loops like this it's very common to create temporary copies for one reason or another (even if you avoided this particular problem). To address that, you need to drain your autorelease pool in the loop. For instance:
let dictionary = ["key1":"value1"]
let nsKey: NSString = "key1"
let swiftKey = nsKey as String
for _ in 0 ... 10000000 {
autoreleasepool { // <=== the scope of the current pool
dictionary[swiftKey]
}
}
Adding that will keep your memory steady. This is a very common thing to do in large loops in Cocoa. Otherwise the pool won't be drained until you return from your top-level method.

What is wrong with this line of Swift iOS Code?

I have created an iOS app using Swift and everything is working fine and dandy on the simulator. I get no errors or crashes at all, but when I submit my app to put up on the app store Apple rejects it and lets me know that it crashes when the user makes a selection. I cannot recreate this error/crash. I took the crash logs and symbolicated them. This line of code came up as the culprit for the crashes:
linksToPass = getLinks(season) as [String:[String]]
This line is trying to store the resulting Dictionary from the getLinks() function I created. It for sure is getting a dictionary and if there is no dictionary to send back I create a dictionary which has error information in it, so it is for sure returning a dictionary in that format no matter what. Seeing as I cannot recreate the crash, I am just trying to error check this line of code in any way possible so it does't crash when I resubmit to Apple.
I tried checking if the resulting dictionary was nil like so:
if(getLinks(seasons) != nil){
linksToPass = getLinks(season) as [String:[String]]
}
This is not valid though, and XCode lets me know that UInt8 is not compatible with NSDictionary or something of that nature.
I then fixed that line and changed it to this:
if(getLinks(seasons) != ["":[""]]){
linksToPass = getLinks(season) as [String:[String]]
}
I am just not sure if this is even a good way to check for errors. I was wondering if there were any suggestions on how I may go about making sure this line does not fail and result in a crash. Thank you very much.
EDIT:
Here is my getLinks() function if that helps add more info to the problem:
var season = ""
let hymn_links = Hymn_Links()
func getLinks (nameofseason:String) -> NSDictionary
{
switch (nameofseason)
{
default:
return ["Maps Not Found": []]
}
}
EDIT #2:
This is my updated getLinks() function with the use of optionals.
func getLinks (nameofseason:String) -> NSDictionary?
{
switch (nameofseason)
{
default:
return nil
}
}
Also in my statement of linksToPass I changed it to:
if let links = getLinks(season) as? [String:[String]]
{
linksToPass = links
hymnnames = [String] (linksToPass.keys)
}
There are some known issues with the Swift optimiser. Some people have resorted to shipping with debug builds.
My suggestion would be to test with an optimised build to see if you can reproduce it. You can then try shipping a debug build to the App store.
General Code Comments
Why are you returning an NSDictionary rather than a Swift dictionary anyway? Without knowing the contents and creation method for your hymn_links object I can't be sure how good it is.
I would avoid as casts until Swift 1.2 and stick to using as? and then handling the nil case. At least in your "Edit 2" a nil will cause a crash as nil cannot be cast to [String:[String]] although [String:[String]]? should be possible.
Can you guarantee that all of the items returned by the switch statement will never under any circumstances be nil? If not getLinks should return an Optional.
Note that is is virtually impossible for getLinks to know that one of the items will never be nil and in Swift un-handed nils are a crash waiting to happen. Unless all these methods correctly handle nil.
Return an Optional and handle that in the statement that calls getLinks.
Languages handle nils differently, Objective-C handles them rather well, Java and Swift by crashing. But Swift has a mechanism to handle nils without crashing: Optionals, use it.

parsing json in swift

I'm trying to read the Linkedin response in swift.
My object is something like this ["positions":["values":["data1","data2","data3"]]]
if let positions: NSDictionary = info["positions"] as NSDictionary!{
if let positionsInfo: [NSDictionary] = positions["values"] as? [NSDictionary]{
for position : NSDictionary! in positionsInfo {
dosomething(position, person:usr)
}
}
}
If I do a StepOver line by line it works correctly. But if I run it i'll get a EXC_BAD_ADDRESS(code=1,address=0x7966b04) I enabled Zombie objects and ran it on Instruments. I'm pretty sure this is the code which is causing the problem. But not sure what is wrong with it.
The moment you used ! you opened yourself up for crashes if there were any problem. You must use as? to make sure that the data is actually what you think it is.
There are many blog posts out there on how to safely parse JSON into Swift data structures. It's now almost a rite of passage for Swift bloggers.
http://robots.thoughtbot.com/efficient-json-in-swift-with-functional-concepts-and-generics
http://chris.eidhof.nl/posts/json-parsing-in-swift.html
https://github.com/owensd/json-swift
https://github.com/lingoer/SwiftyJSON
Of course the many packages: https://github.com/search?q=%5Bswift%5D+json
http://robnapier.net/functional-wish-fulfillment - My own version on top of all the others

Resources