旋转MKMapView时Image MKOverlay崩溃且内存暴涨问题求助
问题:MKMapView图片Overlay旋转时内存暴涨崩溃
尝试在MKMapView中添加地理范围约1km的单张图片作为Overlay,目标是每秒更新一次图片。目前图片能正确绘制,移动、缩放正常,但旋转地图时屏幕锁定,内存以每秒10MB速率持续增长,直至耗尽内存退出,模拟器和真机均出现此问题,且期间无自定义代码调用、无输出。改为UIKit原生地图应用可正常旋转,最简测试代码同样崩溃。
测试代码
class ImageOverlay : NSObject, MKOverlay { let image:UIImage let boundingMapRect: MKMapRect let coordinate:CLLocationCoordinate2D init(image: UIImage, rect: MKMapRect) { self.image = image self.boundingMapRect = rect var coord = rect.origin coord.x += rect.size.width / 2 coord.y += rect.size.height / 2; self.coordinate = CLLocationCoordinate2D(latitude: coord.x, longitude: coord.y) } } class ImageOverlayRenderer : MKOverlayRenderer { override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) { guard let overlay = self.overlay as? ImageOverlay else { return } let rect = self.rect(for: overlay.boundingMapRect) context.saveGState() context.setAlpha(0.6) context.draw(overlay.image.cgImage!, in: rect, byTiling: false) context.flush() context.restoreGState() } } struct MapView: UIViewRepresentable { @Binding var imgOver: ImageOverlay? let mapViewDelegate = MapViewDelegate() func makeUIView(context: Context) -> MKMapView { MKMapView(frame:.zero) } func updateUIView(_ view: MKMapView, context: Context) { view.delegate = mapViewDelegate view.translatesAutoresizingMaskIntoConstraints = false view.mapType = .hybrid addImage(to: view) } } extension MapView { func addImage(to view: MKMapView) { if !view.overlays.isEmpty { view.removeOverlays(view.overlays) } guard let img = imgOver else { return } let mapRect = img.boundingMapRect view.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100), animated: true) view.addOverlay(img) } } class MapViewDelegate: NSObject, MKMapViewDelegate { func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { ImageOverlayRenderer(overlay: overlay) } } struct ContentView: View { @State var imageOverlay: ImageOverlay? var body: some View { MapView(imgOver:$imageOverlay) .onAppear { let image = UIImage(named: "portland.png")! let p1 = MKMapPoint( CLLocationCoordinate2DMake(50.564479946138384, -2.4423953407134054)) let p2 = MKMapPoint( CLLocationCoordinate2DMake(50.57264968724567, -2.415437272738347)) let bounds = MKMapRect(x: fmin(p1.x,p2.x), y: fmin(p1.y,p2.y), width: fabs(p1.x-p2.x), height: fabs(p1.y-p2.y)); self.showImage(image:image, bounds:bounds ) } } } private extension ContentView { func showImage(image:UIImage?, bounds:MKMapRect) { if image == nil { return } self.imageOverlay = ImageOverlay(image:image!, rect:bounds) } }
问题根源分析
- 坐标计算错误:直接将MKMapPoint的x/y值作为经纬度,属于概念混淆,会导致Overlay的坐标逻辑异常,间接引发绘制时的额外计算开销。
- Renderer绘制逻辑缺陷:每次
draw调用都直接访问overlay.image.cgImage!,旋转地图时MapKit会高频触发draw,而UIImage.cgImage每次访问都会生成新的CGImage实例,且未被正确释放,导致内存持续暴涨。 - SwiftUI生命周期滥用:
updateUIView会在视图状态变化(包括旋转)时频繁调用,每次都重复设置代理、移除并添加Overlay,加剧内存压力和不必要的对象创建。
修复方案
1. 修正ImageOverlay的坐标计算
将MKMapRect中心的投影坐标正确转换为经纬度:
class ImageOverlay : NSObject, MKOverlay { let image: UIImage let boundingMapRect: MKMapRect let coordinate: CLLocationCoordinate2D init(image: UIImage, rect: MKMapRect) { self.image = image self.boundingMapRect = rect // 正确转换投影坐标为经纬度 let centerPoint = MKMapPoint(x: rect.midX, y: rect.midY) self.coordinate = centerPoint.coordinate super.init() } }
2. 优化Renderer的CGImage缓存
提前缓存CGImage,避免每次绘制都生成新实例:
class ImageOverlayRenderer : MKOverlayRenderer { private let cachedCGImage: CGImage init(overlay: ImageOverlay) { self.cachedCGImage = overlay.image.cgImage! super.init(overlay: overlay) } override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) { let rect = self.rect(for: overlay.boundingMapRect) context.saveGState() context.setAlpha(0.6) context.draw(cachedCGImage, in: rect, byTiling: false) context.restoreGState() // 移除不必要的context.flush(),MapKit会自行管理上下文刷新 } }
3. 规范UIViewRepresentable的生命周期
使用Coordinator管理代理,仅在Overlay变化时更新:
struct MapView: UIViewRepresentable { @Binding var imgOver: ImageOverlay? // 用Coordinator托管代理,避免重复创建 class Coordinator: NSObject, MKMapViewDelegate { func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { guard let imageOverlay = overlay as? ImageOverlay else { return MKOverlayRenderer(overlay: overlay) } return ImageOverlayRenderer(overlay: imageOverlay) } } func makeCoordinator() -> Coordinator { Coordinator() } func makeUIView(context: Context) -> MKMapView { let mapView = MKMapView(frame: .zero) mapView.delegate = context.coordinator mapView.translatesAutoresizingMaskIntoConstraints = false mapView.mapType = .hybrid return mapView } func updateUIView(_ view: MKMapView, context: Context) { // 仅当Overlay实例变化时才更新 if let newOverlay = imgOver { if view.overlays.first !== newOverlay { view.removeOverlays(view.overlays) view.addOverlay(newOverlay) let mapRect = newOverlay.boundingMapRect view.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsets(top: 100, left: 100, bottom: 100, right: 100), animated: true) } } else { view.removeOverlays(view.overlays) } } }
内存泄漏调试方法
- Memory Graph Debugger:点击Xcode调试栏的内存图标,查看对象引用关系,定位未释放的CGImage、Overlay或Renderer实例,找到泄漏点。
- Instruments Allocations模板:运行Allocations工具,旋转地图后查看内存增长的对象类型(如CGImage数量持续增加),通过调用栈追踪创建来源。
- 日志与断点:在
draw、addImage方法中添加打印日志,观察旋转时的调用频率,判断是否存在重复执行逻辑。 - 对比UIKit测试:将SwiftUI封装的MapView替换为纯UIKit视图控制器测试,快速区分是SwiftUI生命周期问题还是MapKit代码问题。
内容的提问来源于stack exchange,提问作者Woody
相关产品推荐
相关产品推荐

