You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MKMapView高内存占用引发崩溃问题求助

MKMapView内存过高崩溃问题排查与优化方案

核心问题分析

你的代码存在重复遍历数据、视图/资源重复创建、缺少复用缓存等问题,导致内存占用飙升并触发崩溃。以下是针对性的优化措施:


1. 折线渲染优化:避免重复遍历数据

rendererFor overlay方法中,每次渲染折线都遍历整个inspectedLineArray,随着折线数量增加会产生大量无效计算。提前缓存折线ID与样式的映射,直接通过ID获取样式:

// 类中添加缓存字典
private var lineStyleCache: [String: (color: UIColor, width: CGFloat)] = [:]

// 在数据加载完成后初始化缓存(比如searchInspectionList赋值后调用)
func setupLineStyleCache() {
    guard let data = searchInspectionList?.data else { return }
    lineStyleCache.removeAll()
    for inspectionLine in data {
        guard let line = inspectionLine.line, let property = line.properties else { continue }
        let hexColor = String(format: "%@ff", property.stroke ?? "")
        let color = UIColor(hexString: hexColor) ?? .black
        let width = CGFloat(property.strokeWidth ?? 0) * kLineWidth
        lineStyleCache[line.id] = (color, width)
    }
}

// 修改rendererFor方法
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    guard let customPolyline = overlay as? CustomPolyline else {
        return overlay as? MKOverlayRenderer ?? MKOverlayRenderer()
    }
    
    let polylineRenderer = CustomPolylineRenderer(overlay: overlay)
    
    // 直接从缓存取样式,无需遍历数组
    if let style = lineStyleCache[customPolyline.lineid] {
        polylineRenderer.strokeColor = style.color
        polylineRenderer.lineWidth = style.width
    }
    
    return polylineRenderer
}

2. 标注视图优化:最大化复用,减少重复创建

2.1 合并重复的视图初始化代码

CustomAnnotation的聚类/非聚类分支代码完全重复,合并后减少冗余:

case let customAnnotation as CustomAnnotation:
    annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: CustomAnnotationView.ReuseID) as? CustomAnnotationView ?? CustomAnnotationView(annotation: customAnnotation, reuseIdentifiers: CustomAnnotationView.ReuseID)
    annotationView?.clusteringIdentifier = shouldCluster ? kCustomClusteringID : nil
    
    // 后续逻辑保持不变...

2.2 缓存文本生成的图片

Helper.generateImageWithText每次生成新图片,2000个标注会导致大量内存占用。添加缓存:

// 类中添加图片缓存
private let textImageCache = NSCache<NSString, UIImage>()

// 封装缓存逻辑
func generateCachedImage(text: String) -> UIImage {
    if let cachedImage = textImageCache.object(forKey: text as NSString) {
        return cachedImage
    }
    let newImage = Helper.generateImageWithText(text: text)
    textImageCache.setObject(newImage, forKey: text as NSString)
    return newImage
}

// 在nameAnnotation分支使用缓存
annotationView?.image = generateCachedImage(text: nameAnnotation.title ?? "")

2.3 复用标注视图内的文本标签

每次移除所有subviews再新建UILabel会导致视图频繁销毁重建,建议在CustomAnnotationView内部内置UILabel,复用仅更新内容:

// CustomAnnotationView类中添加内置标签
class CustomAnnotationView: MKAnnotationView {
    let textLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 12)
        label.layer.borderColor = UIColor.white.cgColor
        label.layer.borderWidth = 2
        label.backgroundColor = UIColor.customMapRGB()
        return label
    }()
    
    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
        addSubview(textLabel)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// 修改viewFor annotation中的文本标签逻辑
case let customAnnotation as CustomAnnotation:
    // ... 前面的初始化代码 ...
    
    guard let customView = annotationView as? CustomAnnotationView else { break }
    let entityType = EntityType(rawValue: customAnnotation.entityTypes)
    customView.textLabel.isHidden = false
    
    if let name = customAnnotation.title {
        customView.textLabel.text = customAnnotation.haveImage ? "  \(name)*" : "  \(name)"
    } else {
        customView.textLabel.isHidden = true
        return annotationView
    }
    
    // 计算并更新frame
    let size = customView.textLabel.sizeThatFits(CGSize(width: 1000, height: 20))
    switch entityType {
    case .structures, .forestryPin:
        customView.textLabel.frame = CGRect(x: -(size.width)/2 + 20, y: -20, width: size.width, height: 20)
    case .stations:
        customView.textLabel.frame = CGRect(x: -20, y: -20, width: size.width, height: 20)
    default:
        customView.textLabel.isHidden = true
    }

2.4 修复用户位置视图的复用问题

用户位置的reuseIdentifier为空字符串,导致无法复用,改为固定ID:

case let userLocationAnnotation as MKUserLocation:
    let reuseID = "UserLocationAnnotationView"
    annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseID) ?? MKAnnotationView(annotation: userLocationAnnotation, reuseIdentifier: reuseID)
    // 后续逻辑保持不变...

2.5 移除强制解包,避免潜在崩溃

替换annotationView?.image?.size.height!为可选绑定:

if let image = annotationView?.image {
    annotationView?.centerOffset = CGPoint(x: 0, y: -image.size.height / 2)
}

3. 其他优化建议

  • 内存监控:使用Xcode的Memory Graph Debugger定位内存泄漏点,检查CustomAnnotationView、CustomPolylineRenderer是否存在循环引用。
  • 按需加载:根据地图缩放级别,只加载当前可见区域内的标注和折线(利用MKMapView的visibleMapRect过滤数据),进一步减少内存占用。
  • 聚类优化:确保聚类视图的reuseIdentifier正确,避免重复创建聚类视图。

内容的提问来源于stack exchange,提问作者Nikhil Mujral

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 17:33:06