From 3f039bd4f33b5785162eccf362a2cbc9153b8465 Mon Sep 17 00:00:00 2001 From: Merrick Sapsford Date: Thu, 19 Jan 2017 19:59:06 +0000 Subject: [PATCH 1/4] Add basic listener priority logic --- Source/Listenable/Listenable.swift | 25 ++++++++++++++++++++++--- Source/Listenable/ListenerNode.swift | 4 +++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Source/Listenable/Listenable.swift b/Source/Listenable/Listenable.swift index 5719de3..c4ede1c 100644 --- a/Source/Listenable/Listenable.swift +++ b/Source/Listenable/Listenable.swift @@ -8,6 +8,19 @@ import Foundation +public enum ListenerPriority { + case low + case high + case custom(value: Int) + + var value: Int { + get { + // TODO - return actual priority values + return 0 + } + } +} + /// An object which can have a number of listeners for delegation. open class Listenable: AnyObject { @@ -38,9 +51,14 @@ open class Listenable: AnyObject { /// /// - Parameter listener: The new listener to add. /// - Returns: Whether the listener was successfully added. - @discardableResult public func add(listener: T) -> Bool { + @discardableResult public func add(listener: T, + priority: ListenerPriority = .low) -> Bool { if self.index(ofListener: listener) == nil { - self.listeners.append(ListenerNode(value: listener)) + + + // TODO - Evaluate priority when adding listeners + self.listeners.append(ListenerNode(value: listener, + priority: priority.value)) return true } return false @@ -49,7 +67,8 @@ open class Listenable: AnyObject { /// Add a number of new listeners to the Listenable object. /// /// - Parameter listeners: The new listeners to add. - public func add(listeners: [T]) -> Void { + public func add(listeners: [T], + priority: ListenerPriority = .low) -> Void { for listener in listeners { self.add(listener: listener) } diff --git a/Source/Listenable/ListenerNode.swift b/Source/Listenable/ListenerNode.swift index 2117cbb..9b7d506 100644 --- a/Source/Listenable/ListenerNode.swift +++ b/Source/Listenable/ListenerNode.swift @@ -11,8 +11,10 @@ import Foundation internal class ListenerNode { weak var value: AnyObject? + var priority: Int - init(value: T) { + init(value: T, priority: Int) { self.value = value as AnyObject + self.priority = priority } } From 7d61fc2e1db5debdcecb8238c664041ee648aaf9 Mon Sep 17 00:00:00 2001 From: Merrick Sapsford Date: Thu, 19 Jan 2017 23:40:55 +0000 Subject: [PATCH 2/4] Add listener priority positional logic --- .../ListenerViewController.swift | 2 +- Source/Listenable.xcodeproj/project.pbxproj | 4 ++ Source/Listenable/Listenable.swift | 46 ++++++++++++------- Source/Listenable/ListenerPriority.swift | 39 ++++++++++++++++ 4 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 Source/Listenable/ListenerPriority.swift diff --git a/Example/Listenable-Example/ListenerViewController.swift b/Example/Listenable-Example/ListenerViewController.swift index 7e362a2..bf6236c 100644 --- a/Example/Listenable-Example/ListenerViewController.swift +++ b/Example/Listenable-Example/ListenerViewController.swift @@ -21,7 +21,7 @@ class ListenerViewController: UIViewController, ListenableObjectDelegate { override func viewDidLoad() { super.viewDidLoad() - self.listenableObject.add(listeners: [self, self.actionButton]) + self.listenableObject.add(listeners: [self, self.actionButton], priority: .low) } // MARK: Actions diff --git a/Source/Listenable.xcodeproj/project.pbxproj b/Source/Listenable.xcodeproj/project.pbxproj index 8b7fc9d..8bd92ec 100644 --- a/Source/Listenable.xcodeproj/project.pbxproj +++ b/Source/Listenable.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 46F9E4311E318209005D7089 /* ListenerPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F9E4301E318209005D7089 /* ListenerPriority.swift */; }; D6465F591E250337004E8623 /* ListenerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6465F581E250337004E8623 /* ListenerNode.swift */; }; D6465F5B1E250355004E8623 /* TestListenableObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6465F5A1E250355004E8623 /* TestListenableObject.swift */; }; D6465F5D1E25063F004E8623 /* TestListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6465F5C1E25063F004E8623 /* TestListener.swift */; }; @@ -27,6 +28,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 46F9E4301E318209005D7089 /* ListenerPriority.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListenerPriority.swift; sourceTree = ""; }; D6465F581E250337004E8623 /* ListenerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListenerNode.swift; sourceTree = ""; }; D6465F5A1E250355004E8623 /* TestListenableObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestListenableObject.swift; sourceTree = ""; }; D6465F5C1E25063F004E8623 /* TestListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestListener.swift; sourceTree = ""; }; @@ -81,6 +83,7 @@ children = ( D6F613B41E24F59D004884E8 /* Listenable.h */, D6F613CB1E24F5F2004884E8 /* Listenable.swift */, + 46F9E4301E318209005D7089 /* ListenerPriority.swift */, D6465F581E250337004E8623 /* ListenerNode.swift */, D6F613B51E24F59D004884E8 /* Info.plist */, ); @@ -209,6 +212,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 46F9E4311E318209005D7089 /* ListenerPriority.swift in Sources */, D6465F591E250337004E8623 /* ListenerNode.swift in Sources */, D6F613CC1E24F5F2004884E8 /* Listenable.swift in Sources */, ); diff --git a/Source/Listenable/Listenable.swift b/Source/Listenable/Listenable.swift index c4ede1c..ab88db4 100644 --- a/Source/Listenable/Listenable.swift +++ b/Source/Listenable/Listenable.swift @@ -8,19 +8,6 @@ import Foundation -public enum ListenerPriority { - case low - case high - case custom(value: Int) - - var value: Int { - get { - // TODO - return actual priority values - return 0 - } - } -} - /// An object which can have a number of listeners for delegation. open class Listenable: AnyObject { @@ -50,15 +37,17 @@ open class Listenable: AnyObject { /// Add a new listener to the Listenable object. /// /// - Parameter listener: The new listener to add. + /// - Paramter priority: Enumeration positional priority of the listener. /// - Returns: Whether the listener was successfully added. @discardableResult public func add(listener: T, priority: ListenerPriority = .low) -> Bool { if self.index(ofListener: listener) == nil { + let insertionIndex = self.insertionIndex(forListenerWithPriority: priority) + let node = ListenerNode(value: listener, + priority: priority.value) + self.listeners.insert(node, at: insertionIndex) - // TODO - Evaluate priority when adding listeners - self.listeners.append(ListenerNode(value: listener, - priority: priority.value)) return true } return false @@ -67,10 +56,11 @@ open class Listenable: AnyObject { /// Add a number of new listeners to the Listenable object. /// /// - Parameter listeners: The new listeners to add. + /// - Paramter priority: Enumeration positional priority of the listeners. public func add(listeners: [T], priority: ListenerPriority = .low) -> Void { for listener in listeners { - self.add(listener: listener) + self.add(listener: listener, priority: priority) } } @@ -128,4 +118,26 @@ open class Listenable: AnyObject { } return index } + + private func insertionIndex(forListenerWithPriority priority: ListenerPriority) -> Int { + var insertionIndex: Int? + + if priority != .low { + + // enumerate until we find position where priority is less than desired + let priorityValue = priority.value + for (index, listener) in self.listeners.enumerated() { + if listener.priority < priorityValue { + insertionIndex = index + break + } + } + } + + if let insertionIndex = insertionIndex { + return insertionIndex + } + + return self.listeners.count + } } diff --git a/Source/Listenable/ListenerPriority.swift b/Source/Listenable/ListenerPriority.swift new file mode 100644 index 0000000..f8f5974 --- /dev/null +++ b/Source/Listenable/ListenerPriority.swift @@ -0,0 +1,39 @@ +// +// ListenerPriority.swift +// Listenable +// +// Created by Merrick Sapsford on 19/01/2017. +// Copyright © 2017 Merrick Sapsford. All rights reserved. +// + +import Foundation + +/// Positional priority of a Listener within the enumeration queue. +/// Ranges from 1 (low) to 1000 (high). +/// +/// - low: Listener is inserted at the end of the queue. (default) +/// - high: Listener is inserted at the front of the queue. +/// - custom: Custom queue prioritisation value (Range: 1 - 999) +public enum ListenerPriority: Equatable { + + case low + case high + case custom(value: Int) + + internal var value: Int { + get { + switch self { + case .high: + return 1000 + case .custom(let value): + return value + default: + return 0 + } + } + } + + public static func == (lhs: ListenerPriority, rhs: ListenerPriority) -> Bool { + return lhs.value == rhs.value + } +} From 6836874c25698dfd24cef782e6b4d33539e50f35 Mon Sep 17 00:00:00 2001 From: Merrick Sapsford Date: Thu, 19 Jan 2017 23:41:06 +0000 Subject: [PATCH 3/4] Add initial positional tests --- Source/ListenableTests/ListenableTests.swift | 26 +++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Source/ListenableTests/ListenableTests.swift b/Source/ListenableTests/ListenableTests.swift index b5fdafe..2ff5390 100644 --- a/Source/ListenableTests/ListenableTests.swift +++ b/Source/ListenableTests/ListenableTests.swift @@ -28,7 +28,7 @@ class ListenableTests: XCTestCase { self.listenableObject = TestListenableObject() } - // MARK: Tests + // MARK: Add listeners func testAddListener() { let initialListenerCount = self.listenableObject.listenerCount @@ -54,6 +54,28 @@ class ListenableTests: XCTestCase { XCTAssert(successfulAdd == false, "Duplicate listener was able to be added") } + func testAddHighPriorityListener() { + let lowPriorityListeners = [TestListener(), TestListener(), TestListener()] + self.listenableObject.add(listeners: lowPriorityListeners, priority: .low) + + let highPriorityListener = TestListener() + self.listenableObject.add(listener: highPriorityListener, priority: .high) + + var initialListener: TestListener! + self.listenableObject.updateListeners { (listener, index) in + if let listener = listener as? TestListener, index == 0 { + initialListener = listener + } + } + + XCTAssert(initialListener === highPriorityListener, + "High priority listener was not correctly inserted at index 0") + } + + // TODO: Add more prioritisation tests + + // MARK: Remove listeners + func testRemoveListener() { let listeners = self.addTestListeners(count: 1, toListenableObject: self.listenableObject) @@ -102,6 +124,8 @@ class ListenableTests: XCTestCase { "All listeners were not removed successfully") } + // MARK: Enumerate listeners + func testEnumerateAllListeners() { let proposedListenerCount = Int(arc4random_uniform(maxListenerCount) + 1) self.addTestListeners(count: proposedListenerCount, From 231980e840d6f33d48f939b3eb38c382bed69c7b Mon Sep 17 00:00:00 2001 From: Merrick Sapsford Date: Fri, 20 Jan 2017 16:19:53 +0000 Subject: [PATCH 4/4] Add additional prioritisation tests --- Source/Listenable.xcodeproj/project.pbxproj | 24 +++++++---- Source/ListenableTests/ListenableTests.swift | 41 ++++++++++++++++++- .../{ => Objects}/TestListenableObject.swift | 0 .../{ => Objects}/TestListener.swift | 0 4 files changed, 56 insertions(+), 9 deletions(-) rename Source/ListenableTests/{ => Objects}/TestListenableObject.swift (100%) rename Source/ListenableTests/{ => Objects}/TestListener.swift (100%) diff --git a/Source/Listenable.xcodeproj/project.pbxproj b/Source/Listenable.xcodeproj/project.pbxproj index 8bd92ec..f22c90b 100644 --- a/Source/Listenable.xcodeproj/project.pbxproj +++ b/Source/Listenable.xcodeproj/project.pbxproj @@ -9,8 +9,8 @@ /* Begin PBXBuildFile section */ 46F9E4311E318209005D7089 /* ListenerPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F9E4301E318209005D7089 /* ListenerPriority.swift */; }; D6465F591E250337004E8623 /* ListenerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6465F581E250337004E8623 /* ListenerNode.swift */; }; - D6465F5B1E250355004E8623 /* TestListenableObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6465F5A1E250355004E8623 /* TestListenableObject.swift */; }; - D6465F5D1E25063F004E8623 /* TestListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6465F5C1E25063F004E8623 /* TestListener.swift */; }; + D67206471E326E42003E22C3 /* TestListenableObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67206451E326E42003E22C3 /* TestListenableObject.swift */; }; + D67206481E326E42003E22C3 /* TestListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67206461E326E42003E22C3 /* TestListener.swift */; }; D6F613BB1E24F59D004884E8 /* Listenable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6F613B11E24F59D004884E8 /* Listenable.framework */; }; D6F613C01E24F59D004884E8 /* ListenableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F613BF1E24F59D004884E8 /* ListenableTests.swift */; }; D6F613C21E24F59D004884E8 /* Listenable.h in Headers */ = {isa = PBXBuildFile; fileRef = D6F613B41E24F59D004884E8 /* Listenable.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -30,8 +30,8 @@ /* Begin PBXFileReference section */ 46F9E4301E318209005D7089 /* ListenerPriority.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListenerPriority.swift; sourceTree = ""; }; D6465F581E250337004E8623 /* ListenerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListenerNode.swift; sourceTree = ""; }; - D6465F5A1E250355004E8623 /* TestListenableObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestListenableObject.swift; sourceTree = ""; }; - D6465F5C1E25063F004E8623 /* TestListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestListener.swift; sourceTree = ""; }; + D67206451E326E42003E22C3 /* TestListenableObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestListenableObject.swift; sourceTree = ""; }; + D67206461E326E42003E22C3 /* TestListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestListener.swift; sourceTree = ""; }; D6F613B11E24F59D004884E8 /* Listenable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Listenable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D6F613B41E24F59D004884E8 /* Listenable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Listenable.h; sourceTree = ""; }; D6F613B51E24F59D004884E8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -60,6 +60,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D67206441E326E42003E22C3 /* Objects */ = { + isa = PBXGroup; + children = ( + D67206451E326E42003E22C3 /* TestListenableObject.swift */, + D67206461E326E42003E22C3 /* TestListener.swift */, + ); + path = Objects; + sourceTree = ""; + }; D6F613A71E24F59D004884E8 = { isa = PBXGroup; children = ( @@ -93,8 +102,7 @@ D6F613BE1E24F59D004884E8 /* ListenableTests */ = { isa = PBXGroup; children = ( - D6465F5A1E250355004E8623 /* TestListenableObject.swift */, - D6465F5C1E25063F004E8623 /* TestListener.swift */, + D67206441E326E42003E22C3 /* Objects */, D6F613BF1E24F59D004884E8 /* ListenableTests.swift */, D6F613C11E24F59D004884E8 /* Info.plist */, ); @@ -223,8 +231,8 @@ buildActionMask = 2147483647; files = ( D6F613C01E24F59D004884E8 /* ListenableTests.swift in Sources */, - D6465F5D1E25063F004E8623 /* TestListener.swift in Sources */, - D6465F5B1E250355004E8623 /* TestListenableObject.swift in Sources */, + D67206481E326E42003E22C3 /* TestListener.swift in Sources */, + D67206471E326E42003E22C3 /* TestListenableObject.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/ListenableTests/ListenableTests.swift b/Source/ListenableTests/ListenableTests.swift index 2ff5390..996f834 100644 --- a/Source/ListenableTests/ListenableTests.swift +++ b/Source/ListenableTests/ListenableTests.swift @@ -54,6 +54,8 @@ class ListenableTests: XCTestCase { XCTAssert(successfulAdd == false, "Duplicate listener was able to be added") } + // MARK: Prioritisation + func testAddHighPriorityListener() { let lowPriorityListeners = [TestListener(), TestListener(), TestListener()] self.listenableObject.add(listeners: lowPriorityListeners, priority: .low) @@ -72,7 +74,44 @@ class ListenableTests: XCTestCase { "High priority listener was not correctly inserted at index 0") } - // TODO: Add more prioritisation tests + func testAddLowPriorityListener() { + let highPriorityListeners = [TestListener(), TestListener(), TestListener()] + self.listenableObject.add(listeners: highPriorityListeners, priority: .high) + + let lowPriorityListener = TestListener() + self.listenableObject.add(listener: lowPriorityListener, priority: .low) + + var finalListener: TestListener! + self.listenableObject.updateListeners { (listener, index) in + if let listener = listener as? TestListener, index == self.listenableObject.listenerCount - 1 { + finalListener = listener + } + } + + XCTAssert(finalListener === lowPriorityListener, + "Low priority listener was not correctly inserted at end of listener queue") + } + + func testAddCustomPriorityListener() { + let highPriorityListeners = [TestListener()] + self.listenableObject.add(listeners: highPriorityListeners, priority: .high) + + let lowPriorityListeners = [TestListener()] + self.listenableObject.add(listeners: lowPriorityListeners, priority: .low) + + let customPriorityListener = TestListener() + self.listenableObject.add(listener: customPriorityListener, priority: .custom(value: 500)) + + var middleListener: TestListener! + self.listenableObject.updateListeners { (listener, index) in + if let listener = listener as? TestListener, index == self.listenableObject.listenerCount - lowPriorityListeners.count - 1 { + middleListener = listener + } + } + + XCTAssert(middleListener === customPriorityListener, + "Custom priority (500) listener was not correctly inserted to the middle of the listener queue") + } // MARK: Remove listeners diff --git a/Source/ListenableTests/TestListenableObject.swift b/Source/ListenableTests/Objects/TestListenableObject.swift similarity index 100% rename from Source/ListenableTests/TestListenableObject.swift rename to Source/ListenableTests/Objects/TestListenableObject.swift diff --git a/Source/ListenableTests/TestListener.swift b/Source/ListenableTests/Objects/TestListener.swift similarity index 100% rename from Source/ListenableTests/TestListener.swift rename to Source/ListenableTests/Objects/TestListener.swift