Skip to content

Commit d9b2416

Browse files
committed
refactor(ios): replace DispatchQueue with Swift concurrency and release SVGKit memory
1 parent 1c67f3c commit d9b2416

File tree

6 files changed

+74
-91
lines changed

6 files changed

+74
-91
lines changed

ios/GoogleMapViewImpl.swift

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ GMSIndoorDisplayDelegate {
355355
) -> NitroModules.Promise<String?> {
356356
let promise = Promise<String?>()
357357

358-
DispatchQueue.main.async {
358+
onMainAsync {
359359
guard let mapView = self.mapView else {
360360
promise.resolve(withResult: nil)
361361
return
@@ -847,12 +847,3 @@ GMSIndoorDisplayDelegate {
847847
}
848848
}
849849
}
850-
851-
@inline(__always)
852-
func onMain(_ block: @escaping () -> Void) {
853-
if Thread.isMainThread {
854-
block()
855-
} else {
856-
DispatchQueue.main.async { block() }
857-
}
858-
}

ios/LocationHandler.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ final class LocationHandler: NSObject, CLLocationManagerDelegate {
3434
}
3535

3636
func showLocationDialog() {
37-
DispatchQueue.main.async {
37+
onMainAsync { [weak self] in
3838
guard let vc = Self.topMostViewController() else { return }
3939
let title =
4040
Bundle.main.object(forInfoDictionaryKey: "LocationNotAvailableTitle")
@@ -61,7 +61,7 @@ final class LocationHandler: NSObject, CLLocationManagerDelegate {
6161
title: openLocationSettingsButton ?? "Open settings",
6262
style: .default
6363
) { _ in
64-
self.openLocationSettings()
64+
self?.openLocationSettings()
6565
}
6666
)
6767
vc.present(alert, animated: true, completion: nil)
@@ -78,7 +78,7 @@ final class LocationHandler: NSObject, CLLocationManagerDelegate {
7878
}
7979

8080
func openLocationSettings() {
81-
DispatchQueue.main.async {
81+
onMainAsync {
8282
let openSettings = {
8383
if #available(iOS 18.3, *) {
8484
guard

ios/MapHelper.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,23 @@ func withCATransaction(
1818
body()
1919
CATransaction.commit()
2020
}
21+
22+
@MainActor @inline(__always)
23+
func onMain(_ block: @escaping @MainActor () -> Void) {
24+
if Thread.isMainThread {
25+
block()
26+
} else {
27+
Task { @MainActor in block() }
28+
}
29+
}
30+
31+
@inline(__always)
32+
func onMainAsync(
33+
_ block: @MainActor @escaping () async -> Void
34+
) {
35+
if Thread.isMainThread {
36+
Task { @MainActor in await block() }
37+
} else {
38+
Task { @MainActor in await block() }
39+
}
40+
}

ios/MapMarkerBuilder.swift

Lines changed: 42 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import SVGKit
33
import UIKit
44

55
final class MapMarkerBuilder {
6-
private let iconCache = NSCache<NSString, UIImage>()
6+
private let iconCache: NSCache<NSNumber, UIImage> = {
7+
let c = NSCache<NSNumber, UIImage>()
8+
c.countLimit = 512
9+
return c
10+
}()
711
private var tasks: [String: Task<Void, Never>] = [:]
8-
private let queue = DispatchQueue(
9-
label: "map.marker.render",
10-
qos: .userInitiated,
11-
attributes: .concurrent
12-
)
1312

1413
func build(_ m: RNMarker, icon: UIImage?) -> GMSMarker {
1514
let marker = GMSMarker(
@@ -35,7 +34,8 @@ final class MapMarkerBuilder {
3534
}
3635
m.zIndex.map { marker.zIndex = Int32($0) }
3736

38-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak marker] in
37+
onMainAsync { [weak marker] in
38+
try? await Task.sleep(nanoseconds: 250_000_000)
3939
marker?.tracksViewChanges = false
4040
}
4141

@@ -99,7 +99,8 @@ final class MapMarkerBuilder {
9999
)
100100
}
101101

102-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak m] in
102+
onMainAsync { [weak m] in
103+
try? await Task.sleep(nanoseconds: 250_000_000)
103104
m?.tracksViewChanges = false
104105
}
105106
}
@@ -144,7 +145,8 @@ final class MapMarkerBuilder {
144145
guard let self else { return }
145146
defer { self.tasks.removeValue(forKey: id) }
146147

147-
let img = await self.renderUIImage(m)
148+
let scale = UIScreen.main.scale
149+
let img = await self.renderUIImage(m, scale)
148150
guard let img, !Task.isCancelled else { return }
149151

150152
self.iconCache.setObject(img, forKey: key)
@@ -172,65 +174,38 @@ final class MapMarkerBuilder {
172174
iconCache.removeAllObjects()
173175
}
174176

175-
private func renderUIImage(_ m: RNMarker) async -> UIImage? {
176-
guard let iconSvg = m.iconSvg else {
177-
return nil
178-
}
179-
180-
return await withTaskCancellationHandler(
181-
operation: {
182-
await withCheckedContinuation {
183-
(cont: CheckedContinuation<UIImage?, Never>) in
184-
queue.async {
185-
if Task.isCancelled {
186-
cont.resume(returning: nil)
187-
return
188-
}
189-
190-
let targetW = max(1, Int(CGFloat(iconSvg.width)))
191-
let targetH = max(1, Int(CGFloat(iconSvg.height)))
192-
let size = CGSize(width: targetW, height: targetH)
193-
194-
guard
195-
let data = iconSvg.svgString.data(using: .utf8),
196-
let svgImg = SVGKImage(data: data)
197-
else {
198-
cont.resume(returning: nil)
199-
return
200-
}
201-
202-
svgImg.size = size
203-
204-
if Task.isCancelled {
205-
cont.resume(returning: nil)
206-
return
207-
}
208-
209-
guard let base = svgImg.uiImage else {
210-
cont.resume(returning: nil)
211-
return
212-
}
213-
214-
let scale = UIScreen.main.scale
215-
let img: UIImage
216-
if let cg = base.cgImage {
217-
img = UIImage(cgImage: cg, scale: scale, orientation: .up)
218-
} else {
219-
let fmt = UIGraphicsImageRendererFormat.default()
220-
fmt.opaque = false
221-
fmt.scale = scale
222-
let renderer = UIGraphicsImageRenderer(size: size, format: fmt)
223-
img = renderer.image { _ in
224-
base.draw(in: CGRect(origin: .zero, size: size))
225-
}
226-
}
227-
228-
cont.resume(returning: img)
229-
}
230-
}
177+
private func renderUIImage(_ m: RNMarker, _ scale: CGFloat) async -> UIImage? {
178+
guard let iconSvg = m.iconSvg,
179+
let data = iconSvg.svgString.data(using: .utf8)
180+
else { return nil }
231181

232-
},
233-
onCancel: {}
182+
let size = CGSize(
183+
width: max(1, CGFloat(iconSvg.width)),
184+
height: max(1, CGFloat(iconSvg.height))
234185
)
186+
187+
return await Task.detached(priority: .userInitiated) {
188+
autoreleasepool {
189+
guard let svgImg = SVGKImage(data: data) else { return nil }
190+
svgImg.size = size
191+
192+
guard !Task.isCancelled else { return nil }
193+
guard let base = svgImg.uiImage else { return nil }
194+
195+
if let cg = base.cgImage {
196+
return UIImage(cgImage: cg, scale: scale, orientation: .up)
197+
}
198+
guard !Task.isCancelled else { return nil }
199+
let fmt = UIGraphicsImageRendererFormat.default()
200+
fmt.opaque = false
201+
fmt.scale = scale
202+
guard !Task.isCancelled else { return nil }
203+
let renderer = UIGraphicsImageRenderer(size: size, format: fmt)
204+
return renderer.image { _ in
205+
base.draw(in: CGRect(origin: .zero, size: size))
206+
}
207+
}
208+
}.value
235209
}
210+
236211
}

ios/RNGoogleMapsPlusView.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,13 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
3434
func afterUpdate() {
3535
if !propsInitialized {
3636
propsInitialized = true
37-
Task { @MainActor in
38-
let options = GMSMapViewOptions()
39-
initialProps?.mapId.map { options.mapID = GMSMapID(identifier: $0) }
40-
initialProps?.liteMode.map { _ in /* not supported */ }
41-
initialProps?.camera.map {
42-
options.camera = $0.toGMSCameraPosition(current: nil)
43-
}
44-
impl.initMapView(googleMapOptions: options)
37+
let options = GMSMapViewOptions()
38+
initialProps?.mapId.map { options.mapID = GMSMapID(identifier: $0) }
39+
initialProps?.liteMode.map { _ in /* not supported */ }
40+
initialProps?.camera.map {
41+
options.camera = $0.toGMSCameraPosition(current: nil)
4542
}
43+
impl.initMapView(googleMapOptions: options)
4644
}
4745
}
4846

ios/extensions/RNMarker+Extension.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ extension RNMarker {
1717
func markerStyleEquals(_ b: RNMarker) -> Bool {
1818
iconSvg?.width == b.iconSvg?.width && iconSvg?.height == b.iconSvg?.height
1919
&& iconSvg?.svgString == b.iconSvg?.svgString
20-
2120
}
2221

23-
func styleHash() -> NSString {
22+
func styleHash() -> NSNumber {
2423
var hasher = Hasher()
2524
hasher.combine(iconSvg?.width)
2625
hasher.combine(iconSvg?.height)
2726
hasher.combine(iconSvg?.svgString)
28-
return String(hasher.finalize()) as NSString
27+
return NSNumber(value: hasher.finalize())
2928
}
3029
}

0 commit comments

Comments
 (0)