0

I am trying to build a Publisher/Observer system to easily observe data changes from API and publish through the app regarding controllers.

The point I have come so far is just working for single observing. However today I came across an issue, which prevents an UIViewController to observe more than one Publisher.

I am not sharing the whole code just to prevent confusions and problem below is the main reason, I think. If there is a solution for it, then my problem would be solved.

protocol Base {
    associatedtype BaseType
}

protocol One: Base where BaseType == String {}

protocol Two: Base where BaseType == Int {}

class BaseClass {}

extension BaseClass: One {
    typealias BaseType = String
}

extension BaseClass: Two {}

When I try to extend the BaseClass to conform Two, it throws

'Two' requires the types 'BaseClass.BaseType' (aka 'String') and 'Int' be equivalent

There are additional methods in the Base protocol and they depend on the BaseType parameter-wise. But as I said before, I do not think that is the issue.

Any suggestions?

UPDATE: More details with an use-case

My base protocols are as follows;

protocol Publishable {
    associatedtype Publisher: Service
}

protocol Service {
    associatedtype Publishing: Publishable
    var data: Publishing? { get set }
    var observers: [AnyObserver<Publishing>] { get set }
    func publish(_ data: Publishing)
    func add(_ observer: AnyObserver<Publishing>)
}

protocol Observer {
    associatedtype ObservingType: Publishable
    func subscribe(toService service: ObservingType.Publisher)
    func received(_ data: ObservingType)
}

Then I needed a solution for adding different types conforms to Observer into same array. Type erasure applied;

struct AnyObserver<Observing: Publishable>: Observer {
    
    private let _subscribe: (Observing.Publisher) -> Void
    private let _received: (Observing) -> Void
    
    init<Base: Observer>(_ base: Base) where Observing == Base.ObservingType {
        _received = base.received
        _subscribe = base.subscribe
    }
    
    func subscribe(toService service: Observing.Publisher) {
        _subscribe(service)
    }
    
    func received(_ data: Observing) {
        _received(data)
    }
}

Afterwards our use case is here. Lets say we have a AViewController which needs data about Books and Movies from API. I did not include the API part because it is an another layer.

struct Book: Codable {
    var name: String?
    var author: String?
}

struct BookList: Codable {
    var data: [Book]?
    var status: Int?
}

extension BookList: Publishable {
    typealias Publisher = BookListService
}

struct Movie: Codable {
    var name: String?
    var director: String?
}

struct MovieList: Codable {
    var data: [Movie]?
    var status: Int?
}

extension MovieList: Publishable {
    typealias Publisher = MovieListService
}

Publishable extends BookList and MovieList because they need to carry information about which Service object can publish them. BookListService and MovieListService are declared as follows;

class BookListService: Service {
    var data: BookList?
    
    var observers: [AnyObserver<BookList>] = []
    
    func publish(_ data: BookList) {
        //publish
    }
    
    func add(_ observer: AnyObserver<BookList>) {
        observers.append(observer)
    }
}

class MovieListService: Service {
    var data: MovieList?
    
    var observers: [AnyObserver<MovieList>] = []
    
    func publish(_ data: MovieList) {
        //publish
    }
    
    func add(_ observer: AnyObserver<MovieList>) {
        observers.append(observer)
    }
}

And finally Observer protocols regarding BookList and MovieList.

protocol BookListObserver: Observer where ObservingType == BookList {}
protocol MovieListObserver: Observer where ObservingType == MovieList {}

And for our final use-case regards AViewController:

class AViewController: UIViewController {
    
    let bookListService = BookListService()
    let movieListService = MovieListService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        subscribe(toService: bookListService)
    }
}

extension AViewController: BookListObserver {
    
    func received(_ data: BookList) {
        // booklist received
    }
    
    func subscribe(toService service: BookListService) {
        service.add(AnyObserver(self))
    }
    
}

Until this point there are no kind of an error. However, all after this, if extending AViewController conforming to MovieListObserver throws 'MovieListObserver' requires the types 'BookList' and 'MovieList' be equivalent

extension AViewController: MovieListObserver {}

Just let me know, if you need further updates.

UPDATE:

I have found this topic, but could not apply to my situation somehow.

Faruk
  • 2,269
  • 31
  • 42
  • 2
    I think you need to rethink your design, what exactly are you trying to achieve here? `BaseType` is `associatedtype` declared in `Base` protocol so once you confirm to either Protocol One or Two you would be asked to provide `typealias` for `BaseType` and there can only be one, it cant toggle between `String` and `Int`. – Sandeep Bhandari Feb 11 '21 at 12:43
  • I do not think whole design is broken. Because the problem is just as explained as above and this is completely separated. At first I thought I could use protocols because normally they are great for extending functionality. I know If I separate those protocols to be exactly same but just with different types and then delete the `Base` protocol; it will work. But the problem, in my opinion, is that there will be multiple protocols with different names and using different types and the rest is the same(method names, functionality). In short, copy-pasting. Obviously, not the best practice. – Faruk Feb 11 '21 at 12:58
  • duplication of the code in separate protocols is what makes me wonder if you have designed it properly, if you can let us know your usecase completely with an example may be we can help better – Sandeep Bhandari Feb 11 '21 at 15:07
  • Ok, I will share complete use case later today. @SandeepBhandari – Faruk Feb 11 '21 at 15:09
  • @SandeepBhandari updated. sorry for the delay, I had some other stuff on my table. Will wait to hear from you. – Faruk Feb 12 '21 at 06:57
  • By the way, I tried and I was wrong and you were right about the design. @SandeepBhandari – Faruk Feb 12 '21 at 09:40
  • Just saw your updated question, will need sometime will revert back once I have better solution – Sandeep Bhandari Feb 12 '21 at 14:29
  • @SandeepBhandari hey. do you have any updates? – Faruk Feb 14 '21 at 12:03
  • Hey frank, sorry I couldnt take a look as I was busy with work, if I can suggest, I would suggest you to delete this question and repost a new question but this time add the actual use case of yours not the analogy code this will draw attention of people with much higher SO points and experience like rob, vadian or matt they might help you better and quicker – Sandeep Bhandari Feb 16 '21 at 04:38

0 Answers0