forked from swiftwasm/JavaScriptKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJSTimer.swift
More file actions
96 lines (89 loc) · 4.06 KB
/
JSTimer.swift
File metadata and controls
96 lines (89 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/// This timer is an abstraction over [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval)
/// / [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval) and
/// [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout)
/// / [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout)
/// JavaScript functions. It intentionally doesn't match the JavaScript API, as a special care is
/// needed to hold a reference to the timer closure and to call `JSClosure.release()` on it when the
/// timer is deallocated. As a user, you have to hold a reference to a `JSTimer` instance for it to stay
/// valid. The `JSTimer` API is also intentionally trivial, the timer is started right away, and the
/// only way to invalidate the timer is to bring the reference count of the `JSTimer` instance to zero.
/// For invalidation you should either store the timer in an optional property and assign `nil` to it,
/// or deallocate the object that owns the timer.
public final class JSTimer {
enum ClosureStorage {
case oneshot(JSOneshotClosure)
case repeating(JSClosure)
var jsValue: JSValue {
switch self {
case .oneshot(let closure): return closure.jsValue
case .repeating(let closure): return closure.jsValue
}
}
func release() {
switch self {
case .oneshot(let closure):
closure.release()
case .repeating(let closure):
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
closure.release()
#else
break // no-op
#endif
}
}
}
/// Indicates whether this timer instance calls its callback repeatedly at a given delay.
public let isRepeating: Bool
private let closure: ClosureStorage
/** Node.js and browser APIs are slightly different. `setTimeout`/`setInterval` return an object
in Node.js, while browsers return a number. Fortunately, clearTimeout and clearInterval take
corresponding types as their arguments, and we can store either as JSValue, so we can treat both
cases uniformly.
*/
private let value: JSValue
private let global = JSObject.global
/**
Creates a new timer instance that calls `setInterval` or `setTimeout` JavaScript functions for you
under the hood.
- Parameters:
- millisecondsDelay: the amount of milliseconds before the `callback` closure is executed.
- isRepeating: when `true` the `callback` closure is executed repeatedly at given
`millisecondsDelay` intervals indefinitely until the timer is deallocated.
- callback: the closure to be executed after a given `millisecondsDelay` interval.
*/
public init(millisecondsDelay: Double, isRepeating: Bool = false, callback: @escaping () -> Void) {
if isRepeating {
closure = .repeating(
JSClosure { _ in
callback()
return .undefined
}
)
} else {
closure = .oneshot(
JSOneshotClosure { _ in
callback()
return .undefined
}
)
}
self.isRepeating = isRepeating
if isRepeating {
value = global.setInterval.object!(arguments: [closure.jsValue, millisecondsDelay.jsValue])
} else {
value = global.setTimeout.object!(arguments: [closure.jsValue, millisecondsDelay.jsValue])
}
}
/** Makes a corresponding `clearTimeout` or `clearInterval` call, depending on whether this timer
instance is repeating. The `closure` instance is released manually here, as it is required for
bridged closure instances.
*/
deinit {
if isRepeating {
global.clearInterval.object!(value)
} else {
global.clearTimeout.object!(value)
}
closure.release()
}
}