Message from debugger: Terminated due to memory issue
Asked Answered
Y

7

21

My app working with Geojson file. I use MapBox SDK to add MGLPolyline to map. But the problem is my file too large, so that the app crash and got the error: Message from debugger: Terminated due to memory issue. I faced with 66234 objects at first loop. I tried to chunk the array to new array but not success. Please help me to solve the prolem. Here is my code for draw on map and here is my test project on github use Xcode 8.1 If have any different 3rd party which can solve my prolems is welcome too:

func drawPolyline() {

    // Parsing GeoJSON can be CPU intensive, do it on a background thread
    DispatchQueue.global(qos: .background).async {
        // Get the path for example.geojson in the app's bundle
        let jsonPath = Bundle.main.path(forResource: "KMLMAPNew", ofType: "json")
        let jsonData = NSData(contentsOfFile: jsonPath!)

        do {
            // Load and serialize the GeoJSON into a dictionary filled with properly-typed objects
            guard let jsonDict = try JSONSerialization.jsonObject(with: jsonData! as Data, options: []) as? Dictionary<String, AnyObject>, let features = jsonDict["features"] as? Array<AnyObject> else{return}

            for feature in features {
                guard let feature = feature as? Dictionary<String, AnyObject>, let geometry = feature["geometry"] as? Dictionary<String, AnyObject> else{ continue }

                if geometry["type"] as? String == "LineString" {
                    // Create an array to hold the formatted coordinates for our line
                    var coordinates: [CLLocationCoordinate2D] = []

                    if let locations = geometry["coordinates"] as? Array<AnyObject> {
                        // Iterate over line coordinates, stored in GeoJSON as many lng, lat arrays
                        for location in locations {
                            // Make a CLLocationCoordinate2D with the lat, lng
                            if let location = location as? Array<AnyObject>{
                                let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)

                                // Add coordinate to coordinates array
                                coordinates.append(coordinate)
                            }
                        }
                    }

                    let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))

                    // Optionally set the title of the polyline, which can be used for:
                    //  - Callout view
                    //  - Object identification
                    line.title = "Crema to Council Crest"

                    // Add the annotation on the main thread
                    DispatchQueue.main.async {
                        // Unowned reference to self to prevent retain cycle
                        [unowned self] in
                        self.mapboxView.addAnnotation(line)
                    }
                }
            }
        }
        catch
        {
            print("GeoJSON parsing failed")
        }
    }
}

EDIT::@Alessandro Ornano and @fragilecat thanks so much. But those solutions still cannot solve the terminate of the app on iPad. I think it so hard to change the current code to get it to work properly, because the data is so large. I think I will need another solution that works with big data. Like chunking the array into the small arrays then loading them by queue. But I don't know how to start :(

I send an email to the support team at MapBox, asking for suggestions.

