From f34ba88db95d2b170301049e59c0faee1da9ff9b Mon Sep 17 00:00:00 2001 From: ysicing Date: Wed, 23 Sep 2026 10:27:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(swift):=20=E6=B8=85=E7=90=86=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E4=B8=8E=E5=BA=9F=E5=BC=83=20API=20=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/XStatsUI/AppKit/StatusPanel.swift | 4 ++-- .../Sources/XStatsUI/Design/Charts.swift | 7 +++--- .../Sources/XStatsUI/Design/Components.swift | 22 ++++++++++++++++++- .../XStatsUI/State/EgressController.swift | 2 +- .../XStatsUI/State/ProcessExplainer.swift | 5 +++-- .../XStatsUI/State/SpeedTestController.swift | 6 ++--- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Packages/XStatsKit/Sources/XStatsUI/AppKit/StatusPanel.swift b/Packages/XStatsKit/Sources/XStatsUI/AppKit/StatusPanel.swift index ba93443..291518a 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/AppKit/StatusPanel.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/AppKit/StatusPanel.swift @@ -34,9 +34,9 @@ final class StatusPanel: NSPanel { defer: true) // 打开时按平铺布局测出的高度只是起点:真实内容会随数据刷新变高变矮,由滚动区域上报差值再校正 makeContent = { [weak self] in - NSHostingView(rootView: content().environment(\.reportPopoverOverflow) { overflow in + NSHostingView(rootView: content().environment(\.reportPopoverOverflow, PopoverOverflowReporter { overflow in self?.adjustHeight(by: overflow) - }) + })) } isFloatingPanel = true level = .statusBar diff --git a/Packages/XStatsKit/Sources/XStatsUI/Design/Charts.swift b/Packages/XStatsKit/Sources/XStatsUI/Design/Charts.swift index 7c6d785..082f58c 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Design/Charts.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Design/Charts.swift @@ -113,7 +113,7 @@ struct TimedLineChart: View { var grid = false var height: CGFloat = DS.Size.chartHeight - static let gapLimit: TimeInterval = 10 + nonisolated static let gapLimit: TimeInterval = 10 var body: some View { Canvas { context, size in @@ -143,7 +143,7 @@ struct TimedLineChart: View { } /// 落在 [end − duration, end] 内的采样换算成坐标(0 在下、1 在上),按间隔断开成若干段 - static func segments(_ points: [TimedValue], duration: TimeInterval, end: Date, in size: CGSize) -> [[CGPoint]] { + nonisolated static func segments(_ points: [TimedValue], duration: TimeInterval, end: Date, in size: CGSize) -> [[CGPoint]] { let start = end.addingTimeInterval(-duration) let inset = DS.Size.chartLine let usable = size.height - inset * 2 @@ -584,6 +584,7 @@ struct ScoreBand: View { private func marker(width: CGFloat) -> some View { let color = DS.Grade.color(for: clamped) let grade = DS.Grade.bands.first { clamped < $0.upper }?.grade ?? "A+" + let markerOffset = width * CGFloat(clamped) / 100 return VStack(spacing: 0) { Text(verbatim: grade) .dsFont(.xs, weight: .semibold) @@ -594,7 +595,7 @@ struct ScoreBand: View { Triangle().fill(color).frame(width: DS.Space.s2, height: DS.Space.s1) } .fixedSize() - .alignmentGuide(.leading) { dimensions in dimensions.width / 2 - width * CGFloat(clamped) / 100 } + .alignmentGuide(.leading) { dimensions in dimensions.width / 2 - markerOffset } } } diff --git a/Packages/XStatsKit/Sources/XStatsUI/Design/Components.swift b/Packages/XStatsKit/Sources/XStatsUI/Design/Components.swift index 0d52d83..b456ab7 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Design/Components.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Design/Components.swift @@ -1,7 +1,27 @@ +import Foundation import Localization import Metrics import SwiftUI +/// 用独立身份包装不可比较的闭包,避免 SwiftUI 把无关环境刷新都视为回调变化。 +struct PopoverOverflowReporter: Equatable, Sendable { + private let id = UUID() + private let action: @MainActor @Sendable (CGFloat) -> Void + + init(_ action: @escaping @MainActor @Sendable (CGFloat) -> Void) { + self.action = action + } + + @MainActor + func callAsFunction(_ overflow: CGFloat) { + action(overflow) + } + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.id == rhs.id + } +} + /// ImageRenderer 无法渲染 ScrollView 等 AppKit 承载的控件,截图模式下改用平铺布局。 extension EnvironmentValues { @Entry var isSnapshot = false @@ -10,7 +30,7 @@ extension EnvironmentValues { /// 菜单栏弹窗:区块去掉卡片底色,用细分隔线隔开,排得更紧凑 @Entry var isPopover = false /// 菜单栏弹窗:滚动内容的实际高度比可视区域高出(负数为矮出)多少,交给面板调整窗口高度 - @Entry var reportPopoverOverflow: (@MainActor @Sendable (CGFloat) -> Void)? = nil + @Entry var reportPopoverOverflow: PopoverOverflowReporter? } // MARK: - 卡片 diff --git a/Packages/XStatsKit/Sources/XStatsUI/State/EgressController.swift b/Packages/XStatsKit/Sources/XStatsUI/State/EgressController.swift index a6c78e7..57612a7 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/State/EgressController.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/State/EgressController.swift @@ -62,7 +62,7 @@ public final class EgressController { let environment = await Self.readEnvironment() self.environment = environment let hits = await EgressProber.measure(interfaceName: environment.physicalInterface) { done in - Task { @MainActor [weak self] in self?.progress = done } + Task { @MainActor in self.progress = done } } let analysis = EgressAnalysis.evaluate(hits, environment: environment) let geo = await lookUpGeo(hits: hits, analysis: analysis, interfaceName: environment.physicalInterface) diff --git a/Packages/XStatsKit/Sources/XStatsUI/State/ProcessExplainer.swift b/Packages/XStatsKit/Sources/XStatsUI/State/ProcessExplainer.swift index 02d084a..4da2876 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/State/ProcessExplainer.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/State/ProcessExplainer.swift @@ -257,8 +257,9 @@ public final class ProcessExplainer { nonisolated private static func executablePath(pid: Int32) -> String? { var buffer = [CChar](repeating: 0, count: 4 * Int(MAXPATHLEN)) - guard proc_pidpath(pid, &buffer, UInt32(buffer.count)) > 0 else { return nil } - return String(cString: buffer) + let length = proc_pidpath(pid, &buffer, UInt32(buffer.count)) + guard length > 0 else { return nil } + return buffer.withUnsafeBytes { String(decoding: $0.prefix(Int(length)), as: UTF8.self) } } } diff --git a/Packages/XStatsKit/Sources/XStatsUI/State/SpeedTestController.swift b/Packages/XStatsKit/Sources/XStatsUI/State/SpeedTestController.swift index 55658e2..4767d94 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/State/SpeedTestController.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/State/SpeedTestController.swift @@ -90,9 +90,9 @@ public final class SpeedTestController { liveBitsPerSecond = 0 broadbandTask = Task { [limit] in let result = await BroadbandTest.run(limit: limit) { stage in - Task { @MainActor [weak self] in self?.broadbandStage = stage; self?.liveBitsPerSecond = 0 } + Task { @MainActor in self.broadbandStage = stage; self.liveBitsPerSecond = 0 } } progress: { speed, _ in - Task { @MainActor [weak self] in self?.liveBitsPerSecond = speed } + Task { @MainActor in self.liveBitsPerSecond = speed } } guard !Task.isCancelled else { return stopBroadband() } broadband = result @@ -217,7 +217,7 @@ public final class SpeedTestController { liveBitsPerSecond = 0 downloadTask = Task { [limit] in let result = await ThroughputProbe.download(node.downloadURL, limit: limit) { speed, _ in - Task { @MainActor [weak self] in self?.liveBitsPerSecond = speed } + Task { @MainActor in self.liveBitsPerSecond = speed } } guard !Task.isCancelled else { downloadingNode = nil