Swift 4中如何管理定时事件?含后台执行及审核疑问
Hey there, let's break down your questions about managing timed events in Swift 4—especially the tricky part of getting them to work within Apple's background execution constraints. I'll cover everything from building a centralized timer manager to legal background workarounds.
一、构建定时任务管理中心
First off, your goal of centralizing timer tasks is totally achievable. Let's address your initial questions:
Can NSNotification be used for this?
Yes, but it's not the most elegant approach. You could post a notification when a timer fires, then listen for that notification to trigger your task. However, this creates tight coupling between components and makes it harder to track active timers. A better approach is to use closure-based callbacks with a centralized manager, or leverage GCD timers instead of Timer (more on that later).
Recommended implementation approach
I'd suggest building a singleton TimerManager class to handle all timer creation, tracking, and cleanup. This keeps your code organized and makes it easy to manage all timers in one place. Below is a practical example using GCD timers (more reliable than Timer since they don't depend on the main runloop).
二、后台执行的合法解决方案 (Your two core questions)
1) Is there a Timer-like solution that works in the background?
Apple's background restrictions are strict—regular Timer instances pause when your app is in the background because the main runloop goes dormant. But there are legal ways to run small background tasks:
Background Task Assertions: Use
UIApplication.shared.beginBackgroundTask()to request a short window of background execution (usually 30 seconds max) to finish small tasks like your query. This is perfect for one-off or periodic small jobs.
Example code:var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid func runBackgroundQuery() { // Request background execution time backgroundTaskID = UIApplication.shared.beginBackgroundTask { [weak self] in // Handle timeout: end the task if we run out of time UIApplication.shared.endBackgroundTask(self?.backgroundTaskID ?? .invalid) self?.backgroundTaskID = .invalid } // Run your query on a background queue DispatchQueue.global().async { [weak self] in // Execute your small query logic here // ... // Clean up: end the background task once done if let taskID = self?.backgroundTaskID, taskID != .invalid { UIApplication.shared.endBackgroundTask(taskID) self?.backgroundTaskID = .invalid } } }GCD Dispatch Source Timers: Unlike
Timer, GCD timers don't rely on the main runloop. They'll work in the background as long as your app is still active (not suspended). If your app is suspended, they'll pause, but you can combine this with background task assertions to handle execution when the timer fires.
Example setup:var gcdTimer: DispatchSourceTimer? func setupRepeatingTimer(interval: TimeInterval) { gcdTimer = DispatchSource.makeTimerSource(queue: DispatchQueue.global()) gcdTimer?.schedule(deadline: .now(), repeating: interval) gcdTimer?.setEventHandler { [weak self] in self?.runBackgroundQuery() // Trigger your background task } gcdTimer?.resume() } func stopTimer() { gcdTimer?.cancel() gcdTimer = nil }Official Background Modes: If your use case fits, enable Apple-approved background modes like:
- Background Fetch: Apple will periodically wake your app to let you fetch new data (no need to manage timers yourself).
- Remote Notifications: Send a silent push notification to trigger background task execution.
These are fully supported and designed for exactly this kind of background work.
2) Will workarounds for Timer background restrictions pass App Review?
Never try to bypass Apple's background rules—using private APIs, fake location updates, or other hacks will get your app rejected immediately. The solutions I listed above are all Apple-approved and compliant with the App Store Review Guidelines. As long as you use them for their intended purpose (e.g., background fetch for content updates, not keeping your app running continuously), you won't have issues with review.
三、Centralized Timer Manager Example
Here's a ready-to-use singleton manager to track all your timers, with support for callbacks and easy cleanup:
class TimerManager { static let shared = TimerManager() private var activeTimers: [String: DispatchSourceTimer] = [:] // Private init to enforce singleton pattern private init() {} /// Add a timed task /// - Parameters: /// - taskID: Unique ID to identify the task /// - interval: Time between executions /// - repeats: Whether the task should run repeatedly /// - taskHandler: Closure to execute when the timer fires func addTimer(taskID: String, interval: TimeInterval, repeats: Bool, taskHandler: @escaping () -> Void) { // Remove existing timer with the same ID if it exists if let existingTimer = activeTimers[taskID] { existingTimer.cancel() activeTimers.removeValue(forKey: taskID) } let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global()) if repeats { timer.schedule(deadline: .now(), repeating: interval) } else { timer.schedule(deadline: .now() + interval) } timer.setEventHandler { [weak self] in taskHandler() // Clean up one-time timers after execution if !repeats { self?.activeTimers.removeValue(forKey: taskID) } } timer.resume() activeTimers[taskID] = timer } /// Stop a specific timed task func stopTimer(taskID: String) { if let timer = activeTimers[taskID] { timer.cancel() activeTimers.removeValue(forKey: taskID) } } /// Stop all active timers func stopAllTimers() { activeTimers.values.forEach { $0.cancel() } activeTimers.removeAll() } } // Usage example TimerManager.shared.addTimer( taskID: "iosActionsRefresh", interval: 60, repeats: true, taskHandler: { // Run your refresh logic DispatchQueue.main.async { self.refreshIosActions() // Switch to main queue if updating UI } } ) // To stop the timer later TimerManager.shared.stopTimer(taskID: "iosActionsRefresh")
四、NSNotification Implementation (If You Prefer)
If you still want to use NSNotification, here's how to set it up:
// Post notification when timer fires NotificationCenter.default.post( name: NSNotification.Name(rawValue: "IosActionsRefresh"), object: nil ) // Listen for the notification in your class override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver( self, selector: #selector(refreshIosActions), name: NSNotification.Name(rawValue: "IosActionsRefresh"), object: nil ) } // Clean up observer when done deinit { NotificationCenter.default.removeObserver(self) }
Just keep in mind this approach is less flexible and makes it harder to track which timers are active compared to the centralized manager.
内容的提问来源于stack exchange,提问作者john

