From 2d056265bd73267999568012f5610d46887635f5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 14:42:27 +0000 Subject: [PATCH] perf: Optimize map annotation lookup from O(N) to O(1) Replace linear filter search over MKMapView.annotations with a dictionary lookup, mapping annotation ID to FlutterAnnotation objects. This significantly speeds up remove, hide, select and update operations, especially for maps with numerous annotations. Co-authored-by: esenmx <43244505+esenmx@users.noreply.github.com> --- benchmark.swift | 38 +++++++++++++++++++ .../Annotations/AnnotationController.swift | 7 ++-- .../MapView/MapKitViewHost.swift | 1 + 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 benchmark.swift diff --git a/benchmark.swift b/benchmark.swift new file mode 100644 index 0000000..e2d4a76 --- /dev/null +++ b/benchmark.swift @@ -0,0 +1,38 @@ +import Foundation + +struct FlutterAnnotation { + var id: String +} + +var annotations: [FlutterAnnotation] = [] +for i in 0..<10000 { + annotations.append(FlutterAnnotation(id: "id_\(i)")) +} + +// Ensure the random seed is reproducible for a fair comparison, though time based is ok +let startArray = Date() +var count = 0 +for i in 0..<1000 { + let id = "id_\(i * 10)" + let found = annotations.filter { $0.id == id }.first + if found != nil { count += 1 } +} +let endArray = Date() + +print("Array filter O(N) time: \(endArray.timeIntervalSince(startArray)) seconds for 1000 lookups in 10000 items") + +var annotationsMap: [String: FlutterAnnotation] = [:] +for a in annotations { + annotationsMap[a.id] = a +} + +let startDict = Date() +count = 0 +for i in 0..<1000 { + let id = "id_\(i * 10)" + let found = annotationsMap[id] + if found != nil { count += 1 } +} +let endDict = Date() + +print("Dict lookup O(1) time: \(endDict.timeIntervalSince(startDict)) seconds for 1000 lookups in 10000 items") diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/Annotations/AnnotationController.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/Annotations/AnnotationController.swift index b5dab0d..d35d7c7 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/Annotations/AnnotationController.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/Annotations/AnnotationController.swift @@ -91,9 +91,8 @@ extension MapKitViewHost { } func annotationsToChange(_ annotations: [PlatformAnnotation]) { - let oldAnnotations: [MKAnnotation] = self.mapView.annotations for annotationData in annotations { - if let annotationToChange = oldAnnotations.first(where: { ($0 as? FlutterAnnotation)?.id == annotationData.id }) as? FlutterAnnotation { + if let annotationToChange = self.getAnnotation(with: annotationData.id) { let newAnnotation = FlutterAnnotation(fromPlatform: annotationData) if annotationToChange != newAnnotation { if !annotationToChange.wasDragged { @@ -137,6 +136,7 @@ extension MapKitViewHost { private func removeAnnotation(id: String) { if let flutterAnnotation: FlutterAnnotation = self.getAnnotation(with: id) { + self.annotationsById.removeValue(forKey: id) self.mapView.removeAnnotation(flutterAnnotation) } } @@ -178,7 +178,7 @@ extension MapKitViewHost { #endif private func getAnnotation(with id: String) -> FlutterAnnotation? { - return self.mapView.annotations.filter { annotation in return (annotation as? FlutterAnnotation)?.id == id }.first as? FlutterAnnotation + return self.annotationsById[id] } private func annotationExists(with id: String) -> Bool { @@ -193,6 +193,7 @@ extension MapKitViewHost { if self.annotationExists(with: annotation.id) { self.removeAnnotation(id: annotation.id) } + self.annotationsById[annotation.id] = annotation self.mapView.addAnnotation(annotation) } diff --git a/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/MapKitViewHost.swift b/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/MapKitViewHost.swift index b3041a2..40c32dd 100644 --- a/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/MapKitViewHost.swift +++ b/darwin/mapkit_flutter/Sources/mapkit_flutter/MapView/MapKitViewHost.swift @@ -25,6 +25,7 @@ public class MapKitViewHost: NSObject, @preconcurrency MapKitHostApi { var flutterApi: MapKitFlutterApi var currentlySelectedAnnotation: String? var tileOverlays: [String: FlutterTileOverlay] = [:] + var annotationsById: [String: FlutterAnnotation] = [:] public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64) { let suffix = "\(id)"