Skip to content

Commit

Permalink
Avoid using Combine FlatMap, that leaks memory when Fail is returned (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
luizmb committed Jan 25, 2022
1 parent a2a0775 commit 21010b4
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ import Foundation
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Publisher {
public func flatMapResult<T>(
maxPublishers: Subscribers.Demand = .unlimited,
_ transform: @escaping (Self.Output) -> Result<T, Failure>
) -> Publishers.FlatMap<Result<T, Failure>.Publisher, Self> {
flatMap(maxPublishers: maxPublishers) { value in
) -> Publishers.FlatMapLatest<Self, Result<T, Failure>.Publisher> {
flatMapLatest { value in
transform(value).publisher
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Combine
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Publishers.CombineLatest {
public func prependLatest<P: Publisher>(_ publisher: P) -> AnyPublisher<Output, Failure> where P.Output == Output, P.Failure == Failure {
publisher.flatMap { left, right in
publisher.flatMapLatest { left, right in
Publishers.CombineLatest(
self.a.prepend(left),
self.b.prepend(right)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public struct NonEmptyPublisher<Upstream: Publisher>: Publisher {
upstream
.map(EmptyStream.someValue)
.replaceEmpty(with: EmptyStream.empty)
.flatMap { valueType -> AnyPublisher<Output, Failure> in
.flatMapLatest { valueType -> AnyPublisher<Output, Failure> in
switch valueType {
case .empty:
switch fallback() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import Foundation
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Publishers.Promise where Success == (data: Data, response: URLResponse), Failure == URLError {
public func validStatusCode() -> Publishers.Promise<Void, URLError> {
flatMap { _, response -> Publishers.Promise<Void, URLError> in
flatMapResult { _, response -> Result<Void, URLError> in
guard let httpResponse = response as? HTTPURLResponse else {
return Publishers.Promise(error: URLError(.badServerResponse))
return .failure(URLError(.badServerResponse))
}

return (200..<400) ~= httpResponse.statusCode
? Publishers.Promise(value: ())
: Publishers.Promise(error: URLError(.badServerResponse))
? .success(())
: .failure(URLError(.badServerResponse))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,34 @@ extension PromiseType {
Publishers.Promise { self.eraseToAnyPublisher().print(prefix, to: stream) }
}

public func flatMapResult<T>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Output) -> Result<T, Failure>) -> Publishers.Promise<T, Failure> {
Publishers.Promise { self.eraseToAnyPublisher().flatMapResult(maxPublishers: maxPublishers, transform) }
public func flatMapResult<T>(_ transform: @escaping (Output) -> Result<T, Failure>) -> Publishers.Promise<T, Failure> {
Publishers.Promise { self.eraseToAnyPublisher().flatMapResult(transform) }
}

public func flatMap<O>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Output) -> Publishers.Promise<O, Never>) -> Publishers.Promise<O, Failure> {
public func flatMap<O>(_ transform: @escaping (Output) -> Publishers.Promise<O, Never>) -> Publishers.Promise<O, Failure> {
Publishers.Promise { self.eraseToAnyPublisher().flatMapLatest { transform($0).setFailureType(to: Failure.self) } }
}

public func flatMap<O, E: Error>(_ transform: @escaping (Output) -> Publishers.Promise<O, E>) -> Publishers.Promise<O, E> where Failure == Never {
Publishers.Promise { self.eraseToAnyPublisher().setFailureType(to: E.self).flatMapLatest(transform) }
}

public func flatMap<O>(_ transform: @escaping (Output) -> Publishers.Promise<O, Failure>) -> Publishers.Promise<O, Failure> {
Publishers.Promise { self.eraseToAnyPublisher().flatMapLatest(transform) }
}

@available(*, deprecated, message: "Please use the function without providing maxPublishers, as the original Combine FlatMap may have some issues when returning error.")
public func flatMap<O>(maxPublishers: Subscribers.Demand, _ transform: @escaping (Output) -> Publishers.Promise<O, Never>) -> Publishers.Promise<O, Failure> {
Publishers.Promise { self.eraseToAnyPublisher().flatMap(maxPublishers: maxPublishers) { transform($0).setFailureType(to: Failure.self) } }
}

public func flatMap<O, E: Error>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Output) -> Publishers.Promise<O, E>) -> Publishers.Promise<O, E> where Failure == Never {
@available(*, deprecated, message: "Please use the function without providing maxPublishers, as the original Combine FlatMap may have some issues when returning error.")
public func flatMap<O, E: Error>(maxPublishers: Subscribers.Demand, _ transform: @escaping (Output) -> Publishers.Promise<O, E>) -> Publishers.Promise<O, E> where Failure == Never {
Publishers.Promise { self.eraseToAnyPublisher().setFailureType(to: E.self).flatMap(maxPublishers: maxPublishers, transform) }
}

public func flatMap<O>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Output) -> Publishers.Promise<O, Failure>) -> Publishers.Promise<O, Failure> {
@available(*, deprecated, message: "Please use the function without providing maxPublishers, as the original Combine FlatMap may have some issues when returning error.")
public func flatMap<O>(maxPublishers: Subscribers.Demand, _ transform: @escaping (Output) -> Publishers.Promise<O, Failure>) -> Publishers.Promise<O, Failure> {
Publishers.Promise { self.eraseToAnyPublisher().flatMap(maxPublishers: maxPublishers, transform) }
}

Expand Down
8 changes: 4 additions & 4 deletions Sources/FoundationExtensions/Reader/Reader+Publisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ extension Reader where Value: Publisher {
mapValue { $0.map(transform).mapError(errorTransform).eraseToAnyPublisher() }
}

public func flatMapPublisher<NewPublisher: Publisher>(
public func flatMapLatestPublisher<NewPublisher: Publisher>(
_ transform: @escaping (Value.Output) -> NewPublisher
) -> Reader<Environment, AnyPublisher<NewPublisher.Output, Value.Failure>> where NewPublisher.Failure == Value.Failure {
mapValue { $0.flatMap { transform($0) }.eraseToAnyPublisher() }
mapValue { $0.flatMapLatest { transform($0) }.eraseToAnyPublisher() }
}

public func flatMapPublisher<NewPublisher: Publisher>(
public func flatMapLatestPublisher<NewPublisher: Publisher>(
_ transform: @escaping (Value.Output) -> Reader<Environment, NewPublisher>
) -> Reader<Environment, AnyPublisher<NewPublisher.Output, NewPublisher.Failure>> where Value: Publisher, Value.Failure == NewPublisher.Failure {
flatMap { publisher -> Reader<Environment, AnyPublisher<NewPublisher.Output, NewPublisher.Failure>> in
Reader<Environment, AnyPublisher<NewPublisher.Output, NewPublisher.Failure>> { env in
publisher.flatMap { value -> AnyPublisher<NewPublisher.Output, NewPublisher.Failure> in
publisher.flatMapLatest { value -> AnyPublisher<NewPublisher.Output, NewPublisher.Failure> in
transform(value).inject(env).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
Expand Down

0 comments on commit 21010b4

Please sign in to comment.