来自调试器的消息:由于内存问题而终止

Posted

技术标签:

【中文标题】来自调试器的消息:由于内存问题而终止【英文标题】:Message from debugger: Terminated due to memory issue 【发布时间】:2017-03-16 21:00:12 【问题描述】:

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

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

            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 的支持团队发送了一封电子邮件,征求建议。

【问题讨论】:

这是基于意见的,但我建议在这种情况下考虑guard statements,以避免"pyramid of doom",从而提高可读性。 请查看更新后的代码,删除末日金字塔 我无法让您的项目运行,我无法安装可可豆荚。无法找到Mapbox-ios-SDK (= 3.3) 的规范是我收到的消息。我想问一下您是否使用仪器运行此程序来分析您的内存问题发生在哪里。 @fragilecat:你需要MacOs 10.12,Xcode 8,并在运行cocoapods之前安装pod install @lee pods 和 Mapbox 似乎有一些问题.. 【参考方案1】:

我从创建内存密集型应用程序中学到的一件事是,如果这些循环很长,那么每次在循环内创建变量时都必须使用 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 在循环的每一轮结束时释放变量及其对应的内存使用量,而不是在函数结束之前保留变量及其内存使用量。这将大大减少您的内存使用量。

【讨论】:

它确实大大减少了内存问题,但是如何使用该图像然后对其进行其他操作呢? 不是每次都分配一个图像,而是使用相同的图像。【参考方案2】:

这里的问题与有效的内存管理有关。您正在通过 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
    




【讨论】:

您的代码没有崩溃。但是您将self.mapboxView.addAnnotation(line) 移出状态:if geometry["type"] as? String == "LineString" 。以至于地图上的数据显示是错误的。使用您的解决方案,我尝试将self.mapboxView.addAnnotation(line) 移入该条件,但它仍然崩溃:( 您必须将主队列和后台队列之间的切换保持在最低限度。除了一组点之外,我不知道您要绘制什么,正如我上面提到的,它可能无法获得您想要的绘图,如果是这种情况,那么您需要创建一个比平面更详细的数据源大批。关键是您处理数据,然后将其渲染到地图上。不知道你的画是什么,我不能给你更多的细节。请提供您尝试执行的操作的屏幕截图。目前,当我运行您的项目时,我会得到一张波特兰或美国的地图。 如果我将文件大小减小到小于 12 倍,我的代码可以加载 okie:for i in 0..&lt;features.count/12。请从链接查看图片:http://www.filetolink.com/f03e1fbff4。但是如果不减少它,应用程序就会崩溃。 您的代码可以修复崩溃,但地图上的数据显示错误。请比较您在地图中的解决方案,与我在上面链接中的图片。那么请给我详细的解决方案。非常感谢。 代码可以打印出** START **** FINISH **,但应用程序卡在这里。无法放大缩小或移动地图。【参考方案3】:

我在使用 pod 测试您的项目时遇到了一些问题,因此我直接从 here 中删除了 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 不足以阻止保留循环。 现在看来效果不错。

希望对您有所帮助。

P.S. (我使用了最新的 Mapbox v3.3.6)


更新(在 cmets 之后):

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

我已对您的 github 项目进行了一些更正,仅针对 ViewController.swift 以避免保留周期。 附注。为了便于阅读,我删除了 cmets 行:

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> elsereturn
                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)
                        
                    
                
            
        
    

【讨论】:

您推出了仪器吗?我现在看不到泄漏。尝试删除该应用程序并重新安装,同时重新启动 iPad,并确保使用最新的 Mapbox 版本。告诉我。 我也用过 Mapbox v3.3.6。尝试重启 iPad,也删除并安装新应用,但应用仍然运行这么久然后终止。 再次运行应用程序,然后应用程序卡在终止状态。似乎主线程永远卡住了。 等了很久才收到消息:Message from debugger: Terminated due to memory issue 我想帮助你,但我没有收到这条信息,啊!太奇怪了..我没有 iPad Pro,所以我现在尝试了模拟器和我的 iPad 4,但没有。 (PS iOS 10.1.1)【参考方案4】:

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

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

刚刚重启了 iPhone 6 - 问题暂时消失了。

【讨论】:

我在尝试使用金属材质对 SceneKit 场景进行 GPU 捕获时遇到了这个问题。关闭所有后台应用程序会使消息消失。【参考方案5】:

第一个解决方案

也许您的 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)

更多关于缓存策略的信息here。

【讨论】:

不知道你是否运行了我的测试项目。但这并不能解决问题。【参考方案6】:

让你在标注上添加东西,这意味着只有在点击图钉时才执行 polyne func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)

【讨论】:

【参考方案7】:

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

最后我发现是因为我加载了一些文件作为映射内存,例如:

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

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

【讨论】:

以上是关于来自调试器的消息:由于内存问题而终止的主要内容,如果未能解决你的问题,请参考以下文章

由于内存问题而终止的 iOS 应用程序

xcode 使用 iOS 10 设备构建项目,但启动时崩溃:来自调试器的消息:因内存问题而终止

来自调试器的消息:在 UITableView/UICollectionView 中使用 gif 图像 Kingfisher 库滚动时因内存问题而终止

将更改的图像保存到相机胶卷时因内存问题而终止

退出应用程序会导致错误“来自调试器的消息:由于信号 9 而终止”

来自调试器的消息:由于信号 9 而终止 - 有没有办法运行后台操作?