Combine: Add alongside(with block:(T) -> Void) publisher

Consider following Service that captures data.

class Service {
  var captured: String = ""
  func save(_ captured: String) -> String {
    self.captured = data
    return captured
  }

  func myPublisher() -> AnyPublisher<String, Error> {
    // ... configure publisher ...
    return publisher.map(self.save)
  }
}

Example above doesn't work, because it causes memory leak if I understand map() correctly.

Could we add .alongside publisher which captures [weak self] and behaves exactly as map which captures non-escaping closure:

// kind of..
func alongside<Value, P: Publisher>(value: Value, block: (Value) -> Void) -> P {
   block(value) // waiting
   return value
}

OR

We could add publisher that accepts functions instead like assign:

.alongside(\.save, on: self)

Combine is not a part of the open-source Swift project and is not therefore within the scope of the Swift Evolution process. I think there may be ways to offer suggestions to Apple.

2 Likes

I don't see any memory leak in your example. You return a publisher that retains the Service, but you didn't include any code that makes the Service retain the publisher.

1 Like

Interesting point. Maybe I should rethink current Service model.

However, in outer code it would be used as:

class ViewModel {
  private var service: Service = .init()
  var subscription: AnyCancellable?

  func setup() {
    self.subscription = self.service.myPublisher().sink{ (value) in
      // process value from publisher.
    }    
  }
}

You probably need to use [weak self] in the “process value from publisher” closure, if you use self in that closure.

Do you mean that everything is fine in this example, because view model retains service and also publisher retains service?

But it seems a bit weird, because publisher can't retain service, no?

Why not?

Because Service produces publisher...
Maybe I don't understand common patterns with Combine / Reactive.

So, this implementation allows following tricky code:

// We don't bother about lifetime of service.
// Service's publisher retains service.
self.subscription = Service.init().myPublisher().sink{ 
  [weak self] (value) in
  self?.update(newValue: value)
}
Terms of Service

Privacy Policy

Cookie Policy