Yabber answered 3/11, 2016 at 10:8 Comment(17)
This is opinion based, but I'd suggest perhaps considering guard statements in this case to increase readability by avoiding the "pyramid of doom".Illlooking
Please view updated code, which remove pyramid of doomYabber
I cannot get your project to run, I am unable to install the cocoa pod. Unable to find a specification for Mapbox-iOS-SDK (= 3.3) is the message I get. I would ask if you have run this using instruments to analysis where your memory issues are occurring.Holleyholli
@fragilecat: you need MacOs 10.12, Xcode 8, and install cocoapods before run pod installYabber
@Yabber Seems there are some problems with pods and Mapbox..Hotpress
Both @Alessandro Ornano and I have demonstrated that you can load the data without terminating the app, yes it is slow and un-responsive. Loading the data via chunks is not going to solve you problem. The number of annotations is simply to high. Your code maps most of the streets of Singapore. You need to look at what your requirements are and see if you can meet them in another way. Perhaps you can use less annotations for the scale the map is set at and introduce more as the user zooms in. That should help with rendering. Also it would help us if you stated what your end goal is. :)Holleyholli
@fragilecat I agree with you. At lee: Try to re-view your json and start with few elements on it, than add them step by step until you don't have this memory issue. I've check your json with 3 validate tools this morning and your json syntax is ok.Hotpress
I will try working more on it. Then tell you guys, if it work okie. Again, thank you to much, Alessandro Ornano and fragilecatYabber
@lee, have you fixed this issue? if so, what is the solution, while I am also facing same kind issue now.Optimistic
@AnilkumariOSdeveloper: you can use MapBox to solve this problem.Yabber
I already implemented MKMapview for my requirement, I can't go back to mapbox now. Even if they used mapbox, they got same issue right?Optimistic
anyway, any example for this kind issues for mapbox?Optimistic
You can contact mapbox support team for help.Yabber
@Yabber I am facing similar error, while using camera to click photos and save it to image array. This doesn't occur immediately but while clicking 15-20 photos and loading them into collectionview, it gives this error - "Terminated due to memory issue". Can you give a hint, how to trace the line or method to identify memory leakage? I have tried didrecievememorywarning method and zombie enabled, but none of both helped.Afterburner
@Mrunal: I don't know your code detail, so it's difficult to guess what it is. But you can try with load your images in the background thread, and using lazy loading(lib: SDWebImage). If you did so, just try to profile your project to find memory leaks, memory allocations, ..Yabber
@AnilkumariOSdeveloper Did you find a solution? I have the same requirementUrbane
@AbinBaby No, I did not found.Optimistic
S
18

One thing I have learnt from creating memory intensive apps is that you have to use autoreleasepool every time you create variables inside loops, if these loops are long

Review all your code and transform things like

func loopALot() {
    for _ in 0 ..< 5000 {
        let image = NSImage(contentsOfFile: filename)
    }
}

into

func loopALot() {
    for _ in 0 ..< 5000 {
      autoreleasepool {
        let image = NSImage(contentsOfFile: filename)
      }
    }
}

Review all kinds of loops for, while, etc.

This will force of iOS to release the variable and its correspondent memory usage at the end of every turn of the loop, instead of holding the variable and its memory usage until the function ends. That will reduce dramatically your memory usage.

Selfabsorption answered 11/11, 2016 at 19:8 Comment(2)
It did reduce the memory problem drastically, but how to use that image then to other operations on it ?Celestinacelestine
instead of allocating one image every time, use the same.Selfabsorption
H
16

The problem here is related to effective memory management. You are loading a lot of data via your json file. You realized that you needed to do the majority of the work on a background queue (thread) however the issue is how you are updating the UI via DispatchQueue.main.async function. In the current version of the drawPolyline() method you are switching between the background queue and the main queue 66234 times, given the number of objects in your first loop. Also you were creating the same number of CLLocationCoordinate2D arrays.

This leads to a huge memory footprint. You do not mention any requirements in regards to how you render the lines. So if we restructure your drawPolyline() method to use a instance variable for the CLLocationCoordinate2D array so we only use one and then we process all of the json file before we update the UI. The memory usage dropped down to a some what more manageable 664.6 MB.

enter image description here

Of course the rendering may not be exactly what you are looking for and if that's the case you might want to restructure your CLLocationCoordinate2D array into a more suitable data structure.

Below is your ViewController class with the rewritten drawPolyline() as drawPolyline2()

