Skip to content

Improve refresh and async safety#3

Open
1254455745 wants to merge 1 commit into
TNT-Likely:mainfrom
1254455745:split/infrastructure-fixes
Open

Improve refresh and async safety#3
1254455745 wants to merge 1 commit into
TNT-Likely:mainfrom
1254455745:split/infrastructure-fixes

Conversation

@1254455745
Copy link
Copy Markdown

@1254455745 1254455745 commented May 31, 2026

Summary by CodeRabbit

发布说明

  • Bug Fixes
    • 改进行情获取的错误处理机制,提供更清晰的错误反馈
    • 增强键盘快捷键处理的稳定性,防止潜在崩溃
    • 优化通知权限和发送处理流程的可靠性
    • 改进行情刷新的防重入机制,提升响应稳定性
    • 优化应用更新检查的性能

@pullfrog
Copy link
Copy Markdown

pullfrog Bot commented May 31, 2026

Review mode finished without calling create_pull_request_review after 3 retry attempts

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using DeepSeek Flash𝕏

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

走查

该 PR 在五个基础设施和服务模块中进行的并发与安全性改进。改动包括行情获取的显式错误传播、键盘布局指针的安全校验、通知服务的结构化并发迁移、报价刷新器的防重入与流程重构、以及更新器的异步等待优化。

变更

并发与安全性全面改进

