Note: I'm an Android developer that trying to create iOS apps. So I may be missing some notions. Sorry for that.
In many managers and buisness classes, we could want to have a listeners mechanism (sometimes called events) with some notions to register (or addListener), unregister (or removeListener) and notify (or broadcast) all the listeners registered.
So the problem is the following: "How to create an implementation of the following contract bellow".
protocol SearchManager {
func search(
input: String
)
func getSearchResult(
input: String
) -> SearchResult
func registerSearchManagerListener(
listener: SearchManagerListener
)
func unregisterSearchManagerListener(
listener: SearchManagerListener
)
}
protocol SearchManagerListener : class {
func onSearchResultChanged()
}
class SearchResult {
private let input: String
private let youtubeIds: [String]
init(
input: String,
youtubeIds: [String]
) {
self.input = input
self.youtubeIds = youtubeIds
}
func getInput() -> String {
return input
}
func getYoutubeIds() -> [String] {
return [String](youtubeIds)
}
}
Note: We could imagine client of this manager for example an UIViewController
with
- a listener implementation of
SearchManagerListener
that update anUITableView
of search result - a method
viewDidAppear()
overriden that register the listener and synchronize the screen - a method
viewDidDisappear()
overriden that unregister the listener to avoid leaks
If I'm not mistaken, this solution use non swift list (more objectiveC) NSHashTable
to contains listeners.
- Pros: The field that contains lsiteners is a list. So it's conform to the expectation that we could have by the reading of the protocol contract.
- Cons: Notification of listeners require a cast for each listeners.
import Foundation
class SearchManagerImpl: SearchManager {
private let networkManager: NetworkManager
private let videoRepository: VideoRepository
private var searchResults = [String: SearchResult]()
private var searchManagerListeners: NSHashTable<AnyObject> = NSHashTable(options: .weakMemory)
init(
networkManager: NetworkManager,
videoRepository: VideoRepository
) {
self.networkManager = networkManager
self.videoRepository = videoRepository
}
func search(
input: String
) {
let queue = DispatchQueue(label: "search-queue")
queue.async {
let videos = SearchUtils.searchSync(
input: input
)
self.videoRepository.put(videos: videos)
let youtubeIds = Video.extractYoutubeIds(
videos: videos
)
DispatchQueue.main.async {
self.searchResults[input] = SearchResult(
input: input,
youtubeIds: youtubeIds
)
self.notifySearchResultChanged()
}
}
}
func getSearchResult(
input: String
) -> SearchResult {
let searchResult = searchResults[input]
if searchResult == nil {
return SearchResult.init(input: input, youtubeIds: [String]())
}
return searchResult!
}
func registerSearchManagerListener(
listener: SearchManagerListener
) {
searchManagerListeners.add(listener)
}
func unregisterSearchManagerListener(
listener: SearchManagerListener
) {
searchManagerListeners.remove(listener)
}
private func notifySearchResultChanged() {
for listener in searchManagerListeners.allObjects {
(listener as! SearchManagerListener).onSearchResultChanged()
}
}
}
Here a proposition to implement the listeners list with a map of <Ids, Listeners>:
- Pros: No need to cast the listeners on notification
- Const: Map is maybe heavier than
NSHashTable
import Foundation
class SearchManagerImpl: SearchManager {
private let networkManager: NetworkManager
private let videoRepository: VideoRepository
private var searchResults = [String: SearchResult]()
private var searchManagerListeners = [ObjectIdentifier: SearchManagerListener]()
init(
networkManager: NetworkManager,
videoRepository: VideoRepository
) {
self.networkManager = networkManager
self.videoRepository = videoRepository
}
func search(
input: String
) {
let queue = DispatchQueue(label: "search-queue")
queue.async {
let videos = SearchUtils.searchSync(
input: input
)
self.videoRepository.put(videos: videos)
let youtubeIds = Video.extractYoutubeIds(
videos: videos
)
DispatchQueue.main.async {
self.searchResults[input] = SearchResult(
input: input,
youtubeIds: youtubeIds
)
self.notifySearchResultChanged()
}
}
}
func getSearchResult(
input: String
) -> SearchResult {
let searchResult = searchResults[input]
if searchResult == nil {
return SearchResult.init(input: input, youtubeIds: [String]())
}
return searchResult!
}
func registerSearchManagerListener(
listener: SearchManagerListener
) {
searchManagerListeners[ObjectIdentifier(listener)] = listener
}
func unregisterSearchManagerListener(
listener: SearchManagerListener
) {
searchManagerListeners.removeValue(forKey: ObjectIdentifier(listener))
}
private func notifySearchResultChanged() {
for (_, listener) in searchManagerListeners {
listener.onSearchResultChanged()
}
}
}
Do not hesitate to comment this gist. Any help, comment, idea, will help a lot!
- The solution 1 is based on the Bowserf commit and the work at MWM.
- The solution 2 is an adaptation of the John Sundell medium article in my personal project.