19

我的应用程序使用Geojson文件。我使用MapBox SDK添加MGLPolyline到地图。但问题是我的文件太大,以至于应用程序崩溃并得到错误:Message from debugger: Terminated due to memory issue. 我在第一个循环中遇到了66234 个对象。我试图将数组分块为新数组,但没有成功。请帮我解决问题。这是我在地图上绘制的代码,这是我在 github 上的测试项目,使用 Xcode 8.1 如果有任何不同的 3rd 方可以解决我的问题,也欢迎

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")
        }
    }
}

编辑:: @Alessandro Ornano 和@fragilecat 非常感谢。但这些解决方案仍然无法解决 iPad 上应用程序的终止问题。我认为很难更改当前代码以使其正常工作,因为数据如此之大。我想我需要另一种适用于大数据的解决方案。就像将数组分块成小数组然后按队列加载它们一样。但我不知道如何开始:(

我向 MapBox 的支持团队发送了一封电子邮件,征求建议。

4

7 回答 7

17

我从创建内存密集型应用程序中学到的一件事是autoreleasepool,如果这些循环很长,那么每次在循环内创建变量时都必须使用

查看您的所有代码并转换诸如

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

进入

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

查看各种循环forwhile等。

这将强制 iOS 在循环的每一轮结束时释放变量及其对应的内存使用量,而不是在函数结束之前保留变量及其内存使用量。这将大大减少您的内存使用量。

于 2016-11-11T19:08:06.877 回答
16

这里的问题与有效的内存管理有关。您正在通过 json 文件加载大量数据。您意识到您需要在后台队列(线程)上完成大部分工作,但问题是您如何通过DispatchQueue.main.async函数更新 UI。在当前版本的drawPolyline()方法中,考虑到第一个循环中的对象数量,您在后台队列和主队列之间切换 66234 次。您还创建了相同数量的CLLocationCoordinate2D数组。

这会导致巨大的内存占用。您没有提及有关如何渲染线条的任何要求。因此,如果我们重构您的drawPolyline()方法以使用CLLocationCoordinate2D数组的实例变量,那么我们只使用一个,然后在更新 UI 之前处理所有 json 文件。内存使用量下降到更易于管理的 664.6 MB。

在此处输入图像描述

当然,渲染可能并不完全符合您的要求,如果是这种情况,您可能希望将CLLocationCoordinate2D数组重组为更合适的数据结构。

以下是您的ViewController课程,改写drawPolyline()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
    }
}


}

在此处输入图像描述

于 2016-11-09T14:36:18.363 回答
3

第一个解决方案

也许您的 for 循环正在无限运行,并且每次都将内存分配给具有 nil 值的数组。它正在使用大量内存,因此会出现此错误。

请通过在 for 循环中打印一些内容来检查。

第二种解决方案

将其添加到didReceiveMemoryWarning

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

您还可以更改缓存策略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)

更多关于缓存策略的信息在这里

于 2016-11-08T12:48:42.297 回答
3

我在使用 pod 测试您的项目时遇到了一些问题,因此我直接从此处删除了 pod 并直接使用 Mapbox 框架。

我在模拟器和真正的 iPad(我的 iPad 4 gen.)中第一次启动都没有问题,但过了一段时间我遇到了同样的错误,所以我用以下代码更正了这段代码:

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

因为unowned这不足以防止保留循环。现在看来它运作良好。

希望能帮助到你。

PS (我使用了最新可用的 Mapbox v3.3.6)


更新(评论后):

因此,首先我使用作为“嵌入式框架”插入的 Mapbox 框架进行所有测试。

我对您的 github 项目进行了一些更正,只是ViewController.swift为了避免保留周期。 附言。我删除了注释行以方便阅读:

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)
                        }
                    }
                }
            }
        }
    }
于 2016-11-10T10:22:22.423 回答
3

将分享我对这个奇怪问题的经验。

对我来说,该应用程序因“来自调试器的消息:因内存问题而终止”而崩溃,而仪器并没有太大帮助。以及内存 - 在绿色范围内。所以我不确定是什么原因造成的。并且无法调试和单个设备特定的问题。

刚刚重新启动 iPhone 6 - 问题现在消失了。

于 2019-06-11T09:22:27.280 回答
1

让你在标注上填充这意味着只有在点击 pin func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) 时才执行 polyne

于 2017-04-02T01:36:30.813 回答
0

我收到此错误并且非常困惑,因为我的应用程序的内存使用量很小。

最后,我发现这是由于我加载了许多文件作为映射内存,例如:

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

我不知道为什么我会遇到这些奇怪的崩溃,但通常只是加载数据可以防止崩溃发生。

于 2021-06-24T16:48:59.070 回答