Equivalent for Java's abstract function

Hi!


I am a long-time Java developer and new to Swift, currently writing my first iOS app.

I am writing an app similar to VVVV or Quartz composer, where you have a set of nodes processing some data and passing the output to a series of listening nodes.

I know the concept of abstract functions is not present in Swift, so I tried to use protocols.


So there's a super-class Node, which holds a Set of other Nodes implementing a protocol.


import Foundation


class Node : Equatable, Hashable {
    
    var id: Int
    var name : String?
    var listeners : Set<MessageListener>!
    
    
    init() {
        self.name = „Bla“
        self.id = 0
        
        self.listeners = Set<MessageListener>()
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(name)
    }
    
    static func == (lhs: Node, rhs: Node) -> Bool {
        return (lhs.id == rhs.id) && (lhs.name == rhs.name)
    }
    
    func addListenerNode(node : Node) {
        listeners.insert(node)
    }
    
    func removeListenerNode(node : Node) {
        listeners.remove(node)
    }
    
    internal func output(message: CircuitMessage) {
        for node in listeners {
            node.receive(message: message)
        }
    }
}

The protocol consists of just one function receive(message: NodeMessage).

protocol MessageListener : Hashable
{
    func receive(message: NodeMessage)
}


I have various subclasses of Node, e.g. SpecialNode1, SpecialNode2, etc. which all do some fancy things upon receiving and use the super class's method to distribute in to the connected nodes


import Foundation


class SpecialNode1 : Node, MessageListener {
    
    init()
    {
        super.init()
    }
    
    func receive(message: NodeMessage) {
        
  //do some fancy stuff to the NodeMessage
  //and send it out to the listeners
        output(message: midiMessage)
    }
    
}


This approach does not work, because apparently the protocol needs to be Hashable in order to be collected in a Set.

I tried an alternative approach and left out the Protocol, having a Set of <Node> in the Node class and putting the receive() function there, with the subclasses overriding that function. That however leads to the problem that the receive function is always called in the superclass, and not in the respective subclass.


Any ideas how to achieve this?

Why not use array instead of Set. And when you insert a node, just test if already in array, to avoid duplicates.


But the problem is not the Set.

In fact, you use Set<Protocol>: get the error

Protocol 'MessageListener' can only be used as a generic constraint because it has Self or associated type requirements


MessageListener should be defined as a class, which conforms to a Protocol.

One issue here is that your use of

Set
is masking the fundamental problem (for complex reasons that are hard to explain). If you switch
listeners
to use
Array
, you get a different error:
…/main.swift:7:27: error: protocol 'MessageListener' can only be used as a generic constraint because it has Self or associated type requirements
    var listeners = Array<MessageListener>()
                          ^

This is still hard to understand, but there’s a lot of work out there explaining the issue. The canonic example of this is WWDC 2015 Session 408 Protocol-Oriented Programming in Swift, but you can search the ’net for the problem’s formal name, the generalised existential problem, or simply for that error message, and find lots of other really useful info.

The fundamental problem here is that, if

MessageListener
is
Hashable
, it must also be
Equatable
. And
Equatable
implies a ‘self’ constraint, that is, the
==
operator only works if both sides are the same. This means the
Array<MessageListener>
must consist of all the same objects, but you have no control over that type of objects you get. Someone could pass you
ListenerA
in one call and
ListenerB
in another, and you have to deal with it.

In some cases it’s possible to fix this by switching to a generic context, but that doesn’t make sense here.

My standard approach for getting around this is to add an

id
property to the protocol, and then use that
id
property for all the work that requires equality checking. For example:
protocol MessageListener {
    var id: UUID { get }
    func receive(message: NodeMessage)  
}

That way I can compare, hash, and so on, two

MessageListener
values even if they’re not the same type.

However, I must stress that this is only one of many potential solutions here. For example, another equally valid option is to use classes and subclasses, just like you would in Java, albeit without the compile-time safety of abstract class support.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Claude31 and Eskimo for the quick response!


In fact I changed the Set to an Array and adjusted the protocol to be of a class instead of Hashable.

protocol MessageListener : class {
     func receive(message: NodeMessage)  
}

Also I found a copy/paste error in my first post's code. The addListenerNode() and removeListenerNode() functions of course take a MessageListener as parameter.


That solved my problem, however rising another one 🙂 However this is not in the scope of this thread.

Thank you for your help!

Start a new thread with the new question.


And don't forget to close this one.


Good continuation.

Equivalent for Java's abstract function
 
 
Q