import UIKit
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {

@IBOutlet var mapboxView: MGLMapView!


fileprivate var coordinates = [[CLLocationCoordinate2D]]()
fileprivate var jsonData: NSData?

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    mapboxView = MGLMapView(frame: view.bounds)
    mapboxView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

    // mapboxView.setCenter(CLLocationCoordinate2D(latitude: 45.5076, longitude: -122.6736),
    //                             zoomLevel: 11, animated: false)

    mapboxView.setCenter(CLLocationCoordinate2D(latitude: 1.290270, longitude: 103.851959),
                         zoomLevel: 11, animated: false)


    view.addSubview(self.mapboxView)


    mapboxView.delegate = self
    mapboxView.allowsZooming = true

    drawPolyline2()
    //newWay()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}



func drawPolyline2() {

    DispatchQueue.global(qos: .background).async {

        if let path = Bundle.main.path(forResource: "KMLMAPNew", ofType: "json") {
            let fileURL = URL(fileURLWithPath: path)
            if let data = try? Data(contentsOf: fileURL) {

                do {

                    let dictionary = try JSONSerialization.jsonObject(with: data as Data, options: []) as? Dictionary<String, AnyObject>

                    if let features = dictionary?["features"] as? Array<AnyObject> {

                        print("** START **")

                        for feature in features {
                            guard let feature = feature as? Dictionary<String, AnyObject>, let geometry = feature["geometry"] as? Dictionary<String, AnyObject> else { continue }

                            if geometry["type"] as? String == "LineString" {
                                // Create an array to hold the formatted coordinates for our line

                                if let locations = geometry["coordinates"] as? Array<AnyObject> {
                                    // Iterate over line coordinates, stored in GeoJSON as many lng, lat arrays

                                    var featureCoordinates = [CLLocationCoordinate2D]()

                                    for location in locations {
                                        // Make a CLLocationCoordinate2D with the lat, lng
                                        if let location = location as? Array<AnyObject>{
                                            let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)

                                            // Add coordinate to coordinates array
                                            featureCoordinates.append(coordinate)
                                        }
                                    }

                                    // Uncomment if you need to store for later use.
                                    //self.coordinates.append(featureCoordinates)

                                    DispatchQueue.main.async {
                                        let line = MGLPolyline(coordinates: &featureCoordinates, count: UInt(featureCoordinates.count))

                                        // Optionally set the title of the polyline, which can be used for:
                                        //  - Callout view
                                        //  - Object identification
                                        line.title = "Crema to Council Crest"
                                        self.mapboxView.addAnnotation(line)

                                    }


                                }

                            }

                        }

                        print("** FINISH **")

                    }

                } catch {
                    print("GeoJSON parsing failed")
                }
            }
        }
    }
}


func drawSmallListObj(list: [Dictionary<String, AnyObject>]){
    for obj in list{
        //            print(obj)
        if let feature = obj as? Dictionary<String, AnyObject> {
            if let geometry = feature["geometry"] as? Dictionary<String, AnyObject> {
                if geometry["type"] as? String == "LineString" {
                    // Create an array to hold the formatted coordinates for our line
                    var coordinates: [CLLocationCoordinate2D] = []

                    if let locations = geometry["coordinates"] as? Array<AnyObject> {
                        // Iterate over line coordinates, stored in GeoJSON as many lng, lat arrays
                        for location in locations {
                            // Make a CLLocationCoordinate2D with the lat, lng
                            if let location = location as? Array<AnyObject>{
                                let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)

                                // Add coordinate to coordinates array
                                coordinates.append(coordinate)
                            }
                        }
                    }

                    let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))

                    // Optionally set the title of the polyline, which can be used for:
                    //  - Callout view
                    //  - Object identification
                    line.title = "Crema to Council Crest"

                    // Add the annotation on the main thread
                    DispatchQueue.main.async {
                        // Unowned reference to self to prevent retain cycle
                        [unowned self] in
                        self.mapboxView.addAnnotation(line)
                    }
                }
            }
        }
    }
}
func mapView(_ mapView: MGLMapView, alphaForShapeAnnotation annotation: MGLShape) -> CGFloat {
    // Set the alpha for all shape annotations to 1 (full opacity)
    return 1
}

func mapView(_ mapView: MGLMapView, lineWidthForPolylineAnnotation annotation: MGLPolyline) -> CGFloat {
    // Set the line width for polyline annotations
    return 2.0
}

func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor {
    // Give our polyline a unique color by checking for its `title` property
    if (annotation.title == "Crema to Council Crest" && annotation is MGLPolyline) {
        // Mapbox cyan
        return UIColor(red: 59/255, green:178/255, blue:208/255, alpha:1)
    }
    else
    {
        return UIColor.red
    }
}


}

enter image description here

