Skip to content

Commit

Permalink
Replace legacy image downloader with new actor-based implementation (#…
Browse files Browse the repository at this point in the history
…4588)

* Replace legacy image downloader with new actor-based implementation

* Remove failed tasks from inflightRequests array

* add changelog entry

* small refactoring
  • Loading branch information
azarovalex authored Jan 24, 2024
1 parent ce064ed commit c976919
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 532 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

* Added PrivacyInfo.xcprivacy support. ([#4573](https://github.com/mapbox/mapbox-navigation-ios/pull/4573))
* Removed `NavigationEventsManager.init(activeNavigationDataSource:passiveNavigationDataSource:accessToken:mobileEventsManager:)` in favor of `NavigationEventsManager.init(activeNavigationDataSource:passiveNavigationDataSource:accessToken:)`. ([#4572](https://github.com/mapbox/mapbox-navigation-ios/pull/4572))
* Fixed a rare issue that could lead to memory corruption under specific conditions. This was resolved by replacing the internal image downloader with brand new actor-based implementation. ([#4588](https://github.com/mapbox/mapbox-navigation-ios/pull/4588))

## v2.17.0

Expand Down
192 changes: 0 additions & 192 deletions Sources/MapboxNavigation/ImageDownload.swift

This file was deleted.

119 changes: 0 additions & 119 deletions Sources/MapboxNavigation/ImageDownloader.swift

This file was deleted.

47 changes: 47 additions & 0 deletions Sources/MapboxNavigation/ImageDownloader/ImageDownloader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Foundation

enum DownloadError: Error {
case serverError
case clientError
case noImageData
}

@available(iOS 13.0, *)
actor ImageDownloader: ImageDownloaderProtocol {
private let urlSession: URLSession

private var inflightRequests: [URL: Task<CachedURLResponse, Error>] = [:]

init(configuration: URLSessionConfiguration? = nil) {
let defaultConfiguration = URLSessionConfiguration.default
defaultConfiguration.urlCache = URLCache(memoryCapacity: 5 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024)
self.urlSession = URLSession(configuration: configuration ?? defaultConfiguration)
}

nonisolated func download(with url: URL, completion: @escaping (Result<CachedURLResponse, Error>) -> Void) {
Task {
do {
let response = try await self.fetch(url)
completion(.success(response))
} catch {
completion(.failure(error))
}
}
}

private func fetch(_ url: URL) async throws -> CachedURLResponse {
if let inflightTask = inflightRequests[url] {
return try await inflightTask.value
}

let downloadTask = Task<CachedURLResponse, Error> {
let (data, response) = try await urlSession.data(from: url)
return CachedURLResponse(response: response, data: data)
}

inflightRequests[url] = downloadTask
defer { inflightRequests[url] = nil }

return try await downloadTask.value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

protocol ImageDownloaderProtocol {
func download(with url: URL, completion: @escaping (Result<CachedURLResponse, Error>) -> Void)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

@available(iOS, deprecated: 13.0, message: "Use ImageDownloader instead.")
final class LegacyImageDownloader: ImageDownloaderProtocol {
private let urlSession: URLSession

init(configuration: URLSessionConfiguration? = nil) {
let defaultConfiguration = URLSessionConfiguration.default
defaultConfiguration.urlCache = URLCache(memoryCapacity: 5 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024, diskPath: nil)
self.urlSession = URLSession(configuration: configuration ?? defaultConfiguration)
}

func download(with url: URL, completion: @escaping (Result<CachedURLResponse, Error>) -> Void) {
urlSession.dataTask(with: URLRequest(url)) { data, response, error in
if let response, let data {
completion(.success(CachedURLResponse(response: response, data: data)))
} else if let error {
completion(.failure(error))
}
}.resume()
}
}
Loading

0 comments on commit c976919

Please sign in to comment.