Swift:如何用Timer确保API获取的current_price每分钟稳定更新?
Swift定时获取加密货币价格异常修复方案
问题现象
点击trackBtn触发价格获取逻辑,预期每分钟通过API拉取最新加密货币价格,但实际current_price仅首次更新后,后续每隔11-12分钟才刷新,定时器已配置为60秒触发一次。
关联代码
模型类
struct CryptoPrices: Decodable { var current_price: Double? mutating func update(current_price: Double) -> CryptoPrices { self.current_price = current_price return self } }
CryptoManager业务类
import Combine import Alamofire class CryptoManager { static let shared = CryptoManager() var timer: Timer? private var cancellables: Set<AnyCancellable> = [] var currentCryptoPrice: CryptoPrices? func getCryptoPrices(symbol: String) -> AnyPublisher<CryptoPrices, Error> { let url = "https://example.com/api/v3/coins/markets?vs_currency=usd&ids=\(symbol)&order=market_cap_desc&per_page=100&page=1&sparkline=false&locale=en" var urlRequest = URLRequest(url: URL(string: url)!) urlRequest.httpMethod = HTTPMethod.get.rawValue urlRequest.cachePolicy = .reloadIgnoringLocalCacheData return AF.request(urlRequest) .publishDecodable(type: [CryptoPrices].self, decoder: JSONDecoder()) .tryMap { response in guard let cryptoPricesArray = response.value else { throw AFError.responseValidationFailed(reason: .dataFileNil) } guard let cryptoPrice = cryptoPricesArray.first else { throw NSError(domain: "Invalid response format", code: 0, userInfo: nil) } if let updatedPrice = cryptoPrice.current_price { return CryptoPrices(current_price: updatedPrice) } else { return CryptoPrices(current_price: nil) } } .mapError { $0 as Error } .eraseToAnyPublisher() } func startFetchingWithTimer(symbol: String) -> AnyPublisher<CryptoPrices, Error> { let timerPublisher = Timer.publish(every: 60, on: .main, in: .common) .autoconnect() .map { _ in () } return timerPublisher .flatMap { _ in self.fetchCryptoPrices(symbol: symbol) .handleEvents(receiveOutput: { cryptoPrices in print("Received crypto prices: \(cryptoPrices)") }) } .eraseToAnyPublisher() } private func fetchCryptoPrices(symbol: String) -> AnyPublisher<CryptoPrices, Error> { getCryptoPrices(symbol: symbol) .handleEvents(receiveCompletion: { completion in switch completion { case .failure(let error): print("Error fetching crypto prices: \(error)") case .finished: break } }) .eraseToAnyPublisher() } private var subscriptions = Set<AnyCancellable>() }
控制器代码
import Combine @IBOutlet weak var minimumRate: UITextField! @IBOutlet weak var maximumRate: UITextField! private let cryptoManager: CryptoManager private var userNotificationCenter: UNUserNotificationCenter var symbol: String! func startMonitoring(symbol: String, minRate: Double, maxRate: Double) { guard let minRateText = minimumRate.text, let maxRateText = maximumRate.text else { return } guard let minRate = Double(minRateText), let maxRate = Double(maxRateText) else { return } print("Minimum Rate: \(minRate), Maximum Rate: \(maxRate)") self.cryptoManager.startFetchingWithTimer(symbol: symbol) .sink(receiveCompletion: { completion in switch completion { case .failure(let error): print("Error fetching price: \(error)") case .finished: break } }, receiveValue: { cryptoPrices in if let currentPrice = cryptoPrices.current_price { print("Received price: \(currentPrice)") if currentPrice <= minRate { print("Min rate:", minRate) print(currentPrice, "after checking the price has dropped") self.sendNotification(symbol: symbol, price: currentPrice) } else if currentPrice >= maxRate { print("Max rate:", maxRate) print(currentPrice, "after checking the price has increased") self.sendNotification(symbol: symbol, price: currentPrice) } else { print("Price is the same") } } }) .store(in: &self.subscriptions) }
核心修复点
1. 限制并发请求数量
默认flatMap会无限制创建新的请求订阅,如果API响应耗时超过60秒,会导致定时器事件积压,后续请求被延迟处理。修改startFetchingWithTimer方法,添加maxPublishers: .max(1)确保同一时间只有一个请求执行:
func startFetchingWithTimer(symbol: String) -> AnyPublisher<CryptoPrices, Error> { let timerPublisher = Timer.publish(every: 60, on: .main, in: .common) .autoconnect() .map { _ in () } return timerPublisher // 限制同时仅一个请求在执行 .flatMap(maxPublishers: .max(1)) { _ in self.fetchCryptoPrices(symbol: symbol) .handleEvents(receiveOutput: { cryptoPrices in print("Received crypto prices: \(cryptoPrices)") }) } .eraseToAnyPublisher() }
2. 强制禁用服务器端缓存
虽然设置了URLRequest.CachePolicy.reloadIgnoringLocalCacheData,但服务器可能仍返回缓存数据。在getCryptoPrices方法中添加请求头,强制服务器返回最新数据:
var urlRequest = URLRequest(url: URL(string: url)!) urlRequest.httpMethod = HTTPMethod.get.rawValue urlRequest.cachePolicy = .reloadIgnoringLocalCacheData // 添加缓存控制头 urlRequest.setValue("no-cache, no-store, must-revalidate", forHTTPHeaderField: "Cache-Control") urlRequest.setValue("no-cache", forHTTPHeaderField: "Pragma") urlRequest.setValue("0", forHTTPHeaderField: "Expires")
3. 统一订阅管理
CryptoManager中存在重复的订阅存储变量cancellables和subscriptions,保留一个即可,避免订阅管理混乱:
// 删除其中一个,保留统一的订阅容器 private var cancellables: Set<AnyCancellable> = []
4. 确保控制器订阅不被释放
确认控制器的subscriptions是类级别的实例属性,如果是局部变量会在方法执行完毕后被释放,导致订阅失效:
// 在控制器类顶部声明 private var subscriptions = Set<AnyCancellable>()
验证方法
修改后运行应用,查看控制台打印的Received price:日志,确认每分钟都会输出最新价格;同时检查API请求的响应头,确认Cache-Control已生效,服务器未返回缓存数据。
内容的提问来源于stack exchange,提问作者Rustin Wilde
相关产品推荐
相关产品推荐