Holleyholli answered 9/11, 2016 at 14:36 Comment(5)
Your code is not crash. But you move self.mapboxView.addAnnotation(line) out of condition: if geometry["type"] as? String == "LineString" {}. So that the data display on map is wrong. With your solution,I tried to move self.mapboxView.addAnnotation(line) into the condition, it still crash :(Yabber
You have to keep the switching between main and the background queues to a minimum. I don't know what you are trying to draw other than a set of points, as I mentioned above it may not get the drawing you want, if that is the case then you need to create a more detailed data source other than a flat array. The key is that you process your data AND THEN render it to the map. Without knowing what your drawing I cannot give you any more detail. Please provide screen shots of what your trying to do. At present when I run your project I get a map of Portland OR USA.Holleyholli
My code can load okie if I reduce the size of the file less than 12 times:for i in 0..<features.count/12. Please view the image from link : http://www.filetolink.com/f03e1fbff4 . But with out reduce it, the app crash.Yabber
Your code can fix crash, but the data on map display wrong. Please compare your solution in map, with my image on the link above. Then please give me the solution in detail. Thanks so much.Yabber
The code can print out ** START ** and ** FINISH ** but the app got the stuck here. Cannot zoom in zoom out or move the map.Yabber
I
3

First Solution

Maybe your for loop is running infinitely and allocating memory to an array with nil value every time. It is using high amounts of memory, so it gives this error.

Please check by print something in the for loop.

Second Solution

Add this in didReceiveMemoryWarning:

NSURLCache.sharedURLCache().removeAllCachedResponses()
NSURLCache.sharedURLCache().diskCapacity = 0
NSURLCache.sharedURLCache().memoryCapacity = 0

You can also change the cache policy of the NSURLRequest:

let day_url = NSURL(string: "http://www.example.com")
let day_url_request = NSURLRequest(URL: day_url,
    cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData,
    timeoutInterval: 10.0)

let day_webView = UIWebView()
day_webView.loadRequest(day_url_request)

More information on cache policies here.

Infusible answered 8/11, 2016 at 12:48 Comment(1)
don't know if you run my test project or not yet. But this not resolve the problem.Yabber
H
3

I had some problems to test your project with pods, so I've directly remove pods and use Mapbox framework directly from here.

I've no problems for the first launches both in simulator and real iPad (my iPad 4 gen.) but after a while I've your same error, so I've correct this code with:

DispatchQueue.main.async {
      // weaked reference to self to prevent retain cycle
      [weak self] in
      guard let strongSelf = self else { return } 
      strongSelf.mapboxView.addAnnotation(line)
}

because unowned it's not enough to prevent retain cycle. Now seems it works well.

Hope it helps.

P.S. (I've used the latest available Mapbox v3.3.6)


Update (after comments):

So, first of all I make all my test with Mapbox framework inserted as "embedded framework".

I've make some corrections to your github project only to ViewController.swift to avoid retain cycles. P.S. I remove comments lines to make easy reading:

func drawPolyline() {
        DispatchQueue.global(qos: .background).async {
            [weak self] in
            guard let strongSelf = self else { return }
            let jsonPath = Bundle.main.path(forResource: "KMLMAPNew", ofType: "json")
            let jsonData = NSData(contentsOfFile: jsonPath!)
            do {
                guard let jsonDict = try JSONSerialization.jsonObject(with: jsonData! as Data, options: []) as? Dictionary<String, AnyObject>, let features = jsonDict["features"] as? Array<AnyObject> else{return}
                for feature in features {
                    guard let feature = feature as? Dictionary<String, AnyObject>, let geometry = feature["geometry"] as? Dictionary<String, AnyObject> else{ continue }
                    if geometry["type"] as? String == "LineString" {
                        var coordinates: [CLLocationCoordinate2D] = []
                        if let locations = geometry["coordinates"] as? Array<AnyObject> {
                            for location in locations {
                                if let location = location as? Array<AnyObject>{
                                    let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)
                                    coordinates.append(coordinate)
                                }
                            }
                        }
                        let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
                        line.title = "Crema to Council Crest"
                        print(feature) // Added this line just for debug to see the flow..
                        DispatchQueue.main.async {
                            strongSelf.mapboxView.addAnnotation(line)
                        }
                    }
                }
            }
            catch
            {
                print("GeoJSON parsing failed")
            }
        }
    }

func newWay(){
        DispatchQueue.global(qos: .background).async {
            [weak self] in
            guard let strongSelf = self else { return }
            let jsonPath = Bundle.main.path(forResource: "KMLMAPNew", ofType: "json")
            let jsonData = NSData(contentsOfFile: jsonPath!)
            do {
                if let jsonDict = try JSONSerialization.jsonObject(with: jsonData! as Data, options: []) as? Dictionary<String, AnyObject> {
                    if let features = jsonDict["features"] as? Array<AnyObject> {
                        let chunks = stride(from: 0, to: features.count, by: 2).map {
                            Array(features[$0..<min($0 + 2, features.count)])
                        }
                        for obj in chunks{
                            strongSelf.drawSmallListObj(list: obj as! [Dictionary<String, AnyObject>])
                        }
                    }
                }
            }
            catch
            {
                print("GeoJSON parsing failed")
            }
        }
    }

func drawSmallListObj(list: [Dictionary<String, AnyObject>]){
        for obj in list{
            if let feature = obj as? Dictionary<String, AnyObject> {
                if let geometry = feature["geometry"] as? Dictionary<String, AnyObject> {
                    if geometry["type"] as? String == "LineString" {
                        var coordinates: [CLLocationCoordinate2D] = []
                        if let locations = geometry["coordinates"] as? Array<AnyObject> {
                            for location in locations {
                                if let location = location as? Array<AnyObject>{
                                    let coordinate = CLLocationCoordinate2DMake(location[1].doubleValue, location[0].doubleValue)
                                    coordinates.append(coordinate)
                                }
                            }
                        }
                        let line = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
                        line.title = "Crema to Council Crest"
                        DispatchQueue.main.async {
                            [weak self] in
                            guard let strongSelf = self else { return }
                            strongSelf.mapboxView.addAnnotation(line)
                        }
                    }
                }
            }
        }
    }
Hotpress answered 10/11, 2016 at 10:22 Comment(8)
Have you launch instruments? I don't see leaks now. Try to remove the app and re-install, also reboot the iPad and be sure to use the latest Mapbox version. Let me know.Hotpress
I used Mapbox v3.3.6 too.Tried to restart iPad, delete and install new app too, but the app still run so long then terminated.Yabber
Run the app again, then app get stuck instate of terminated. It's seem main thread is stuck forever.Yabber
waiting for a long time then get the message: Message from debugger: Terminated due to memory issue.Yabber
I try to help you but I don't get this message , argh! So strange..I don't have iPad Pro so I've try both simulator and my iPad 4 now but nothing. (PS iOS 10.1.1)Hotpress
Thanks for your support. Upgrade to 10.1.1 but still got the same problem :(.Yabber
Now I try other ways to get the same issue. After my tries I will know my results..Hotpress
All right, I've update my answer, make some corrections to your code, launch many tests and seems all stable and ok, also debug prints are ok. Let me know about your tests.Hotpress
A
3

Will share my experience with this strange issue.

For me the app crashed with "Message from debugger: Terminated due to memory issue" and instruments didn't help a lot. As well the Memory - was within the green limits. So I was not sure what is causing that. And it was not possible to debug, and single-device specific issue.

Just restarted the iPhone 6 - and the issue disappeared for now.

Adalard answered 11/6, 2019 at 9:22 Comment(1)
I experienced this issue when trying to take a GPU Capture of a SceneKit scene with Metal materials. Closing all background apps made the message go away.Colin
S
1

make you stuff on callout this means execute polyne only when click on the pin func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)

Swearingen answered 2/4, 2017 at 1:36 Comment(0)
M
0

I was receiving this error and was very confused since my app's memory usage was fairly small.

In the end, I discovered that it was due to me loading a number of files as mapped memory, e.g:

let data = try Data(contentsOf: url, options: .mappedIfSafe)

I don't know why I was getting these bizarre crashes, but just loading the data normally prevented the crashes from happening.

Merritt answered 24/6, 2021 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.