Layer / File(s) 摘要
行情获取显式错误处理
PanBar/Domain/Services/PortfolioService.swift
computeSnapshot() 行情获取从容错回退改为显式错误传播;符号集非空时持续 try await provider.fetch,空结果时抛出 ProviderError.empty
键盘布局指针安全处理
PanBar/Infrastructure/HotkeyBinding.swift
translateKeyCode()CFDataGetBytePtr 强制解包改为 guard 校验,并使用 withMemoryRebound 安全地进行内存重绑定,消除强制解包导致的崩溃风险。
NotificationService 迁移到结构化并发
PanBar/Infrastructure/NotificationService.swift
四处主线程切换从 DispatchQueue.main.async 改为 Task { @mainactor in ... }:授权状态刷新、系统设置获取、通知完成回调日志、权限拒绝弹窗触发。
QuoteRefresher 刷新流程重构与防重入
PanBar/Infrastructure/QuoteRefresher.swift
移除多次 MainActor.run 改为直接状态读写;新增 guard !isRefreshing 防重入与 defer 自动复位;扩展成功条件以覆盖空持仓场景,并清空 quotes 和扩大告警引擎评估范围。
Updater 移除不必要的 await
PanBar/Infrastructure/Updater.swift
checkForUpdates() 后台 Task 中移除对同步 @MainActor 方法的不必要 await,保持 UI 处理逻辑不变。

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 五个文件齐改进,
指针安全守护线,
结构化并发升
防重入成画面,
主线程更现代!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确反映了本次 PR 的核心目的:通过修改错误处理、指针安全性和异步调度方式来改进代码的刷新逻辑和异步安全性。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@PanBar/Infrastructure/HotkeyBinding.swift`:
- Around line 87-105: The current use of layoutPtr.withMemoryRebound(to:
UCKeyboardLayout.self, capacity: 1) in translateKeyCode(_:) is acceptable, but
add a defensive size check before rebinding: verify CFDataGetLength(dataRef) >=
MemoryLayout<UCKeyboardLayout>.size and return nil (or handle error) if not, and
optionally add a debug-time assertion to check alignment/size; keep the
remainder of the call (layoutPtr.withMemoryRebound, UCKeyTranslate,
deadKeyState, chars, actualLength) unchanged and reference layoutPtr,
withMemoryRebound, UCKeyboardLayout, and translateKeyCode(_:) to locate the
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e6a85bf8-e011-4a30-a486-75fd75510e8b

📥 Commits

Reviewing files that changed from the base of the PR and between b6c2792 and 94ebceb.

📒 Files selected for processing (5)
  • PanBar/Domain/Services/PortfolioService.swift
  • PanBar/Infrastructure/HotkeyBinding.swift
  • PanBar/Infrastructure/NotificationService.swift
  • PanBar/Infrastructure/QuoteRefresher.swift
  • PanBar/Infrastructure/Updater.swift

Comment on lines +87 to +105
guard let layoutPtr = CFDataGetBytePtr(dataRef) else { return nil }

var deadKeyState: UInt32 = 0
var actualLength = 0
var chars: [UniChar] = Array(repeating: 0, count: 4)
let status = UCKeyTranslate(
keyLayout,
keyCode,
UInt16(kUCKeyActionDisplay),
0,
UInt32(LMGetKbdType()),
UInt32(kUCKeyTranslateNoDeadKeysBit),
&deadKeyState,
chars.count,
&actualLength,
&chars
)
let status = layoutPtr.withMemoryRebound(to: UCKeyboardLayout.self, capacity: 1) { keyLayout in
UCKeyTranslate(
keyLayout,
keyCode,
UInt16(kUCKeyActionDisplay),
0,
UInt32(LMGetKbdType()),
UInt32(kUCKeyTranslateNoDeadKeysBit),
&deadKeyState,
chars.count,
&actualLength,
&chars
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Apple Swift 官方文档里,UnsafePointer.withMemoryRebound(to:capacity:_:)的前提条件是什么?它是否适合把CFDataGetBytePtr返回的UnsafePointer临时重绑定为UnsafePointer后传给UCKeyTranslate

💡 Result:

UnsafePointer.withMemoryRebound(to:capacity:_:) is a Swift method used to temporarily treat memory as a different type [1][2]. Its primary preconditions and requirements are as follows: 1. Layout Compatibility: The target type T must be layout-compatible with the pointer's current Pointee type, or they must satisfy specific aggregation relationships (where one is an aggregate of the other) [3]. 2. Memory Alignment: The pointer must be correctly aligned for the target type T [3]. 3. Exclusive Access: During the execution of the closure, you must not access the memory via the original pointer [2]. You must only use the pointer provided by the closure to ensure the optimizer respects the temporary type binding [2][4]. 4. Optimization Barrier: This method acts as an optimization barrier, ensuring that the compiler does not make invalid assumptions about the memory's type binding while the closure is active [4]. Regarding the use of CFDataGetBytePtr with UCKeyTranslate: Yes, using withMemoryRebound is a safe and correct approach to convert the UnsafePointer<UInt8> returned by CFDataGetBytePtr to an UnsafePointer<UCKeyboardLayout> for use with UCKeyTranslate [1][3]. Since the raw bytes of the CFData containing the keyboard layout are inherently structured as a UCKeyboardLayout object, withMemoryRebound allows you to bridge the type system safely [1][5]. Example implementation: let dataRef: CFData =... // Your CFData reference let bytePtr = CFDataGetBytePtr(dataRef) let layoutPtr = bytePtr.withMemoryRebound(to: UCKeyboardLayout.self, capacity: 1) { ptr in // Call UCKeyTranslate here using the re-bound 'ptr' UCKeyTranslate(ptr,...) } This method is preferred over unsafeBitCast or direct pointer initialization because it explicitly informs the Swift compiler of the intent to treat the memory as a different type, avoiding undefined behavior related to aliasing rules [1][2][4]. Top results: [1][3][2][4][5]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and print relevant surrounding code for translateKeyCode
FILE="PanBar/Infrastructure/HotkeyBinding.swift"

# Show a wider context around translateKeyCode
sed -n '1,200p' "$FILE" | nl -ba | sed -n '1,220p'
sed -n '200,400p' "$FILE" | nl -ba | sed -n '1,220p'

# Specifically search for translateKeyCode definition and CFDataGetBytePtr usage
rg -n "translateKeyCode|CFDataGetBytePtr|dataRef" "$FILE" -n

# Print around the CFDataGetBytePtr call site with some extra lines
line=$(rg -n "CFDataGetBytePtr" "$FILE" | head -n1 | cut -d: -f1)
start=$((line-40))
end=$((line+40))
sed -n "${start},${end}p" "$FILE" | nl -ba

Repository: TNT-Likely/PanBar

Length of output: 104


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="PanBar/Infrastructure/HotkeyBinding.swift"

# Print first 240 lines with line numbers (cat -n instead of nl)
cat -n "$FILE" | sed -n '1,240p'

# Print translateKeyCode region if present by searching
rg -n "func\s+translateKeyCode|translateKeyCode\(" "$FILE" || true

# Show around the CFDataGetBytePtr call site
line=$(rg -n "CFDataGetBytePtr" "$FILE" | head -n1 | cut -d: -f1)
start=$((line-60))
end=$((line+80))
cat -n "$FILE" | sed -n "${start},${end}p"

# Find where dataRef is set/assigned
rg -n "dataRef\s*=|let\s+dataRef|var\s+dataRef|CFData\s" "$FILE" || true

Repository: TNT-Likely/PanBar

Length of output: 9063


🌐 Web query:

kTISPropertyUnicodeKeyLayoutData CFData UCKeyboardLayout bytes layout UCKeyTranslate

💡 Result:

To use UCKeyTranslate with keyboard layout data retrieved via TISGetInputSourceProperty, follow these steps to process the kTISPropertyUnicodeKeyLayoutData property. 1. Retrieve the Data: Use TISGetInputSourceProperty with the key kTISPropertyUnicodeKeyLayoutData to obtain the reference to the keyboard layout data [1][2][3]. This property returns a CFDataRef [1][4]. 2. Access the Bytes: Extract the underlying pointer to the keyboard layout data using CFDataGetBytePtr [1][2][4]. 3. Cast to UCKeyboardLayout: Cast the resulting byte pointer to an UnsafePointer [2][3]. The UCKeyboardLayout structure is defined in the CarbonCore framework (UnicodeUtilities.h) and acts as a header for the layout's internal tables [5]. 4. Call UCKeyTranslate: Pass the pointer obtained in the previous step as the first argument (keyLayoutPtr) to the UCKeyTranslate function [6][5]. Example (Swift): let layoutData = TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData) let data = Unmanaged.fromOpaque(layoutData!).takeUnretainedValue as Data data.withUnsafeBytes { buffer in guard let baseAddress = buffer.bindMemory(to: UCKeyboardLayout.self).baseAddress else { return } let osStatus = UCKeyTranslate(baseAddress, keyCode, UInt16(kUCKeyActionDown), modifierKeyState, UInt32(LMGetKbdType), OptionBits(kUCKeyTranslateNoDeadKeysBit), &deadKeyState, maxChars, &length, &chars) } Important Considerations: - Memory Management: UCKeyTranslate does not move memory, so the resource handle does not need to be locked [6]. - Modifiers: Ensure your modifier key state is correctly formatted for Carbon-era APIs (often requiring a right-shift of 8 bits) [1][3]. - Unicode Utilities: The UCKeyboardLayout structure is essentially a resource header; do not attempt to manually traverse its internal offsets unless you are parsing the raw layout file structure, as UCKeyTranslate handles this internally [5].

Citations:


🌐 Web query:

Swift UnsafePointer withMemoryRebound from UnsafePointer<UInt8> to struct layout-compatible precondition

💡 Result:

When using withMemoryRebound(to:capacity:_:) in Swift, you must ensure that the target type T is layout-compatible with the pointer's current Pointee type [1][2]. Accessing memory as an unrelated type without first rebinding it is undefined behavior [1][2]. The requirements for this layout compatibility are as follows: 1. Layout Equivalence or Aggregation: The types must be layout-equivalent, or one type must be a homogeneous aggregate of the other [3]. Specifically, the stride of the temporary type (T) may be an integer multiple or a whole fraction of the Pointee stride [1][2]. This allows for scenarios such as pointing to one element of an aggregate [1]. 2. Initialization Consistency: Within the rebound region, every instance of Pointee overlapping with a given instance of T should have the same initialization state (either all initialized or all uninitialized) [1]. Accessing T where the underlying Pointee storage has mixed initialization states results in undefined behavior [1]. 3. Alignment: If T and Pointee have different alignments, the pointer must be aligned with the larger of the two [1][2]. 4. Correct Usage: Use withMemoryRebound only for temporary access within the provided closure [1][2]. The memory is automatically rebound to the original Pointee type after the closure finishes [1][2]. Do not access the memory via the original pointer inside the closure [2]. If the types do not meet these compatibility requirements, you should convert the pointer to an UnsafeRawPointer and use the bindMemory(to:capacity:) method instead [1][2]. Note that bindMemory performs a permanent binding change to the memory's type, unlike the temporary nature of withMemoryRebound [4][5].

Citations:


关于 withMemoryRebound 的类型安全性:当前用法更可能是正确的(PanBar/Infrastructure/HotkeyBinding.swift,translateKeyCode(_:) 第 83-108 行)

  • layoutData 来自 kTISPropertyUnicodeKeyLayoutData,该 CFData 的字节内容用于提供 UCKeyTranslate 所需的键盘布局内存(常见做法就是将 CFDataGetBytePtr 的结果按 UCKeyboardLayout 读取后直接传入 UCKeyTranslate)。
  • 因此不应直接断言从 UnsafePointer<UInt8> 绑定到 UCKeyboardLayout “不满足前提”而必须改回 unsafeBitCastcapacity: 1 也符合“把 UCKeyboardLayout 当作头部起始指针传给 UCKeyTranslate”的典型用法。
  • 仍建议加一个最小长度防御:在 withMemoryRebound 前判断 CFDataGetLength(dataRef) >= MemoryLayout<UCKeyboardLayout>.size(并可在调试时断言对齐情况),避免异常/损坏数据导致越界读取。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PanBar/Infrastructure/HotkeyBinding.swift` around lines 87 - 105, The current
use of layoutPtr.withMemoryRebound(to: UCKeyboardLayout.self, capacity: 1) in
translateKeyCode(_:) is acceptable, but add a defensive size check before
rebinding: verify CFDataGetLength(dataRef) >=
MemoryLayout<UCKeyboardLayout>.size and return nil (or handle error) if not, and
optionally add a debug-time assertion to check alignment/size; keep the
remainder of the call (layoutPtr.withMemoryRebound, UCKeyTranslate,
deadKeyState, chars, actualLength) unchanged and reference layoutPtr,
withMemoryRebound, UCKeyboardLayout, and translateKeyCode(_:) to locate the
change.

@TNT-Likely
Copy link
Copy Markdown
Owner

image 行情请求失败的时候不该显示离线 @1254455745 这里需要区分 断网/行情请求失败/正常多种情况的展示

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants