辅助工具版本检查、后台低频探测、进程页 2 秒刷新、记住菜单栏图标顺序
This commit is contained in:
1 parent
b901bb8b67
commit
f9f4201654
13 files changed
+146
-37
No files matched your search
@@ -10,6 +10,13 @@
|
||||
#### 新增
|
||||
|
||||
- 在线升级:启动时和之后每天检查官网版本清单,发现新版本时弹出版本号与更新摘要,可一键安装、以后再说或跳过此版本;下载后依次核对 sha256、包名与版本、开发者签名团队和 Apple 公证,任何一项不通过都放弃安装;旧版先备份再原地替换,失败自动还原,装完自动重启到新版。「设置 · 关于」与应用菜单里可以手动检查更新。
|
||||
- 辅助工具版本检查:启动时核对正在运行的辅助工具版本,比应用旧时在风扇、合盖运行和辅助工具设置里提示“重新安装”,侧边栏对应项目显示提醒圆点;有新版本时“关于”旁同样显示圆点。
|
||||
|
||||
#### 调整
|
||||
|
||||
- 连接探测更省电:打开网络详情时按设置的间隔探测;详情关闭、只在菜单栏显示网速时改为每 10 秒低频探测,并允许系统合并唤醒,也可以在设置里关闭后台探测。
|
||||
- 进程页每 2 秒刷新一次(读取全系统进程需要启动 ps),其他页面仍为每秒。
|
||||
- 菜单栏图标用 ⌘ 拖动调整的顺序会被记住,重启应用或切换显示项目后保持不变。
|
||||
|
||||
## 0.2.0 · 2026-09-13
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
model.quit = { NSApp.terminate(nil) }
|
||||
model.helper.refreshStatus()
|
||||
Task { await model.helper.verifyVersion() }
|
||||
model.helper.onReconnect = { [weak self] in
|
||||
Task { [weak self] in
|
||||
await self?.model.fans.reapply()
|
||||
@@ -46,7 +47,7 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
observeModel()
|
||||
observeProbeSettings()
|
||||
applyAppearance()
|
||||
model.network.updateProbing(networkShown: model.networkShown)
|
||||
updateNetworkVisibility()
|
||||
Task { await model.geo.updateIfNeeded() }
|
||||
startUpdateChecks()
|
||||
|
||||
@@ -143,9 +144,9 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
_ = model.settings.hiddenPopoverSections
|
||||
_ = model.keepAwake.isActive
|
||||
_ = model.settings.appearance
|
||||
_ = model.networkShown
|
||||
_ = model.isNetworkDetailVisible
|
||||
_ = model.settings.probeEnabled
|
||||
_ = model.settings.probeInBackground
|
||||
} onChange: { [weak self] in
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
@@ -153,8 +154,7 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
self.applyAppearance()
|
||||
self.menuBar.update()
|
||||
self.menuBar.refreshPopoverHeight()
|
||||
self.model.network.updateProbing(networkShown: self.model.networkShown)
|
||||
self.model.network.setDetailVisible(self.model.isNetworkDetailVisible)
|
||||
self.updateNetworkVisibility()
|
||||
self.observeModel()
|
||||
}
|
||||
}
|
||||
@@ -180,6 +180,11 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateNetworkVisibility() {
|
||||
model.network.setVisibility(inMenuBar: model.settings.menuBarItems.contains(.network),
|
||||
detailVisible: model.isNetworkDetailVisible)
|
||||
}
|
||||
|
||||
private func applyAppearance() {
|
||||
switch model.settings.appearance {
|
||||
case .system: NSApp.appearance = nil
|
||||
@@ -202,7 +207,7 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
MainActor.assumeIsolated {
|
||||
guard let self else { return }
|
||||
self.menuBar.dismissPopovers()
|
||||
self.model.network.setPaused(true, networkShown: self.model.networkShown)
|
||||
self.model.network.setPaused(true)
|
||||
Task { await self.model.hub.setPaused(true) }
|
||||
}
|
||||
})
|
||||
@@ -211,7 +216,7 @@ public final class AppController: NSObject, NSApplicationDelegate {
|
||||
workspaceObservers.append(center.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
|
||||
MainActor.assumeIsolated {
|
||||
guard let self else { return }
|
||||
self.model.network.setPaused(false, networkShown: self.model.networkShown)
|
||||
self.model.network.setPaused(false)
|
||||
Task {
|
||||
await self.model.hub.setPaused(false)
|
||||
await self.model.fans.reapply()
|
||||
|
||||
@@ -33,7 +33,14 @@ final class MenuBarController: NSObject {
|
||||
|
||||
private func rebuild(_ signature: [MenuBarItem?]) {
|
||||
dismissPopovers()
|
||||
// 移除图标时系统会清掉它记住的位置;先记下用户 ⌘ 拖动后的位置,重建后放回
|
||||
let defaults = UserDefaults.standard
|
||||
let positions = items.compactMap { entry -> (String, Any)? in
|
||||
let key = Self.positionKey(entry.key)
|
||||
return defaults.object(forKey: key).map { (key, $0) }
|
||||
}
|
||||
for entry in items { NSStatusBar.system.removeStatusItem(entry.statusItem) }
|
||||
for (key, value) in positions { defaults.set(value, forKey: key) }
|
||||
items = []
|
||||
panels = panels.filter { key, _ in signature.contains(key) }
|
||||
layoutSignature = signature
|
||||
@@ -41,6 +48,8 @@ final class MenuBarController: NSObject {
|
||||
// 新建的状态栏图标出现在已有图标左侧,倒序创建才能保持从左到右的顺序
|
||||
for key in signature.reversed() {
|
||||
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
// 按项目命名后,系统会记住每个图标被 ⌘ 拖动后的位置,下次启动保持原顺序
|
||||
statusItem.autosaveName = Self.autosaveName(key)
|
||||
if let button = statusItem.button {
|
||||
button.target = self
|
||||
button.action = #selector(statusItemClicked(_:))
|
||||
@@ -51,6 +60,14 @@ final class MenuBarController: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private static func autosaveName(_ key: MenuBarItem?) -> String {
|
||||
"OpenStats.\(key?.rawValue ?? "combined")"
|
||||
}
|
||||
|
||||
private static func positionKey(_ key: MenuBarItem?) -> String {
|
||||
"NSStatusItem Preferred Position \(autosaveName(key))"
|
||||
}
|
||||
|
||||
func refreshImages() {
|
||||
let settings = model.settings
|
||||
let reading = MenuBarReading(model: model)
|
||||
|
||||
@@ -216,6 +216,8 @@ struct SidebarButton: View {
|
||||
let title: String
|
||||
let symbol: String
|
||||
let isSelected: Bool
|
||||
/// 需要注意时在右侧显示一个圆点(有新版本、辅助工具需要重新安装)
|
||||
var badge: Tone?
|
||||
let action: () -> Void
|
||||
@State private var hovering = false
|
||||
|
||||
@@ -227,6 +229,10 @@ struct SidebarButton: View {
|
||||
.frame(width: DS.Size.iconStandalone)
|
||||
Text(title).dsFont(.sm, weight: isSelected ? .semibold : .regular)
|
||||
Spacer()
|
||||
if let badge {
|
||||
Circle().fill(badge.color).frame(width: DS.Space.s2, height: DS.Space.s2)
|
||||
.accessibilityLabel("需要注意")
|
||||
}
|
||||
}
|
||||
.foregroundStyle(isSelected ? DS.Palette.primary : hovering ? DS.Palette.textPrimary : DS.Palette.textSecondary)
|
||||
.padding(.leading, DS.Space.s3)
|
||||
|
||||
@@ -117,7 +117,7 @@ private struct LidCard: View {
|
||||
.disabled(!model.helper.isReady)
|
||||
}
|
||||
|
||||
if !model.helper.isReady {
|
||||
if model.helper.needsAttention {
|
||||
HelperRequiredBanner(text: "合盖运行需要修改系统睡眠设置,需安装辅助工具(管理员授权一次)。")
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,14 @@ private struct MainSidebar: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
private func badge(for tab: PanelTab) -> Tone? {
|
||||
switch tab {
|
||||
case .settingsAbout: model.updates.release != nil ? .primary : nil
|
||||
case .settingsHelper: model.helper.isReady && model.helper.isOutdated ? .warning : nil
|
||||
default: nil
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func group(_ title: String, _ tabs: [PanelTab]) -> some View {
|
||||
Text(title)
|
||||
@@ -117,7 +125,7 @@ private struct MainSidebar: View {
|
||||
.padding(.horizontal, DS.Space.s2)
|
||||
.padding(.top, DS.Space.s2)
|
||||
ForEach(tabs) { tab in
|
||||
SidebarButton(title: tab.title, symbol: tab.symbol, isSelected: model.settings.panelTab == tab) {
|
||||
SidebarButton(title: tab.title, symbol: tab.symbol, isSelected: model.settings.panelTab == tab, badge: badge(for: tab)) {
|
||||
model.settings.panelTab = tab
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ private struct FanCard: View {
|
||||
CustomLevelControl(fans: fans)
|
||||
}
|
||||
|
||||
if !model.helper.isReady {
|
||||
if model.helper.needsAttention {
|
||||
HelperRequiredBanner(text: "调节风扇需要安装辅助工具,仅需管理员授权一次。")
|
||||
}
|
||||
|
||||
@@ -202,6 +202,10 @@ struct HelperRequiredBanner: View {
|
||||
case .requiresApproval:
|
||||
Button("去批准") { model.helper.openLoginItemsSettings() }
|
||||
.buttonStyle(DSButtonStyle(kind: .primary))
|
||||
case .enabled where model.helper.isOutdated:
|
||||
Button("重新安装") { Task { await model.helper.reinstall() } }
|
||||
.buttonStyle(DSButtonStyle(kind: .primary))
|
||||
.disabled(model.helper.isWorking)
|
||||
case .enabled, .unavailable:
|
||||
EmptyView()
|
||||
}
|
||||
@@ -212,6 +216,7 @@ struct HelperRequiredBanner: View {
|
||||
switch model.helper.status {
|
||||
case .requiresApproval: "请在“系统设置 › 通用 › 登录项”中允许 OpenStats 的后台项目。"
|
||||
case .unavailable(let reason): reason
|
||||
case .enabled where model.helper.isOutdated: HelperClient.outdatedMessage
|
||||
default: text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,12 +323,12 @@ struct NetworkSettings: View {
|
||||
|
||||
SettingsGroup(caption: "连接探测") {
|
||||
GroupRow(showsDivider: false) {
|
||||
SettingRow(title: "定时探测网络", subtitle: "用 ping 测量延迟与丢包,在网络详情里以格子显示;只在菜单栏显示网络项或打开网络详情时运行") {
|
||||
SettingRow(title: "定时探测网络", subtitle: "用 ping 测量延迟与丢包,在网络详情里以格子显示") {
|
||||
DSToggle(isOn: $settings.probeEnabled, label: "定时探测网络")
|
||||
}
|
||||
}
|
||||
GroupRow {
|
||||
SettingRow(title: "探测间隔") {
|
||||
SettingRow(title: "探测间隔", subtitle: "打开网络详情时使用") {
|
||||
SegmentedControl(selection: $settings.probeSeconds,
|
||||
options: AppSettings.probeOptions.map { ($0, "\($0) 秒") })
|
||||
.frame(width: DS.Size.sidebarWidth)
|
||||
@@ -348,6 +348,13 @@ struct NetworkSettings: View {
|
||||
}
|
||||
}
|
||||
.disabled(!settings.probeEnabled)
|
||||
GroupRow {
|
||||
SettingRow(title: "后台低频探测",
|
||||
subtitle: "详情关闭时,菜单栏显示网络项期间每 \(NetworkController.backgroundProbeSeconds) 秒探测一次,打开详情就能看到最近的连接情况;关闭后只在打开详情时探测,更省电") {
|
||||
DSToggle(isOn: $settings.probeInBackground, label: "后台低频探测")
|
||||
}
|
||||
}
|
||||
.disabled(!settings.probeEnabled)
|
||||
}
|
||||
|
||||
SettingsGroup(caption: "公网 IP") {
|
||||
@@ -537,6 +544,13 @@ struct HelperSettings: View {
|
||||
if case .unavailable(let reason) = helper.status {
|
||||
InfoBanner(icon: "exclamationmark.triangle.fill", text: reason, tone: .error)
|
||||
}
|
||||
if helper.isReady && helper.isOutdated {
|
||||
InfoBanner(icon: "arrow.triangle.2.circlepath", text: HelperClient.outdatedMessage, tone: .warning) {
|
||||
Button("重新安装") { Task { await helper.reinstall() } }
|
||||
.buttonStyle(DSButtonStyle(kind: .primary))
|
||||
.disabled(helper.isWorking)
|
||||
}
|
||||
}
|
||||
if let error = helper.lastError {
|
||||
InfoBanner(icon: "exclamationmark.triangle.fill", text: error, tone: .error)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ enum SnapshotRenderer {
|
||||
settings.probeSeconds = 1
|
||||
let model = AppModel(settings: settings)
|
||||
model.isMainWindowVisible = true
|
||||
model.network.updateProbing(networkShown: true)
|
||||
model.network.setDetailVisible(true)
|
||||
model.network.setVisibility(inMenuBar: true, detailVisible: true)
|
||||
|
||||
// 采集约 12 秒的真实数据,让历史曲线有内容
|
||||
var demand = MetricsDemand()
|
||||
@@ -34,8 +33,7 @@ enum SnapshotRenderer {
|
||||
await model.hub.start { snapshot in model.handle(snapshot) }
|
||||
try? await Task.sleep(for: .seconds(12))
|
||||
await model.hub.stop()
|
||||
model.network.setDetailVisible(false)
|
||||
model.network.updateProbing(networkShown: false)
|
||||
model.network.setVisibility(inMenuBar: false, detailVisible: false)
|
||||
model.network.maskForSnapshot()
|
||||
// 清理页展示真实扫描结果(只读,不删除任何文件)
|
||||
model.cleaner.scan()
|
||||
|
||||
@@ -55,7 +55,9 @@ public final class AppModel {
|
||||
let menu = settings.menuBarItems
|
||||
let thermalPopover = popover == .temperature || popover == .fan
|
||||
|
||||
demand.interval = window || popover != nil ? .seconds(1) : .seconds(settings.refreshSeconds)
|
||||
// 进程页要读全系统进程(启动 ps),每 2 秒刷新一次足够,也更省电
|
||||
demand.interval = window && tab == .processes && popover == nil ? .seconds(2)
|
||||
: window || popover != nil ? .seconds(1) : .seconds(settings.refreshSeconds)
|
||||
demand.memory = true
|
||||
demand.network = true
|
||||
let showing = { (page: PanelTab, item: MenuBarItem) in (window && tab == page) || popover == item }
|
||||
@@ -76,11 +78,6 @@ public final class AppModel {
|
||||
return demand
|
||||
}
|
||||
|
||||
/// 连接探测在菜单栏显示网络项或网络详情打开时运行
|
||||
var networkShown: Bool {
|
||||
settings.menuBarItems.contains(.network) || isNetworkDetailVisible
|
||||
}
|
||||
|
||||
/// 网络详情(接口、公网 IP、进程流量)正在显示
|
||||
var isNetworkDetailVisible: Bool {
|
||||
openPopover == .network || (isMainWindowVisible && settings.panelTab == .network)
|
||||
|
||||
@@ -349,6 +349,10 @@ public final class AppSettings {
|
||||
public var probeEnabled: Bool {
|
||||
didSet { defaults.set(probeEnabled, forKey: Keys.probeEnabled) }
|
||||
}
|
||||
/// 网络详情关闭时,菜单栏显示网络项期间继续低频探测
|
||||
public var probeInBackground: Bool {
|
||||
didSet { defaults.set(probeInBackground, forKey: Keys.probeInBackground) }
|
||||
}
|
||||
public var probeSeconds: Int {
|
||||
didSet { defaults.set(probeSeconds, forKey: Keys.probeSeconds) }
|
||||
}
|
||||
@@ -406,6 +410,7 @@ public final class AppSettings {
|
||||
menuBarLayout = defaults.string(forKey: Keys.menuBarLayout).flatMap(MenuBarLayout.init(rawValue:)) ?? .separate
|
||||
hiddenPopoverSections = Set(defaults.stringArray(forKey: Keys.hiddenPopoverSections)?.compactMap(PopoverSection.init(rawValue:)) ?? [])
|
||||
probeEnabled = defaults.object(forKey: Keys.probeEnabled) as? Bool ?? true
|
||||
probeInBackground = defaults.object(forKey: Keys.probeInBackground) as? Bool ?? true
|
||||
probeSeconds = Self.probeOptions.contains(defaults.integer(forKey: Keys.probeSeconds))
|
||||
? defaults.integer(forKey: Keys.probeSeconds) : 2
|
||||
probeTarget = defaults.string(forKey: Keys.probeTarget).flatMap(ProbeTarget.init(rawValue:)) ?? .cloudflare
|
||||
@@ -458,6 +463,7 @@ public final class AppSettings {
|
||||
static let hiddenPopoverSections = "hiddenPopoverSections"
|
||||
static let probeEnabled = "probeEnabled"
|
||||
static let probeSeconds = "probeSeconds"
|
||||
static let probeInBackground = "probeInBackground"
|
||||
static let probeTarget = "probeTarget"
|
||||
static let publicIPLookup = "publicIPLookup"
|
||||
static let geoIncludeCity = "geoIncludeCity"
|
||||
|
||||
@@ -26,6 +26,8 @@ public final class HelperClient {
|
||||
public private(set) var status: Status = .notInstalled
|
||||
public private(set) var isWorking = false
|
||||
public private(set) var lastError: String?
|
||||
/// 正在运行的辅助工具比应用旧:登记的是另一份应用包里的辅助工具,或替换应用后旧进程还没退出
|
||||
public private(set) var isOutdated = false
|
||||
|
||||
/// 连接中断后重新连上时回调,用于重新下发风扇 / 防休眠状态
|
||||
@ObservationIgnored var onReconnect: (() -> Void)?
|
||||
@@ -37,6 +39,11 @@ public final class HelperClient {
|
||||
|
||||
public var isReady: Bool { status == .enabled }
|
||||
|
||||
static let outdatedMessage = "辅助工具版本比应用旧,部分功能可能无法使用,请重新安装一次(需要管理员授权)。"
|
||||
|
||||
/// 未安装、待批准或版本过旧,需要用户处理
|
||||
var needsAttention: Bool { !isReady || isOutdated }
|
||||
|
||||
public func refreshStatus() {
|
||||
switch service.status {
|
||||
case .enabled: status = .enabled
|
||||
@@ -87,6 +94,29 @@ public final class HelperClient {
|
||||
refreshStatus()
|
||||
}
|
||||
|
||||
/// 卸载后重新登记,让 launchd 运行当前应用包里的辅助工具
|
||||
public func reinstall() async {
|
||||
await uninstall()
|
||||
install()
|
||||
await verifyVersion(recheckDelay: .zero)
|
||||
}
|
||||
|
||||
/// 核对辅助工具的协议版本。辅助工具空闲 30 秒后自动退出,下次调用时 launchd 会启动应用包里的新版本,
|
||||
/// 所以第一次发现版本旧时等它退出后再确认一次,避免刚升级完就误报
|
||||
func verifyVersion(recheckDelay: Duration = .seconds(45)) async {
|
||||
guard let version = await remoteProtocolVersion(), version < HelperConstants.protocolVersion else {
|
||||
isOutdated = false
|
||||
return
|
||||
}
|
||||
if recheckDelay > .zero {
|
||||
try? await Task.sleep(for: recheckDelay)
|
||||
guard let again = await remoteProtocolVersion() else { return }
|
||||
isOutdated = again < HelperConstants.protocolVersion
|
||||
} else {
|
||||
isOutdated = true
|
||||
}
|
||||
}
|
||||
|
||||
public func openLoginItemsSettings() {
|
||||
SMAppService.openSystemSettingsLoginItems()
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@ public final class NetworkController {
|
||||
@ObservationIgnored private var lastPublicLookup: (date: Date, localIPv4: [String])?
|
||||
@ObservationIgnored private var isPaused = false
|
||||
@ObservationIgnored private var wantsDetail = false
|
||||
@ObservationIgnored private var inMenuBar = false
|
||||
/// 正在运行的探测是否为前台频率;前后台切换时重启循环,不必等完后台的长间隔
|
||||
@ObservationIgnored private var probingForeground: Bool?
|
||||
|
||||
/// 详情关闭、只在菜单栏显示网速时的低频探测间隔
|
||||
static let backgroundProbeSeconds = 10
|
||||
|
||||
@ObservationIgnored private let geo: GeoDatabaseController
|
||||
|
||||
@@ -61,38 +67,45 @@ public final class NetworkController {
|
||||
|
||||
// MARK: 生命周期
|
||||
|
||||
/// 菜单栏显示网络项或网络详情打开时持续探测
|
||||
func updateProbing(networkShown: Bool) {
|
||||
let shouldRun = settings.probeEnabled && networkShown && !isPaused
|
||||
if shouldRun, probeTask == nil {
|
||||
startProbing()
|
||||
} else if !shouldRun {
|
||||
/// 网络详情打开时按设置的间隔探测;只在菜单栏显示网络项时按 10 秒低频探测(可在设置里关闭),其余时间停止
|
||||
func setVisibility(inMenuBar: Bool, detailVisible: Bool) {
|
||||
self.inMenuBar = inMenuBar
|
||||
wantsDetail = detailVisible
|
||||
applyDetailState()
|
||||
applyProbeState()
|
||||
}
|
||||
|
||||
private func applyProbeState() {
|
||||
let foreground = wantsDetail
|
||||
let shouldRun = settings.probeEnabled && !isPaused
|
||||
&& (foreground || (inMenuBar && settings.probeInBackground))
|
||||
guard shouldRun else {
|
||||
probeTask?.cancel()
|
||||
probeTask = nil
|
||||
probingForeground = nil
|
||||
return
|
||||
}
|
||||
guard probeTask == nil || probingForeground != foreground else { return }
|
||||
probeTask?.cancel()
|
||||
probingForeground = foreground
|
||||
startProbing()
|
||||
}
|
||||
|
||||
/// 探测目标或间隔变化时重新开始,历史清空避免混入不同目标的数据
|
||||
func restartProbing() {
|
||||
guard probeTask != nil else { return }
|
||||
probeTask?.cancel()
|
||||
probeTask = nil
|
||||
probes = History(capacity: Self.probeCapacity)
|
||||
startProbing()
|
||||
}
|
||||
|
||||
func setPaused(_ paused: Bool, networkShown: Bool) {
|
||||
func setPaused(_ paused: Bool) {
|
||||
isPaused = paused
|
||||
updateProbing(networkShown: networkShown)
|
||||
applyProbeState()
|
||||
applyDetailState()
|
||||
}
|
||||
|
||||
/// 网络详情打开时每 2 秒刷新接口信息与进程流量,关闭后停止
|
||||
func setDetailVisible(_ visible: Bool) {
|
||||
wantsDetail = visible
|
||||
applyDetailState()
|
||||
}
|
||||
|
||||
private func applyDetailState() {
|
||||
let shouldRun = wantsDetail && !isPaused
|
||||
guard shouldRun != (detailTask != nil) else { return }
|
||||
@@ -168,12 +181,13 @@ public final class NetworkController {
|
||||
// MARK: 探测
|
||||
|
||||
private func startProbing() {
|
||||
let foreground = probingForeground ?? true
|
||||
probeTask = Task { [weak self] in
|
||||
var sequence: UInt16 = 0
|
||||
let clock = ContinuousClock()
|
||||
while !Task.isCancelled {
|
||||
guard let self else { return }
|
||||
let interval = Double(self.settings.probeSeconds)
|
||||
let interval = Double(foreground ? self.settings.probeSeconds : Self.backgroundProbeSeconds)
|
||||
if self.details == nil { await self.refreshDetails() }
|
||||
let started = clock.now
|
||||
guard let address = self.probeAddress else {
|
||||
@@ -187,7 +201,9 @@ public final class NetworkController {
|
||||
}.value
|
||||
guard !Task.isCancelled else { return }
|
||||
self.probes.append(ProbeSample(latency: latency))
|
||||
try? await Task.sleep(until: started + .seconds(interval), tolerance: .milliseconds(100), clock: clock)
|
||||
// 后台探测允许系统合并唤醒,更省电
|
||||
try? await Task.sleep(until: started + .seconds(interval),
|
||||
tolerance: foreground ? .milliseconds(100) : .seconds(2), clock: clock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user