axiom-timer-patterns-ref

安装量: 47
排名: #15590

安装

npx skills add https://github.com/charleswiltgen/axiom --skill axiom-timer-patterns-ref
Timer Patterns Reference
Complete API reference for iOS timer mechanisms. For decision trees and crash prevention, see
axiom-timer-patterns
.
Part 1: Timer API
Timer.scheduledTimer (Block-Based)
// Most common — block-based, auto-added to current RunLoop
let
timer
=
Timer
.
scheduledTimer
(
withTimeInterval
:
1.0
,
repeats
:
true
)
{
[
weak
self
]
_
in
self
?
.
updateProgress
(
)
}
Key detail
Added to .default RunLoop mode. Stops during scrolling. See Part 1 RunLoop modes table below. Timer.scheduledTimer (Selector-Based) // Objective-C style — RETAINS TARGET (leak risk) let timer = Timer . scheduledTimer ( timeInterval : 1.0 , target : self , // Timer retains self! selector :

selector

(
update
)
,
userInfo
:
nil
,
repeats
:
true
)
Danger
This API retains
target
. If
self
also holds the timer, you have a retain cycle. The block-based API with
[weak self]
is always safer.
Timer.init (Manual RunLoop Addition)
// Create timer without adding to RunLoop
let
timer
=
Timer
(
timeInterval
:
1.0
,
repeats
:
true
)
{
[
weak
self
]
_
in
self
?
.
updateProgress
(
)
}
// Add to specific RunLoop mode
RunLoop
.
current
.
add
(
timer
,
forMode
:
.
common
)
// Survives scrolling
timer.tolerance
timer
.
tolerance
=
0.1
// Allow 100ms flexibility for system coalescing
System batches timers with similar fire dates when tolerance is set. Minimum recommended: 10% of interval. Reduces CPU wakes and energy consumption.
RunLoop Modes
Mode
Constant
When Active
Timer Fires?
Default
.default
/
RunLoop.Mode.default
Normal user interaction
Yes
Tracking
.tracking
/
RunLoop.Mode.tracking
Scroll/drag gesture active
Only if added to
.common
Common
.common
/
RunLoop.Mode.common
Pseudo-mode (default + tracking)
Yes (always)
timer.invalidate()
timer
.
invalidate
(
)
// Stops timer, removes from RunLoop
// Timer is NOT reusable after invalidate — create a new one
timer
=
nil
// Release reference
Key detail
:
invalidate()
must be called from the same thread that created the timer (usually main thread).
timer.isValid
if
timer
.
isValid
{
// Timer is still active
}
Returns
false
after
invalidate()
or after a non-repeating timer fires.
Timer.publish (Combine)
Timer
.
publish
(
every
:
1.0
,
tolerance
:
0.1
,
on
:
.
main
,
in
:
.
common
)
.
autoconnect
(
)
.
sink
{
[
weak
self
]
_
in
self
?
.
updateProgress
(
)
}
.
store
(
in
:
&
cancellables
)
See Part 3 for full Combine timer details.
Part 2: DispatchSourceTimer API
Creation
// Create timer source on a specific queue
let
queue
=
DispatchQueue
(
label
:
"com.app.timer"
)
let
timer
=
DispatchSource
.
makeTimerSource
(
flags
:
[
]
,
queue
:
queue
)
flags
Usually empty (
[]
). Use
.strict
for precise timing (disables system coalescing, higher energy cost).
Schedule
// Relative deadline (monotonic clock)
timer
.
schedule
(
deadline
:
.
now
(
)
+
1.0
,
// First fire
repeating
:
.
seconds
(
1
)
,
// Interval
leeway
:
.
milliseconds
(
100
)
// Tolerance (like Timer.tolerance)
)
// Wall clock deadline (survives device sleep)
timer
.
schedule
(
wallDeadline
:
.
now
(
)
+
1.0
,
repeating
:
.
seconds
(
1
)
,
leeway
:
.
milliseconds
(
100
)
)
deadline vs wallDeadline
:
deadline
uses monotonic clock (pauses when device sleeps).
wallDeadline
uses wall clock (continues across sleep). Use
deadline
for most cases.
Event Handler
timer
.
setEventHandler
{
[
weak
self
]
in
self
?
.
performWork
(
)
}
Before cancel
Set handler to nil to break retain cycles:
timer
.
setEventHandler
(
handler
:
nil
)
timer
.
cancel
(
)
Lifecycle Methods
timer
.
activate
(
)
// Start — can only call ONCE (idle → running)
timer
.
suspend
(
)
// Pause (running → suspended)
timer
.
resume
(
)
// Unpause (suspended → running)
timer
.
cancel
(
)
// Stop permanently (must NOT be suspended)
State Machine Lifecycle
activate()
idle ──────────────► running
│ ▲
suspend() │ │ resume()
▼ │
suspended
resume() + cancel()
cancelled
Critical rules
:
activate()
can only be called once (idle → running)
cancel()
requires non-suspended state (resume first if suspended)
cancelled
is terminal — no further operations allowed
Dealloc requires non-suspended state (cancel first if needed)
Leeway (Tolerance)
// Leeway values
timer
.
schedule
(
deadline
:
.
now
(
)
,
repeating
:
1.0
,
leeway
:
.
milliseconds
(
100
)
)
timer
.
schedule
(
deadline
:
.
now
(
)
,
repeating
:
1.0
,
leeway
:
.
seconds
(
1
)
)
timer
.
schedule
(
deadline
:
.
now
(
)
,
repeating
:
1.0
,
leeway
:
.
never
)
// Strict — high energy
Leeway is the DispatchSourceTimer equivalent of
Timer.tolerance
. Allows system to coalesce timer firings for energy efficiency.
End-to-End Example
Complete DispatchSourceTimer lifecycle in one block:
let
queue
=
DispatchQueue
(
label
:
"com.app.polling"
)
let
timer
=
DispatchSource
.
makeTimerSource
(
queue
:
queue
)
timer
.
schedule
(
deadline
:
.
now
(
)
+
1.0
,
repeating
:
.
seconds
(
5
)
,
leeway
:
.
milliseconds
(
500
)
)
timer
.
setEventHandler
{
[
weak
self
]
in
self
?
.
fetchUpdates
(
)
}
timer
.
activate
(
)
// idle → running
// Later — pause:
timer
.
suspend
(
)
// running → suspended
// Later — resume:
timer
.
resume
(
)
// suspended → running
// Cleanup — MUST resume before cancel if suspended:
timer
.
setEventHandler
(
handler
:
nil
)
// Break retain cycles
timer
.
resume
(
)
// Ensure non-suspended state
timer
.
cancel
(
)
// running → cancelled (terminal)
For a safe wrapper that prevents all crash patterns, see
axiom-timer-patterns
Part 4: SafeDispatchTimer.
Part 3: Combine Timer
Timer.publish
import
Combine
// Create publisher — RunLoop mode matters here too
let
publisher
=
Timer
.
publish
(
every
:
1.0
,
// Interval
tolerance
:
0.1
,
// Optional tolerance
on
:
.
main
,
// RunLoop
in
:
.
common
// Mode — use .common to survive scrolling
)
.autoconnect()
// Starts immediately when first subscriber attaches
Timer
.
publish
(
every
:
1.0
,
on
:
.
main
,
in
:
.
common
)
.
autoconnect
(
)
.
sink
{
date
in
print
(
"Fired at
(
date
)
"
)
}
.
store
(
in
:
&
cancellables
)
.connect() (Manual Start)
// Manual control over when timer starts
let
timerPublisher
=
Timer
.
publish
(
every
:
1.0
,
on
:
.
main
,
in
:
.
common
)
let
cancellable
=
timerPublisher
.
sink
{
date
in
print
(
"Fired at
(
date
)
"
)
}
// Start later
let
connection
=
timerPublisher
.
connect
(
)
// Stop
connection
.
cancel
(
)
Cancellation
// Via AnyCancellable storage — cancelled when Set is cleared or object deallocs
private
var
cancellables
=
Set
<
AnyCancellable
>
(
)
// Manual cancellation
cancellables
.
removeAll
(
)
// Cancels all subscriptions
SwiftUI Integration
class
TimerViewModel
:
ObservableObject
{
@Published
var
elapsed
:
Int
=
0
private
var
cancellables
=
Set
<
AnyCancellable
>
(
)
func
start
(
)
{
Timer
.
publish
(
every
:
1.0
,
tolerance
:
0.1
,
on
:
.
main
,
in
:
.
common
)
.
autoconnect
(
)
.
sink
{
[
weak
self
]
_
in
self
?
.
elapsed
+=
1
}
.
store
(
in
:
&
cancellables
)
}
func
stop
(
)
{
cancellables
.
removeAll
(
)
}
}
Part 4: AsyncTimerSequence (Swift Concurrency)
ContinuousClock.timer
// Monotonic clock — does NOT pause when app suspends
for
await
_
in
ContinuousClock
(
)
.
timer
(
interval
:
.
seconds
(
1
)
)
{
await
updateData
(
)
}
// Loop exits when task is cancelled
SuspendingClock.timer
// Suspending clock — pauses when app suspends
for
await
_
in
SuspendingClock
(
)
.
timer
(
interval
:
.
seconds
(
1
)
)
{
await
processItem
(
)
}
ContinuousClock vs SuspendingClock
:
ContinuousClock
Time keeps advancing during app suspension. Use for absolute timing.
SuspendingClock
Time pauses when app suspends. Use for "user-perceived" timing. Task Cancellation // Timer automatically stops when task is cancelled let timerTask = Task { for await _ in ContinuousClock ( ) . timer ( interval : . seconds ( 1 ) ) { await fetchLatestData ( ) } } // Later: cancel the timer timerTask . cancel ( ) Background Polling with Structured Concurrency func startPolling ( ) async { do { for try await _ in ContinuousClock ( ) . timer ( interval : . seconds ( 30 ) ) { try Task . checkCancellation ( ) let data = try await api . fetchUpdates ( ) await MainActor . run { updateUI ( with : data ) } } } catch is CancellationError { // Clean exit } catch { // Handle fetch error } } Part 5: Task.sleep Alternatives One-Shot Delay // Simple delay — NOT a timer try await Task . sleep ( for : . seconds ( 1 ) ) // Deadline-based try await Task . sleep ( until : . now + . seconds ( 1 ) , clock : . continuous ) When to Use Sleep vs Timer Need Use One-shot delay before action Task.sleep(for:) Repeating action ContinuousClock().timer(interval:) Delay with cancellation Task.sleep(for:) in a Task Retry with backoff Task.sleep(for:) in a loop Retry with Exponential Backoff func fetchWithRetry ( maxAttempts : Int = 3 ) async throws -> Data { var delay : Duration = . seconds ( 1 ) for attempt in 1 ... maxAttempts { do { return try await api . fetch ( ) } catch where attempt < maxAttempts { try await Task . sleep ( for : delay ) delay *= 2 // Exponential backoff } } throw FetchError . maxRetriesExceeded } Part 6: LLDB Timer Inspection Timer (NSTimer) Commands

