From 34cac2aab8c48ebb94c8b447fad003c6f78edfd8 Mon Sep 17 00:00:00 2001 From: Quentin Jin Date: Tue, 19 Mar 2019 22:54:16 +0800 Subject: [PATCH] Naming & Docs --- Schedule.xcodeproj/project.pbxproj | 30 +++-- Sources/Schedule/Atomic.swift | 3 +- Sources/Schedule/Bag.swift | 104 ++++++++++++++++++ Sources/Schedule/Cabinet.swift | 70 ------------ Sources/Schedule/DeinitObserver.swift | 25 +++-- Sources/Schedule/Extensions.swift | 13 ++- Sources/Schedule/Task.swift | 27 ++--- Sources/Schedule/Timeline.swift | 4 +- .../{CabinetTests.swift => BagTests.swift} | 33 +++--- Tests/ScheduleTests/DeinitObserverTests.swift | 4 +- Tests/ScheduleTests/XCTestManifests.swift | 2 +- 11 files changed, 188 insertions(+), 127 deletions(-) create mode 100644 Sources/Schedule/Bag.swift delete mode 100644 Sources/Schedule/Cabinet.swift rename Tests/ScheduleTests/{CabinetTests.swift => BagTests.swift} (69%) diff --git a/Schedule.xcodeproj/project.pbxproj b/Schedule.xcodeproj/project.pbxproj index e61b5cd..f34e100 100644 --- a/Schedule.xcodeproj/project.pbxproj +++ b/Schedule.xcodeproj/project.pbxproj @@ -25,7 +25,7 @@ OBJ_101 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* XCTestManifests.swift */; }; OBJ_103 /* Schedule.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Schedule::Schedule::Product" /* Schedule.framework */; }; OBJ_50 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Atomic.swift */; }; - OBJ_51 /* Cabinet.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Cabinet.swift */; }; + OBJ_51 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Bag.swift */; }; OBJ_52 /* DeinitObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* DeinitObserver.swift */; }; OBJ_53 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* Deprecated.swift */; }; OBJ_54 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Extensions.swift */; }; @@ -44,7 +44,7 @@ OBJ_74 /* Schedule.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Schedule::Schedule::Product" /* Schedule.framework */; }; OBJ_81 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; OBJ_92 /* AtomicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* AtomicTests.swift */; }; - OBJ_93 /* CabinetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* CabinetTests.swift */; }; + OBJ_93 /* BagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* BagTests.swift */; }; OBJ_94 /* DateTimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* DateTimeTests.swift */; }; OBJ_95 /* DeinitObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* DeinitObserverTests.swift */; }; OBJ_96 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* ExtensionsTests.swift */; }; @@ -78,7 +78,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - OBJ_10 /* Cabinet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cabinet.swift; sourceTree = ""; }; + OBJ_10 /* Bag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bag.swift; sourceTree = ""; }; OBJ_11 /* DeinitObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeinitObserver.swift; sourceTree = ""; }; OBJ_12 /* Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecated.swift; sourceTree = ""; }; OBJ_13 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; @@ -95,7 +95,7 @@ OBJ_25 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; OBJ_26 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; OBJ_29 /* AtomicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicTests.swift; sourceTree = ""; }; - OBJ_30 /* CabinetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CabinetTests.swift; sourceTree = ""; }; + OBJ_30 /* BagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BagTests.swift; sourceTree = ""; }; OBJ_31 /* DateTimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeTests.swift; sourceTree = ""; }; OBJ_32 /* DeinitObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeinitObserverTests.swift; sourceTree = ""; }; OBJ_33 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = ""; }; @@ -139,13 +139,24 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 6639192D223FD4B5000F412E /* Utils */ = { + isa = PBXGroup; + children = ( + OBJ_29 /* AtomicTests.swift */, + OBJ_30 /* BagTests.swift */, + OBJ_32 /* DeinitObserverTests.swift */, + OBJ_33 /* ExtensionsTests.swift */, + ); + name = Utils; + sourceTree = ""; + }; 66C87992223A90D300A95D60 /* Utils */ = { isa = PBXGroup; children = ( OBJ_13 /* Extensions.swift */, OBJ_11 /* DeinitObserver.swift */, OBJ_9 /* Atomic.swift */, - OBJ_10 /* Cabinet.swift */, + OBJ_10 /* Bag.swift */, ); name = Utils; sourceTree = ""; @@ -183,11 +194,8 @@ OBJ_28 /* ScheduleTests */ = { isa = PBXGroup; children = ( - OBJ_29 /* AtomicTests.swift */, - OBJ_30 /* CabinetTests.swift */, + 6639192D223FD4B5000F412E /* Utils */, OBJ_31 /* DateTimeTests.swift */, - OBJ_32 /* DeinitObserverTests.swift */, - OBJ_33 /* ExtensionsTests.swift */, OBJ_34 /* Helpers.swift */, OBJ_35 /* PlanTests.swift */, OBJ_36 /* TaskCenterTests.swift */, @@ -358,7 +366,7 @@ buildActionMask = 0; files = ( OBJ_50 /* Atomic.swift in Sources */, - OBJ_51 /* Cabinet.swift in Sources */, + OBJ_51 /* Bag.swift in Sources */, OBJ_52 /* DeinitObserver.swift in Sources */, OBJ_53 /* Deprecated.swift in Sources */, OBJ_54 /* Extensions.swift in Sources */, @@ -397,7 +405,7 @@ buildActionMask = 0; files = ( OBJ_92 /* AtomicTests.swift in Sources */, - OBJ_93 /* CabinetTests.swift in Sources */, + OBJ_93 /* BagTests.swift in Sources */, OBJ_94 /* DateTimeTests.swift in Sources */, OBJ_95 /* DeinitObserverTests.swift in Sources */, OBJ_96 /* ExtensionsTests.swift in Sources */, diff --git a/Sources/Schedule/Atomic.swift b/Sources/Schedule/Atomic.swift index eb9997c..9402454 100644 --- a/Sources/Schedule/Atomic.swift +++ b/Sources/Schedule/Atomic.swift @@ -1,11 +1,12 @@ import Foundation -/// A value box that can read and write the underlying value atomically. +/// Represents a box that can read and write the underlying value atomically. final class Atomic { private var v: T private let lock = NSLock() + /// Init with the underlying value. init(_ value: T) { self.v = value } diff --git a/Sources/Schedule/Bag.swift b/Sources/Schedule/Bag.swift new file mode 100644 index 0000000..c7c6a5b --- /dev/null +++ b/Sources/Schedule/Bag.swift @@ -0,0 +1,104 @@ +import Foundation + +/// A unique key used to remove the corresponding element from a bag. +struct BagKey: Equatable { + + fileprivate let i: UInt64 + + fileprivate init(underlying: UInt64) { + self.i = underlying + } + + /// Returns a Boolean value indicating whether two BagKeys are equal. + static func == (lhs: BagKey, rhs: BagKey) -> Bool { + return lhs.i == rhs.i + } +} + +/// A generator can generate a sequence of unique `BagKey`. +/// +/// let k1 = gen.next() +/// let k2 = gen.next() +/// ... +struct BagKeyGenerator: Sequence, IteratorProtocol { + + typealias Element = BagKey + + private var k = BagKey(underlying: 0) + + /// Gets next BagKey. + mutating func next() -> Element? { + if k.i == UInt64.max { + return nil + } + defer { k = BagKey(underlying: k.i + 1) } + return k + } +} + +/// A data structure used to store a sequence of elements. +/// +/// let k1 = bag.append(e1) +/// let k2 = bag.append(e2) +/// +/// for e in bag { +/// // -> e1 +/// // -> e2 +/// } +/// +/// bag.delete(k1) +struct Bag { + + private typealias Entry = (key: BagKey, element: Element) + + private var keys = BagKeyGenerator() + private var entries: [Entry] = [] + + /// Pushes the given element on to the end of this container. + @discardableResult + mutating func append(_ new: Element) -> BagKey { + let key = keys.next()! + + let entry = (key: key, element: new) + entries.append(entry) + + return key + } + + /// Returns the element for key if key is in this container. + func get(_ key: BagKey) -> Element? { + if let entry = entries.first(where: { $0.key == key }) { + return entry.element + } + return nil + } + + /// Deletes the element with the given key and returns this element. + @discardableResult + mutating func delete(_ key: BagKey) -> Element? { + if let i = entries.firstIndex(where: { $0.key == key }) { + return entries.remove(at: i).element + } + return nil + } + + /// Removes all elements from this containers. + mutating func clear() { + entries.removeAll() + } + + /// The number of elements in this containers. + var count: Int { + return entries.count + } +} + +extension Bag: Sequence { + + func makeIterator() -> AnyIterator { + var iterator = entries.makeIterator() + return AnyIterator { + return iterator.next()?.element + } + } +} diff --git a/Sources/Schedule/Cabinet.swift b/Sources/Schedule/Cabinet.swift deleted file mode 100644 index 99ba436..0000000 --- a/Sources/Schedule/Cabinet.swift +++ /dev/null @@ -1,70 +0,0 @@ -import Foundation - -struct CabinetKey: Equatable { - - private let i: UInt64 - - init(underlying: UInt64) { - self.i = underlying - } - - func increased() -> CabinetKey { - return CabinetKey(underlying: i &+ 1) - } - - static func == (lhs: CabinetKey, rhs: CabinetKey) -> Bool { - return lhs.i == rhs.i - } -} - -struct Cabinet { - - private typealias Entry = (key: CabinetKey, element: Element) - - private var key = CabinetKey(underlying: 0) - private var entries: [Entry] = [] - - @discardableResult - mutating func append(_ new: Element) -> CabinetKey { - defer { key = key.increased() } - - let entry = (key: key, element: new) - entries.append(entry) - - return key - } - - func get(_ key: CabinetKey) -> Element? { - if let entry = entries.first(where: { $0.key == key }) { - return entry.element - } - return nil - } - - @discardableResult - mutating func delete(_ key: CabinetKey) -> Element? { - if let i = entries.firstIndex(where: { $0.key == key }) { - return entries.remove(at: i).element - } - - return nil - } - - mutating func clear() { - entries.removeAll() - } - - var count: Int { - return entries.count - } -} - -extension Cabinet: Sequence { - - func makeIterator() -> AnyIterator { - var iterator = entries.makeIterator() - return AnyIterator { - return iterator.next()?.element - } - } -} diff --git a/Sources/Schedule/DeinitObserver.swift b/Sources/Schedule/DeinitObserver.swift index 50e684c..00a17d5 100644 --- a/Sources/Schedule/DeinitObserver.swift +++ b/Sources/Schedule/DeinitObserver.swift @@ -1,12 +1,19 @@ import Foundation -#if canImport(ObjectiveC) +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) -private var deinitObserverKey: Void = () +private var DEINIT_OBSERVER_KEY: Void = () +/// Used to observe object deinit. +/// +/// let observer = DeinitObserver.observe(target) { +/// print("\(target) deinit") +/// } +/// +/// observer.cancel() class DeinitObserver { - private(set) weak var object: AnyObject? + private(set) weak var observed: AnyObject? private var action: (() -> Void)? @@ -14,23 +21,25 @@ class DeinitObserver { self.action = action } + /// Installs observation. @discardableResult static func observe( _ object: AnyObject, onDeinit action: @escaping () -> Void ) -> DeinitObserver { let observer = DeinitObserver(action) - observer.object = object + observer.observed = object - objc_setAssociatedObject(object, &deinitObserverKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + objc_setAssociatedObject(object, &DEINIT_OBSERVER_KEY, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return observer } - func invalidate() { + /// Uninstalls observation. + func cancel() { action = nil - if let o = object { - objc_setAssociatedObject(o, &deinitObserverKey, nil, .OBJC_ASSOCIATION_ASSIGN) + if let o = observed { + objc_setAssociatedObject(o, &DEINIT_OBSERVER_KEY, nil, .OBJC_ASSOCIATION_ASSIGN) } } diff --git a/Sources/Schedule/Extensions.swift b/Sources/Schedule/Extensions.swift index 825cf3a..5294f4d 100644 --- a/Sources/Schedule/Extensions.swift +++ b/Sources/Schedule/Extensions.swift @@ -22,6 +22,7 @@ extension Int { extension Calendar { + /// The gregorian calendar with `en_US_POSIX` locale. static let gregorian: Calendar = { var cal = Calendar(identifier: .gregorian) cal.locale = Locale(identifier: "en_US_POSIX") @@ -31,6 +32,7 @@ extension Calendar { extension Date { + /// Zero o'clock in the morning. var startOfToday: Date { return Calendar.gregorian.startOfDay(for: self) } @@ -38,10 +40,17 @@ extension Date { extension NSLocking { + /// Executes a closure returning a value while acquiring the lock. @inline(__always) func withLock(_ body: () throws -> T) rethrows -> T { - lock() - defer { unlock() } + lock(); defer { unlock() } return try body() } + + /// Executes a closure returning a value while acquiring the lock. + @inline(__always) + func withLock(_ body: () throws -> Void) rethrows { + lock(); defer { unlock() } + try body() + } } diff --git a/Sources/Schedule/Task.swift b/Sources/Schedule/Task.swift index 12abcde..d4b7e70 100644 --- a/Sources/Schedule/Task.swift +++ b/Sources/Schedule/Task.swift @@ -3,19 +3,21 @@ import Foundation /// `ActionKey` represents a token that can be used to remove the action. public struct ActionKey { - fileprivate let cabinetKey: CabinetKey + fileprivate let bagKey: BagKey } -extension CabinetKey { +extension BagKey { - func asActionKey() -> ActionKey { - return ActionKey(cabinetKey: self) + fileprivate func asActionKey() -> ActionKey { + return ActionKey(bagKey: self) } } /// `Task` represents a timed task. open class Task { + public let id = UUID() + public typealias Action = (Task) -> Void private let _mutex = NSRecursiveLock() @@ -23,8 +25,8 @@ open class Task { private var _iterator: AnyIterator private var _timer: DispatchSourceTimer - private lazy var _onElapseActions = Cabinet() - private lazy var _onDeinitActions = Cabinet() + private lazy var _onElapseActions = Bag() + private lazy var _onDeinitActions = Bag() private lazy var _suspensions: UInt64 = 0 private lazy var _timeline = Timeline() @@ -34,8 +36,8 @@ open class Task { private lazy var _lifetime: Interval = Int.max.seconds private lazy var _lifetimeTimer: DispatchSourceTimer = { let timer = DispatchSource.makeTimerSource() - timer.setEventHandler { - self.cancel() + timer.setEventHandler { [weak self] in + self?.cancel() } timer.schedule(after: _lifetime) timer.resume() @@ -100,7 +102,7 @@ open class Task { /// Execute this task now, without disrupting its plan. public func execute() { - let actions = _mutex.withLock { () -> Cabinet in + let actions = _mutex.withLock { () -> Bag in let now = Date() if _timeline.firstExecution == nil { _timeline.firstExecution = now @@ -117,7 +119,7 @@ open class Task { execute() } - #if canImport(ObjectiveC) + #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) open func host(on target: AnyObject) { DeinitObserver.observe(target) { [weak self] in self?.cancel() @@ -287,7 +289,7 @@ open class Task { /// Removes action by key from this task. public func removeAction(byKey key: ActionKey) { _mutex.withLock { - _ = _onElapseActions.delete(key.cabinetKey) + _ = _onElapseActions.delete(key.bagKey) } } @@ -306,9 +308,8 @@ open class Task { extension Task: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) + hasher.combine(id) } /// Returns a boolean value indicating whether two tasks are equal. diff --git a/Sources/Schedule/Timeline.swift b/Sources/Schedule/Timeline.swift index 35994aa..52be369 100644 --- a/Sources/Schedule/Timeline.swift +++ b/Sources/Schedule/Timeline.swift @@ -22,13 +22,13 @@ extension Timeline: CustomStringConvertible { /// A textual representation of this timeline. public var description: String { - enum Cache { + enum Lazy { static let fmt = ISO8601DateFormatter() } let desc = { (d: Date?) -> String in guard let d = d else { return "nil" } - return Cache.fmt.string(from: d) + return Lazy.fmt.string(from: d) } return "Timeline: { " + diff --git a/Tests/ScheduleTests/CabinetTests.swift b/Tests/ScheduleTests/BagTests.swift similarity index 69% rename from Tests/ScheduleTests/CabinetTests.swift rename to Tests/ScheduleTests/BagTests.swift index d064d8a..bc17b5c 100644 --- a/Tests/ScheduleTests/CabinetTests.swift +++ b/Tests/ScheduleTests/BagTests.swift @@ -1,26 +1,29 @@ import XCTest @testable import Schedule -final class CabinetTests: XCTestCase { +final class BagTests: XCTestCase { typealias Fn = () -> Int - func testCabinetKey() { - let key = CabinetKey(underlying: 0) - XCTAssertEqual(key.increased(), CabinetKey(underlying: 1)) + func testBagKey() { + var g = BagKeyGenerator() + let k1 = g.next() + let k2 = g.next() + XCTAssertNotNil(k1) + XCTAssertNotNil(k2) + XCTAssertNotEqual(k1, k2) } func testAppend() { - var cabinet = Cabinet() - let k1 = cabinet.append { 1 } - let k2 = cabinet.append { 2 } + var cabinet = Bag() + cabinet.append { 1 } + cabinet.append { 2 } - XCTAssertEqual(k1.increased(), k2) XCTAssertEqual(cabinet.count, 2) } func testGet() { - var cabinet = Cabinet() + var cabinet = Bag() let k1 = cabinet.append { 1 } let k2 = cabinet.append { 2 } @@ -33,12 +36,10 @@ final class CabinetTests: XCTestCase { } XCTAssertEqual(fn1(), 1) XCTAssertEqual(fn2(), 2) - - XCTAssertNil(cabinet.get(k2.increased())) } func testDelete() { - var cabinet = Cabinet() + var cabinet = Bag() let k1 = cabinet.append { 1 } let k2 = cabinet.append { 2 } @@ -52,12 +53,10 @@ final class CabinetTests: XCTestCase { XCTAssertNotNil(fn2) XCTAssertEqual(cabinet.count, 0) - - XCTAssertNil(cabinet.delete(k2.increased())) } func testClear() { - var cabinet = Cabinet() + var cabinet = Bag() cabinet.append { 1 } cabinet.append { 2 } @@ -69,7 +68,7 @@ final class CabinetTests: XCTestCase { } func testSequence() { - var cabinet = Cabinet() + var cabinet = Bag() cabinet.append { 0 } cabinet.append { 1 } cabinet.append { 2 } @@ -82,7 +81,7 @@ final class CabinetTests: XCTestCase { } static var allTests = [ - ("testCabinetKey", testCabinetKey), + ("testBagKey", testBagKey), ("testAppend", testAppend), ("testGet", testGet), ("testDelete", testDelete), diff --git a/Tests/ScheduleTests/DeinitObserverTests.swift b/Tests/ScheduleTests/DeinitObserverTests.swift index bfde058..d4cd966 100644 --- a/Tests/ScheduleTests/DeinitObserverTests.swift +++ b/Tests/ScheduleTests/DeinitObserverTests.swift @@ -1,7 +1,7 @@ import XCTest @testable import Schedule -#if canImport(ObjectiveC) +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) final class DeinitObserverTests: XCTestCase { @@ -21,7 +21,7 @@ final class DeinitObserverTests: XCTestCase { let observer = DeinitObserver.observe(obj) { i += 1 } - observer.invalidate() + observer.cancel() } fn() XCTAssertEqual(i, 1) diff --git a/Tests/ScheduleTests/XCTestManifests.swift b/Tests/ScheduleTests/XCTestManifests.swift index 39c0505..25c93fb 100644 --- a/Tests/ScheduleTests/XCTestManifests.swift +++ b/Tests/ScheduleTests/XCTestManifests.swift @@ -8,7 +8,7 @@ public func allTests() -> [XCTestCaseEntry] { testCase(TaskCenterTests.allTests), testCase(TaskTests.allTests), testCase(AtomicTests.allTests), - testCase(CabinetTests.allTests), + testCase(BagTests.allTests), testCase(CalendarTests.allTests), testCase(ExtensionsTests.allTests) ]