-
-
Notifications
You must be signed in to change notification settings - Fork 534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DataPublisher created from async function should start lazily #693
Comments
Nice catch; thanks for the report. It was never intended to work this way. The async work should be performed lazily. The implementation-wise, it should probably not perform the conversion to a publisher, but it's also OK if it does. There are ways to make it lazy. I'll address it when I get some time, but in the meantime, PRs are always welcome. |
Sorry for delayed reply, I wanted to test it in production to make sure it works, and it doesn't work that good after all. About 0.5% of users who received this update got crashes like this:
My implementation of I agree that it should probably not be converted to a publisher internally and better just call the async method when needed. |
I'm also having a similar situation, where I'm loading an image from data stored on a CoreData object I'd like to load / decompress / cache (in memory) the image via the same Nuke pipeline I use for loading remote images, however none of the local images load :( extension MyObject {
var image: UIImage? {
get async {
let request = ImageRequest(id: albumIdentifier, data: {
return imageData
})
do {
return try await ImagePipeline.shared.image(for: request)
} catch {
debugPrint("Failed to load image: \(error)")
return nil
}
}
}
} Is this sort of use-case unsupported? Or do you have a suggested workaround (other than the publisher one above?)? (Thanks for making a pretty slick library btw!) |
#693: DataPublisher created from async function should start lazily
Fixed in version 12.1.1 |
Ah, indeed, I'm not sure using Let me share my custom lazy publisher that solved the crashes for me, just in case. private final class LazyPublisher<Output, Failure>: Publisher where Failure: Error {
typealias Promise = (Swift.Result<Output, Failure>) -> Void
private let handler: (@escaping Promise) -> Void
init(_ handler: @escaping (@escaping Promise) -> Void) {
self.handler = handler
}
func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
let subject = PassthroughSubject<Output, Failure>()
subject.receive(subscriber: subscriber)
handler { result in
switch result {
case .success(let value):
subject.send(value)
subject.send(completion: .finished)
case .failure(let error):
subject.send(completion: .failure(error))
}
}
}
} The usage looks similar to regular Future then: private func loadImageData(action: @escaping () async -> Data?) -> LazyPublisher<Data, Error> {
LazyPublisher { promise in
Task {
do {
let result = try await action()
promise(.success(result))
} catch {
promise(.failure(error))
}
}
}
} let request = ImageRequest(
id: "preview_\(photoId)",
dataPublisher: loadImageData { await loadPreviewFromDb(photoId) },
options: [.disableDiskCache]
) |
Thanks for the details. I would be surprised that there is something inherently wrong with My long-term goal is to do it the other way around and update the framework to use |
I'm a pretty inexperienced Swift / iOS developer, but in my understanding the I was not able to reproduce the crashes myself but 0.5% crash rate was high enough for us to stop the rollout and release the fix asap. For us it was not a problem that
|
I went through it, and I'm skeptical about the described scenario with blocking the threads using semaphores. But yeah, I think the best solution would be to entirely remove Combine from the equation and add first-class support for async/await on the |
Thanks for the fix @kean! This makes my code so much easier |
I need to show a photo grid where each photo has a tiny preview stored in local DB, and I want to display these previews shortly until a bigger thumbnail is loaded from remote URL.
To load local previews I'm creating a custom
ImageRequest
like this:Now when the grid is shown only the first 6 previews are loaded and the rest are just ignored, even though I can confirm that the actual data is loaded from the DB correctly. In the logs I see
Data loader returned empty data.
errors.Adding
.skipDataLoadingQueue
option kinda helps but I still see random blank previews from time to time.It turns out the async
data
function is called eagerly when it's converted intoPublisher
(see DataPublisher.swift/publisher(...) code), andPassthroughSubject
will ignore data that was sent while it had no subscribers (queued?).I managed to fix it by using lazy
Publisher
created like this:This seems to work just fine, but I think it worth fixing it in the library as well.
The text was updated successfully, but these errors were encountered: