Type-erasure on PAT with Self constraint

Hello. Trying to wrap NSOperation and ran into a series of problems. I will describe the structure with which I am currently working. The project is based on the following PAT:

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol ProducerTaskProtocol: Operation {
  
  associatedtype Output
  associatedtype Failure: Error
  
  // MARK: -
  
  typealias Produced = Result<Output, ProducerTaskProtocolError<Failure>>

  var produced: Produced? { get }
  
  // MARK: -
  
  var conditions: [AnyTaskCondition] { get }
  
  @discardableResult
  func addCondition<C: TaskCondition>(_ condition: C) -> Self
  
  // MARK: -
  
  var observers: [Observer] { get }
   
  @discardableResult
  func addObserver<O: Observer>(_ observer: O) -> Self
  
  // MARK: -
  
  @discardableResult
  func addDependency<T: ProducerTaskProtocol>(_ task: T) -> Self
  
  @discardableResult
  func addDependencies<T: ProducerTaskProtocol>(_ tasks: [T]) -> Self

  // MARK: -
  
  func willEnqueue()
  
  // MARK: -
  
  func execute()
  
  // MARK: -
  
  func finish(with produced: Produced)
  
  func finished(with produced: Produced)
  
  // MARK: -
  
  func produce<T: ProducerTaskProtocol>(new task: T)
  
  // MARK: -
  
  func recieve(completion: @escaping (Produced) -> Void) -> Self
}

And the main class implements it:

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
open class ProducerTask<Output, Failure: Error>: Operation, ProducerTaskProtocol { 
  // ...
}

In turn, the protocol is expanded as follows:

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol ConsumerProducerTaskProtocol: ProducerTaskProtocol {
  
  associatedtype Input
  
  // MARK: -
  
  typealias ProducingTask = ProducerTask<Input, Failure>
  
  var producing: ProducingTask { get }
  
  // MARK: -
  
  typealias Consumed = ProducingTask.Produced
  
  var consumed: Consumed? { get }
  
  // MARK: -
  
  func execute(with consumed: Consumed)
}

And implemented as:

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
open class ConsumerProducerTask<Input, Output, Failure: Error>: ProducerTask<Output, Failure>, ConsumerProducerTaskProtocol {
  // ...
}

And here a problem arises. I cannot storing various tasks, I can’t even pass them as an argument, in a similar way:

  public init<T: ProducerTaskProtocol>(
    name: String? = nil,
    qos: QualityOfService = .default,
    priority: Operation.QueuePriority = .normal,
    underlyingQueue: DispatchQueue? = nil,
    tasks: [T],
    produced: ProducerTask<Output, Failure>
  ) { 
    // ...
  }

Of course, here comes the thought of type-erasures, but how to do it? After all, the main protocol has a Self constraint - NSOperation.

Could you post the compilation error you are getting ?

Is the error is something like Protocol 'P1' can only be used as a generic constraint because it has Self or associated type requirements ?

  • If so then you could use Opaque Types (represented by the keyword some followed by the protocol).

Well, I use it as a generic constraint. Look at the initializer in the example.

The problem is that the array must be homogeneous.

let t1 = ProducerTask<Int, MyError>(...)
let t2 = ConsumerProduserTask<Int, Int, MyError>(...)
let t3 = ProducerTask<String, MySecondError>(...)

init(tasks: [t1, t2]) // OK

but

init(tasks: [t1, t2, t3]) // not OK

Would it be possible for the tasks to accept [Operation] instead ?

Also if possible you could use the base class Operation where ever sufficient instead of Self.

I can’t go for such a restriction, because the whole point is precisely in these two PATs (in the examples above).

The developers of the same SwiftUI apparently also faced this problem. But they do it by wrapping one type in another...

I'm in a panic