-1

I am trying to convert a design of mine to be pop. However I am stuck and already have bunch of threads for my approaches -not a duplicate of this question though- and apparently they are all dead end.

My question is, is there a way to override parameter types of a protocol method which inherited from another protocol?

struct Books: Codable {}

protocol Listener {
    func listen(_ param: Codable)
}

protocol BooksListener: Listener {
    func listen(_ param: Books)
}


class MyClass: BooksListener {
    // I want only this one to required with the type.
    func listen(_ param: Books) {
        <#code#>
    }
    
    func listen(_ param: Codable) {
        <#code#>
    }
}

I did my research and I believe this is not how protocols work. I am just seeking a solution to this.

I tried to add a associatedType to the Listener and use it as the type inside listen(_:). But this solution restricts any class to have conformance to multiple protocols which inherits from Listener. Details can be found here

Faruk
  • 2,269
  • 31
  • 42
  • Beyond the answer below, you may find this helpful: https://www.youtube.com/watch?v=DXwJg0QTlZE I believe you're misunderstanding what "POP" is. – Rob Napier Feb 16 '21 at 14:43
  • I have already watched it couple times, you suggested it before in another thread of mine. Apparently I need to watch it again. @RobNapier – Faruk Feb 16 '21 at 15:10
  • Heh; very fair. Suggests I'm not as clear as I'd like to be. I'll keep that in mind. I see in your other code, you've done a good job of starting with simple model objects (this question doesn't make that as clear). You're on an ok track, except for memory management. I'm writing up something – Rob Napier Feb 16 '21 at 15:13
  • Ok. I will wait for the response from you. Sadly though I am not using the other thread right now because of the issue stated there. I have let all the abstractification purpose go. It is working now, but obviously not the best practice with similar classes and protocols being there not sharing any ancestor. I will share current so called structure if you request. Btw, previous comment of mine is not a criticism towards you. It said it becuase my native language is not english and my mind works in a twisted way that sometimes I struggle to understand things. @RobNapier – Faruk Feb 16 '21 at 15:23
  • 1
    "but obviously not the best practice with similar classes and protocols being there not sharing any ancestor." I disagree that similar classes must share an ancestor. You can over-DRY code. Abstraction should always serve a purpose. Making things very complicated to eliminate a small amount of clear code can hurt a program more than it helps. But there may be some value here, so let's explore. :D – Rob Napier Feb 16 '21 at 15:33
  • 1
    (Also, building this up from scratch, I've recreated every single issue you've described. Some of this just can't be expressed in Swift.) – Rob Napier Feb 16 '21 at 15:45
  • I also started to think that having bunch of threads with no answers. I believe there must be something to do though. Maybe another class in between publishers and observers which is handling all, I mean all kind of publishers and observers, communication and delegation. Also memory issues could be solved using a solution mentioned in here https://stackoverflow.com/a/24128121/1404324 – Faruk Feb 16 '21 at 16:09
  • Once you add this many layers of protocols, you will find that it's hard to also add Weak. You'll need more type erasers. And if you find yourself creating a lot of type-erasers, you're generally on the wrong path. The main thing you're running into is that protocols are not first-class types. You can't describe protocols with other protocols in Swift. That's a legitimate thing to want; Swift just can't do it. – Rob Napier Feb 16 '21 at 16:14
  • So you say there is not any sensable/acceptable way to solve the problem? – Faruk Feb 16 '21 at 16:18
  • Depends on what "the problem" is. For example, creating a publishing thing is pretty straightforward. https://gist.github.com/rnapier/58e00f3c3260950be0dc – Rob Napier Feb 16 '21 at 16:19
  • 1
    But if you mean an API where you can call `service.add(observer: self)` and have that handled through protocol conformances in a highly generic way, no. Swift can't express that. – Rob Napier Feb 16 '21 at 16:21
  • 1
    Also, this is exactly the problem that we now have Combine available to solve. (My Observable, and later experiments like https://gist.github.com/rnapier/981e86fbf345b049c1df41f63e4a2c6e, were all built before Combine.) – Rob Napier Feb 16 '21 at 16:25
  • Sorry for the delay. And well, I am sorry to hear that. It was all started once I experienced Combine in a small SwiftUI project and I just wanted to have not the same but similar usability in iOS 11 and above. I will look into your version and try to understand how it is working. https://gist.github.com/mrfarukturgut/34a0b7d59ec66298f9d081b009b74a79, this is my version I am using right now. It just works. I have added a very simple version, starting point even, of another idea. @RobNapier – Faruk Feb 17 '21 at 07:26
  • 1
    My current incarnation, which I use for production iOS 11 projects, is here: https://gist.github.com/rnapier/4a397197a4c698d872301597668d964d – Rob Napier Feb 17 '21 at 14:43

1 Answers1

1

As a general rule, Swift protocols are not covariant. But your question is a special case, where, even if Swift allowed protocol covariance, your approach couldn't work.

In your example, a BooksListener is required to do everything a Listener can do. A Listener can take an arbitrary Codable as its parameter to listen. That means that a BooksListener must also be able to take an arbitrary Codable. Consider the following:

struct Books: Codable {}

protocol Listener {
    func listen(_ param: Codable)
}

protocol BooksListener: Listener {
    func listen(_ param: Books)
}

class MyClass: BooksListener {
    // Illegal, but assuming you had what you're asking for
    func listen(_ param: Books) {}
}

let l: Listener = MyClass()
l.listen("Strings are Codable, and Listener accepts Codable")

What would this do? This is violating LSP.

What you might want here is an associatedtype as you mention (but I'll explain later why you probably don't). With an associatedtype, you can define Listener the way I expect you mean.

struct Books: Codable {}

protocol Listener {
    associatedtype Parameter: Codable
    func listen(_ param: Parameter)
}

protocol BooksListener: Listener where Parameter == Books {
    func listen(_ param: Books)
}

class MyClass: BooksListener {
    func listen(_ param: Books) {}
}

Why don't you want this? Because it almost certainly doesn't mean anything. What can I possibly do with an arbitrary Listener? I can't call listen on it; I don't know the type of Parameter. There's no algorithm that it helps. And enabling algorithms is what associatedtypes are about.

But the real point is that "POP" doesn't mean "use lots of protocols" and absolutely doesn't mean "recreate class inheritance with protocols." It means "start with concrete types and extract protocols to share algorithms." So before you create a single protocol, you need to ask, what other kinds of Listeners exist in your program, and what algorithm needs to work on an arbitrary Listener? If there's no such algorithm, there should be no protocol.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I've answered this question based on what's in the question itself. I'm looking at your original question, which provides more background, and I'll write up an answer over there based on that question. – Rob Napier Feb 16 '21 at 14:48