web-dev-qa-db-fra.com

Message du débogueur: Terminé en raison d'un problème de mémoire

Mon application fonctionne avec le fichier Geojson. J'utilise MapBox SDK pour ajouter MGLPolyline à la carte. Mais le problème est que mon fichier est trop volumineux, de sorte que l'application se bloque et génère l'erreur: Message from debugger: Terminated due to memory issue. J'ai rencontré 66234 objets lors de la première boucle. J'ai essayé de fragmenter le tableau en un nouveau tableau, mais sans succès. S'il vous plaît, aidez-moi à résoudre le problème. Voici mon code pour dessiner sur la carte et voici mon projet de test sur github utiliser Xcode 8.1 } _ Si toute tierce partie différente qui peut résoudre mes problèmes est la bienvenue:

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 et @fragilecat, merci beaucoup. Mais ces solutions ne peuvent toujours pas résoudre la fin de l'application sur iPad. Je pense qu'il est très difficile de modifier le code actuel pour le faire fonctionner correctement, car les données sont très volumineuses. Je pense que j'aurai besoin d'une autre solution qui fonctionne avec le Big Data. C'est comme couper le tableau en petits tableaux puis les charger en file d'attente. Mais je ne sais pas comment commencer :(

J'envoie un courrier électronique à l'équipe de support de MapBox, demandant des suggestions.

7
lee

Le problème ici est lié à la gestion efficace de la mémoire. Vous chargez beaucoup de données via votre fichier json. Vous vous êtes rendu compte que vous deviez effectuer la majorité du travail sur une file d'attente en arrière-plan (thread), mais le problème est de savoir comment vous mettez à jour l'interface utilisateur via la fonction DispatchQueue.main.async. Dans la version actuelle de la méthode drawPolyline(), vous passez 66234 fois entre la file d'attente en arrière-plan et la file principale, en fonction du nombre d'objets de votre première boucle. De plus, vous créiez le même nombre de tableaux CLLocationCoordinate2D.

Cela conduit à une énorme empreinte mémoire. Vous ne mentionnez aucune exigence en ce qui concerne la manière de rendre les lignes. Donc, si nous restructurons votre méthode drawPolyline() pour utiliser une variable d'instance pour le tableau CLLocationCoordinate2D, nous n'en utiliserons donc qu'une, puis nous traiterons tout le fichier json avant de mettre à jour l'interface utilisateur. L’utilisation de la mémoire est tombée à 664,6 Mo, ce qui est plus gérable.

 enter image description here

Bien sûr, le rendu peut ne pas être exactement ce que vous recherchez et, si tel est le cas, vous voudrez peut-être restructurer votre tableau CLLocationCoordinate2D en une structure de données plus appropriée.

Ci-dessous, votre classe ViewController avec la drawPolyline() réécrite en tant que 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

9
Pete Hornsby

Une chose que j'ai apprise en créant des applications gourmandes en mémoire est qu'il faut utiliser autoreleasepool chaque fois que vous créez des variables dans des boucles, si ces boucles sont longues.

Examinez tout votre code et transformez des choses comme

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

dans

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

Passez en revue tous les types de boucles for, while, etc.

Ceci obligera iOS à libérer la variable et son utilisation de mémoire correspondante à la fin de chaque tour de la boucle, au lieu de conserver la variable et son utilisation de mémoire jusqu'à la fin de la fonction. Cela réduira considérablement votre utilisation de la mémoire. 

9
SpaceDog

J'ai eu quelques problèmes pour tester votre projet avec des pods. J'ai donc directement supprimé les pods et utilisé le framework Mapbox directement depuis ici .

Je n'ai eu aucun problème pour les premiers lancements à la fois sur simulateur et sur iPad réel (mon iPad 4 gén.), Mais après un certain temps, j'ai la même erreur, j'ai donc corrigé ce code avec:

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

parce que unowned il ne suffit pas d'empêcher le cycle de rétention ..__ semble maintenant que cela fonctionne bien.

J'espère que ça aide. 

P.S.(J'ai utilisé la dernière version disponible de Mapbox v3.3.6)}


Mise à jour (après commentaires):

Donc, tout d’abord, je fais tous mes tests avec Mapbox Framework inséré en tant que "framework embarqué".

J'ai apporté quelques corrections à votre projet github uniquement dans ViewController.Swift pour éviter de conserver les cycles .P.S. Je supprime les lignes de commentaires pour faciliter la lecture:

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)
                        }
                    }
                }
            }
        }
    }
2
Alessandro Ornano

faites-vous des trucs sur la légende, cela signifie exécuter polyne que lorsque vous cliquez sur l'épingle func mapView (_ mapView: MKMapView, didSelect view: MKAnnotationView)

1
demopix

Première solution

Peut-être que votre boucle for fonctionne pendant une durée infinie avec l’allocation de mémoire à un tableau avec une valeur nulle chaque fois qu’elle utilise une mémoire haute, ce qui donne cette erreur.

S'il vous plaît vérifier par imprimer quelque chose pour la boucle.

Deuxième solution

Ajouter ceci dans 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.domain.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)

Plus d'informations sur les politiques de cache ici

1
User511