私のアプリはGeojson
ファイルを使用しています。 MapBox SDK を使用して、MGLPolyline
をマップに追加します。しかし、問題はファイルが大きすぎるため、アプリがクラッシュしてエラーが発生することです:Message from debugger: Terminated due to memory issue
。最初のループで66234オブジェクトに直面しました。配列を新しい配列にチャンクしようとしましたが、成功しませんでした。問題を解決するのを手伝ってください。これが地図上に描画するための私のコードであり、ここに私の githubのテストプロジェクトは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> 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と@fragilecat本当にありがとう。ただし、これらのソリューションでは、iPadでのアプリの終了を解決できません。データが非常に大きいため、現在のコードを変更して適切に動作させるのは難しいと思います。ビッグデータで機能する別のソリューションが必要になると思います。配列を小さな配列にチャンクしてからキューでロードするようなものです。しかし、私は開始する方法がわかりません:(
MapBoxのサポートチームにメールを送り、提案を求めます。
ここでの問題は、効果的なメモリ管理に関連しています。 JSONファイルを介して大量のデータをロードしています。バックグラウンドキュー(スレッド)で大部分の作業を行う必要があることに気付きましたが、問題は_DispatchQueue.main.async
_関数を介してUIを更新する方法です。 drawPolyline()
メソッドの現在のバージョンでは、最初のループ内のオブジェクトの数を考えると、バックグラウンドキューとメインキューを66234回切り替えています。また、同じ数の_CLLocationCoordinate2D
_配列を作成していました。
これにより、メモリフットプリントが膨大になります。線のレンダリング方法に関する要件については言及していません。したがって、drawPolyline()
メソッドを再構築して_CLLocationCoordinate2D
_配列のインスタンス変数を使用し、1つだけを使用してから、UIを更新する前にすべてのjsonファイルを処理します。メモリ使用量は、管理しやすい664.6 MBにまで低下しました。
もちろん、レンダリングは正確にはあなたが探しているものではないかもしれません。その場合は_CLLocationCoordinate2D
_配列をより適切なデータ構造に再構築したいかもしれません。
以下は、drawPolyline()
としてdrawPolyline2()
を書き換えたViewController
クラスです。
_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
}
}
}
_
メモリを集中的に使用するアプリの作成から学んだことの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)
}
}
}
あらゆる種類のループfor
、while
などを確認します。
これにより、変数とそのメモリ使用量を関数が終了するまで保持する代わりに、ループの各ターンの終わりにiOSが変数とそれに対応するメモリ使用量を強制的に解放します。これにより、メモリ使用量が大幅に削減されます。
ポッドを使用してプロジェクトをテストするためにいくつかの問題があったため、ポッドを直接削除し、Mapboxフレームワークを here から直接使用しました。
シミュレーターと実際のiPad(私のiPad 4世代)の両方で最初の起動に問題はありませんが、しばらくすると同じエラーが発生するので、このコードを修正しました:
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を使用しました)
更新(コメント後):
したがって、まず、すべてのテストを「埋め込みフレームワーク」として挿入されたMapboxフレームワークで行います。
Githubプロジェクトにいくつかの修正を加えたのは、ViewController.Swift
保持サイクルを回避します。 P.S。読みやすくするためにコメント行を削除します。
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)
}
}
}
}
}
}
これは、コールアウトであなたのものにする、これはピンfunc mapView(_ mapView:MKMapView、didSelect view:MKAnnotationView)をクリックしたときにのみポリーンを実行することを意味します
最初の解決策
たぶんあなたの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)
キャッシュポリシーの詳細情報 こちら 。
この奇妙な問題で私の経験を共有します。
私にとって、アプリは「デバッガーからのメッセージ:メモリの問題により終了しました」でクラッシュし、機器はあまり役に立ちませんでした。メモリも-緑の制限内でした。だから私はそれが何を引き起こしているのか分かりませんでした。そして、デバッグすることは不可能であり、単一デバイス固有の問題でした。
ちょうどiPhone 6を再起動-問題は今のところ消えました。