2

I'm actually trying to implement a pub/sub pattern into my Swift projects, I have few Agents (Singletons) that perform tasks, when their status change they should notify all the listener previously subscribed.

I get the error shaping my protocol, probably it should be conforming to the Equatable protocol, but I cannot see in which way (declaring protocol AvailableChatListener: Equatable resolves nothing).

I know that I could use the Apple's NSNotificationCenter (as nicely points here), but it's not a solution completely suitable for my requirements.

Here the code:

AvailableChatListener.swift

protocol AvailableChatListener {
    func onAvailableChatChange()
}

AvailableChatAgent.swift

class AvailableChatAgent {
  // MARK: - Singleton

  class var sharedInstance: AvailableChatAgent {
    struct Singleton {
      static let instance = AvailableChatAgent()
    }
    return Singleton.instance
  }

  // MARK: - Listeners

  private var availableChatListeners = [AvailableChatListener]()

  // MARK: - Public Properties

  // current Chats
  var currentChats = [Chat]()   {
    didSet  {
      statusChanged()
    }
  }

  func startObserving()   {
    // ...
    // perform tasks and change currentChats
    // ...
  }

  func stopObserving()    {
    // ...
    // stop tasks
    // ...
  }

  func statusChanged()    {

    // notify listeners
    for receiver in availableChatListeners {
      receiver.onAvailableChatChange()
    }
  }

  // ERROR #1
  func subscribeListener(listener: AvailableChatListener)    {
    availableChatListeners.append(listener)
  }

  // ERROR #1
  func unsubscribeListener(listener: AvailableChatListener)  {

    // ERROR #2
    if let index = availableChatListeners.indexOf(listener) {
      availableChatListeners.removeAtIndex(availableChatListeners.indexOf(listener)!)

      if availableChatListeners.count == 0 {
        self.stopObserving()
      }
    }
  }
}

And here where I use this logic (for example in a TableViewController)

ChatListTableViewController.swift

class ChatListTableViewController: UITableViewController, AvailableChatListener {

  // MARK: - Properties

  var availableChatAgent = AvailableChatAgent.sharedInstance

  // MARK: - ViewDidLoad

  override func viewDidLoad() {
    super.viewDidLoad()

    availableChatAgent.subscribeListener(self)
  }

  // MARK: - Implement AvailableChatListener

  func onAvailableChatChange()    {
    // perform task with the new data, for example update UI
  }
}

Thanks in advance.

UPDATE 1

Errors that I get:

  1. protocol "AvailableChatListener' can only be used as a generic constraint because it has Self or associated type requirements
  2. Cannot convert value of type 'AvailableChatListener' to expect argument type '@noescpae(AvailableChatListener) throws -> Bool'
giacavicchioli
  • 334
  • 2
  • 14
  • What error do you get? – Pradeep K Feb 16 '16 at 10:46
  • I would use a set rather than an array for your listeners and implement the Hashable protocol – Paulw11 Feb 16 '16 at 11:02
  • @PradeepK This is the errors that I get: `protocol "AVailableChatListener' can only be used as a generic constraint because it has Self or associated type requirements` and `Cannot convert value of type 'AvailableChatListener' to expect argument type '@noescpae(AvailableChatListener) throws -> Bool`. Now I update the question. @Paulw11 Now I try with a Set instead of an Array, how can i implement the Hashable protocol? – giacavicchioli Feb 16 '16 at 11:05
  • http://nshipster.com/swift-comparison-protocols/ – Paulw11 Feb 16 '16 at 11:08
  • Actually I don't think you need to implement Hashable for set membership – Paulw11 Feb 16 '16 at 11:09
  • Is this your problem? http://stackoverflow.com/questions/24926310/what-does-protocol-can-only-be-used-as-a-generic-constraint-because-it-has – Paulw11 Feb 16 '16 at 11:10
  • @Paulw11 Yes, similar, but I cannot apply to my problem actually – giacavicchioli Feb 16 '16 at 11:15

2 Answers2

0

Please try this method of removing your listener and see if it works. I have converted the object into an NSArray and then look for the index of the listener in that array. I am presuming that two listeners are equal if they are the same object and not for any other condition.

func unsubscribeListener(listener: AvailableChatListener)  {
    let arr = availableChatListeners as NSArray
    let index = arr.indexOfObject(listener)
    if index != NSNotFound {
        availableChatListeners.removeAtIndex(index)
        if availableChatListeners.count == 0 {
            self.stopObserving()
        }
    }
}

You should also declare your protocol this way (add @objc)

@objc protocol AvailableChatListener  {
    func onAvailableChatChange()
}
Pradeep K
  • 3,671
  • 1
  • 11
  • 15
  • Nice, that actually resolved my problem with `func unsubscribeListener`. The problem is still there with the `func subscribeListener` and `availableChatListener` declaration (as noted by @Paulw11 I made my protocol conforming `Hashable` , but I get the error on declaration: `Using 'LocationListener' as a concrete type coforming to Protocol 'Hashable' is not supported`). How can I solve this? – giacavicchioli Feb 16 '16 at 12:39
  • I forgot to mention a change to the protocol. I have updated the answer. Not sure if that will solve your second problem though. With the updated answer I don't get errors in the places that you have mentioned – Pradeep K Feb 16 '16 at 16:13
0

I had the hit same problem and did the following to resolve it. The use of Objective-C Sets requires the use of NSObject based subscribers to get over the Hashing problems mentioned.

import UIKit

protocol LogoListener {
    func logo(urlString url: String, hasImage image: UIImage)
}

class Server {
    var logoListeners = NSMutableSet()
    func addListener(listener: LogoListener) {
        logoListeners.add(listener)
        print("Added listener \(listener)")
    }
}

class Registration: NSObject, LogoListener {
    func logo(urlString url: String, hasImage image: UIImage) {
        savedImage = image
    }
    var savedImage: UIImage = UIImage()
    override init() {
        super.init()
        let server = Server()
        server.addListener(listener: self)
    }
}

let reg = Registration()
Faisal Memon
  • 2,711
  • 16
  • 30