Check if timer is still valid

po timer.isValid

See next fire date

po timer.fireDate

See timer interval

po timer.timeInterval

Force RunLoop iteration (may trigger timer)

expression -l objc -- (void)[[NSRunLoop mainRunLoop] run] DispatchSourceTimer Commands

Inspect dispatch source

po timer

Break on dispatch source cancel (all sources)

breakpoint set -n dispatch_source_cancel

Break on EXC_BAD_INSTRUCTION to catch timer crashes

(Xcode does this automatically for Swift runtime errors)

Check if a DispatchSource is cancelled

expression -l objc -- (long)dispatch_source_testcancel((void*)timer) General Timer Debugging

List all timers on the main RunLoop

expression -l objc -- (void)CFRunLoopGetMain()

Break when any Timer fires

breakpoint set -S "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:" Part 7: Platform Availability Matrix API iOS macOS watchOS tvOS Timer 2.0+ 10.0+ 2.0+ 9.0+ DispatchSourceTimer 8.0+ (GCD) 10.10+ 2.0+ 9.0+ Timer.publish (Combine) 13.0+ 10.15+ 6.0+ 13.0+ AsyncTimerSequence 16.0+ 13.0+ 9.0+ 16.0+ Task.sleep 13.0+ 10.15+ 6.0+ 13.0+

返回排行榜