diff --git a/Schedule.xcodeproj/project.pbxproj b/Schedule.xcodeproj/project.pbxproj index ee2c64a..2b3d6b1 100644 --- a/Schedule.xcodeproj/project.pbxproj +++ b/Schedule.xcodeproj/project.pbxproj @@ -32,7 +32,6 @@ OBJ_48 /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* Interval.swift */; }; OBJ_49 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Lock.swift */; }; OBJ_50 /* Monthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* Monthday.swift */; }; - OBJ_51 /* ParasiticTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* ParasiticTask.swift */; }; OBJ_52 /* Period.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* Period.swift */; }; OBJ_53 /* Schedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Schedule.swift */; }; OBJ_54 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* Task.swift */; }; @@ -82,7 +81,6 @@ OBJ_12 /* Interval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interval.swift; sourceTree = ""; }; OBJ_13 /* Lock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = ""; }; OBJ_14 /* Monthday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Monthday.swift; sourceTree = ""; }; - OBJ_15 /* ParasiticTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParasiticTask.swift; sourceTree = ""; }; OBJ_16 /* Period.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Period.swift; sourceTree = ""; }; OBJ_17 /* Schedule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Schedule.swift; sourceTree = ""; }; OBJ_18 /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; @@ -222,7 +220,6 @@ OBJ_8 /* Schedule */ = { isa = PBXGroup; children = ( - OBJ_15 /* ParasiticTask.swift */, 66D9F43221563D7700D9F441 /* RunLoopTask.swift */, OBJ_17 /* Schedule.swift */, OBJ_18 /* Task.swift */, @@ -352,7 +349,6 @@ OBJ_48 /* Interval.swift in Sources */, OBJ_49 /* Lock.swift in Sources */, OBJ_50 /* Monthday.swift in Sources */, - OBJ_51 /* ParasiticTask.swift in Sources */, OBJ_52 /* Period.swift in Sources */, OBJ_53 /* Schedule.swift in Sources */, OBJ_54 /* Task.swift in Sources */, diff --git a/Sources/Schedule/DeinitObserver.swift b/Sources/Schedule/DeinitObserver.swift index b6c8e32..95688d8 100644 --- a/Sources/Schedule/DeinitObserver.swift +++ b/Sources/Schedule/DeinitObserver.swift @@ -9,24 +9,34 @@ import Foundation #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) -class DeinitObserver { +private var deinitObserverKey: Void = () - private weak var observed: T? +class DeinitObserver { - private var block: () -> Void + private(set) weak var observable: AnyObject? + + private var block: (() -> Void)? private init(_ block: @escaping () -> Void) { self.block = block } - static func observe(_ observed: T, whenDeinit: @escaping () -> Void) { - let observer = DeinitObserver(whenDeinit) - var key: Void = () - objc_setAssociatedObject(observed, &key, observer, .OBJC_ASSOCIATION_RETAIN) + @discardableResult + static func observe(_ observable: AnyObject, whenDeinit block: @escaping () -> Void) -> DeinitObserver { + let observer = DeinitObserver(block) + objc_setAssociatedObject(observable, &deinitObserverKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return observer + } + + func clear() { + block = nil + if let o = observable { + objc_setAssociatedObject(o, &deinitObserverKey, nil, .OBJC_ASSOCIATION_ASSIGN) + } } deinit { - block() + block?() } } diff --git a/Sources/Schedule/ParasiticTask.swift b/Sources/Schedule/ParasiticTask.swift deleted file mode 100644 index bc500f6..0000000 --- a/Sources/Schedule/ParasiticTask.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// ParasiticTask.swift -// Schedule -// -// Created by Quentin Jin on 2018/7/17. -// - -import Foundation - -extension Schedule { - - /// Schedules a task with this schedule. - /// - /// This method will receive a `host` object as a parameter, - /// the returned task will not retain this object, instead, - /// it will observe this object, when this object is dealloced, - /// the task will not be scheduled any more, something like parasitism. - /// - /// This feature is very useful when you want a scheduled task live and die - /// with a controller. - /// - /// - Parameters: - /// - queue: The queue to which the task will be dispatched. - /// - tag: The tag to be associated to the task. - /// - host: The object to be hosted on. - /// - onElapse: The action to do when time is out. - /// - Returns: The task just created. - @discardableResult - public func `do`(queue: DispatchQueue, - tag: String? = nil, - host: AnyObject, - onElapse: @escaping (Task) -> Void) -> Task { - return ParasiticTask(schedule: self, queue: queue, tag: tag, host: host, onElapse: onElapse) - } - - /// Schedules a task with this schedule. - /// - /// This method will receive a `host` object as a parameter, - /// the returned task will not retain this object, instead, - /// it will observe this object, when this object is dealloced, - /// the task will not scheduled any more, something like parasitism. - /// - /// This feature is very useful when you want a scheduled task live and die - /// with a controller. - /// - /// - Parameters: - /// - queue: The queue to which the task will be dispatched. - /// - tag: The tag to be associated to the task. - /// - host: The object to be hosted on. - /// - onElapse: The action to do when time is out. - /// - Returns: The task just created. - @discardableResult - public func `do`(queue: DispatchQueue, - tag: String? = nil, - host: AnyObject, - onElapse: @escaping () -> Void) -> Task { - return self.do(queue: queue, tag: tag, host: host, onElapse: { (_) in onElapse() }) - } -} - -private final class ParasiticTask: Task { - - weak var parasitifer: AnyObject? - - init(schedule: Schedule, queue: DispatchQueue?, tag: String?, host: AnyObject, onElapse: @escaping (Task) -> Void) { - super.init(schedule: schedule, queue: queue, tag: tag) { (task) in - guard (task as? ParasiticTask)?.parasitifer != nil else { - task.cancel() - return - } - onElapse(task) - } - self.parasitifer = host - - #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) - DeinitObserver.observe(host) { [weak self] in - self?.cancel() - } - #endif - } -} diff --git a/Sources/Schedule/RunLoopTask.swift b/Sources/Schedule/RunLoopTask.swift index 7abc3b7..0aa86f4 100644 --- a/Sources/Schedule/RunLoopTask.swift +++ b/Sources/Schedule/RunLoopTask.swift @@ -11,48 +11,30 @@ extension Schedule { /// Schedules a task with this schedule. /// - /// This method will receive a `host` object as a parameter, - /// the returned task will not retain this object, instead, - /// it will observe this object, when this object is dealloced, - /// the task will not be scheduled any more, something like parasitism. - /// - /// This feature is very useful when you want a scheduled task live and die - /// with a controller. - /// /// - Parameters: - /// - queue: The queue to which the task will be dispatched. - /// - tag: The tag to be associated to the task. + /// - mode: The mode in which to add the schedule. /// - host: The object to be hosted on. /// - onElapse: The action to do when time is out. /// - Returns: The task just created. @discardableResult public func `do`(mode: RunLoop.Mode = .default, - tag: String? = nil, + host: AnyObject? = nil, onElapse: @escaping (Task) -> Void) -> Task { - return RunLoopTask(schedule: self, mode: mode, tag: tag, onElapse: onElapse) + return RunLoopTask(schedule: self, mode: mode, host: host, onElapse: onElapse) } /// Schedules a task with this schedule. /// - /// This method will receive a `host` object as a parameter, - /// the returned task will not retain this object, instead, - /// it will observe this object, when this object is dealloced, - /// the task will not scheduled any more, something like parasitism. - /// - /// This feature is very useful when you want a scheduled task live and die - /// with a controller. - /// /// - Parameters: - /// - queue: The queue to which the task will be dispatched. - /// - tag: The tag to be associated to the task. + /// - mode: The mode in which to add the schedule. /// - host: The object to be hosted on. /// - onElapse: The action to do when time is out. /// - Returns: The task just created. @discardableResult public func `do`(mode: RunLoop.Mode = .default, - tag: String? = nil, + host: AnyObject? = nil, onElapse: @escaping () -> Void) -> Task { - return self.do(mode: mode, tag: tag) { (_) in + return self.do(mode: mode, host: host) { (_) in onElapse() } } @@ -62,7 +44,7 @@ private final class RunLoopTask: Task { var timer: Timer! - init(schedule: Schedule, mode: RunLoop.Mode, tag: String?, onElapse: @escaping (Task) -> Void) { + init(schedule: Schedule, mode: RunLoop.Mode, host: AnyObject?, onElapse: @escaping (Task) -> Void) { var this: Task? @@ -74,7 +56,7 @@ private final class RunLoopTask: Task { RunLoop.current.add(timer, forMode: mode) - super.init(schedule: schedule, queue: nil, tag: tag) { (task) in + super.init(schedule: schedule, queue: nil, host: host) { (task) in guard let task = task as? RunLoopTask else { return } task.timer.fireDate = Date() } diff --git a/Sources/Schedule/Schedule.swift b/Sources/Schedule/Schedule.swift index 8fd0f79..16640eb 100644 --- a/Sources/Schedule/Schedule.swift +++ b/Sources/Schedule/Schedule.swift @@ -26,28 +26,30 @@ public struct Schedule { /// /// - Parameters: /// - queue: The queue to which the task will be dispatched. - /// - tag: The tag to be associated to the task. + /// - host: The object to be hosted on. When this object is dealloced, + /// the task will not scheduled any more. /// - onElapse: The action to do when time is out. /// - Returns: The task just created. @discardableResult public func `do`(queue: DispatchQueue, - tag: String? = nil, + host: AnyObject? = nil, onElapse: @escaping (Task) -> Void) -> Task { - return Task(schedule: self, queue: queue, tag: tag, onElapse: onElapse) + return Task(schedule: self, queue: queue, host: host, onElapse: onElapse) } /// Schedules a task with this schedule. /// /// - Parameters: /// - queue: The queue to which the task will be dispatched. - /// - tag: The tag to be associated to the task. + /// - host: The object to be hosted on. When this object is dealloced, + /// the task will not scheduled any more. /// - onElapse: The action to do when time is out. /// - Returns: The task just created. @discardableResult public func `do`(queue: DispatchQueue, - tag: String? = nil, + host: AnyObject? = nil, onElapse: @escaping () -> Void) -> Task { - return self.do(queue: queue, tag: tag, onElapse: { (_) in onElapse() }) + return self.do(queue: queue, host: host, onElapse: { (_) in onElapse() }) } } diff --git a/Sources/Schedule/Task.swift b/Sources/Schedule/Task.swift index dad171f..e9b52ce 100644 --- a/Sources/Schedule/Task.swift +++ b/Sources/Schedule/Task.swift @@ -34,6 +34,10 @@ public class Task { private lazy var _tags: Set = [] private lazy var _countOfExecutions: Int = 0 + #if os(Linux) + private weak var _host: AnyObject? = TaskHub.shared + #endif + private lazy var _lifetime: Interval = Int.max.second private lazy var _lifetimeTimer: DispatchSourceTimer = { let timer = DispatchSource.makeTimerSource() @@ -47,8 +51,8 @@ public class Task { }() init(schedule: Schedule, - queue: DispatchQueue? = nil, - tag: String? = nil, + queue: DispatchQueue?, + host: AnyObject?, onElapse: @escaping (Task) -> Void) { _iterator = schedule.makeIterator() @@ -57,6 +61,14 @@ public class Task { _actions.append(onElapse) _timer.setEventHandler { [weak self] in + + #if os(Linux) + guard self?._host != nil else { + self?.cancel() + return + } + #endif + self?.elapse() } @@ -65,7 +77,18 @@ public class Task { _timer.schedule(after: interval) _timeline.estimatedNextExecution = Date().adding(interval) - TaskHub.shared.add(self, withTag: tag) + #if os(Linux) + _host = host + #else + if let host = host { + DeinitObserver.observe(host) { [weak self] in + self?.cancel() + } + } + #endif + + TaskHub.shared.add(self) + _timer.resume() } @@ -341,9 +364,6 @@ public class Task { public func removeTag(_ tag: String) { removeTags(tag) } -} - -extension Task { /// Suspends all tasks that have the tag. public static func suspend(byTag tag: String) {