Skip to content

About

A lightweight macOS menu bar network speed monitor — native Swift + SwiftUI, zero third-party dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

NetSpeed

macOS 菜单栏实时网速。原生 Swift / SwiftUI 写的纯菜单栏应用——不占 Dock、没有窗口、没有第三方依赖,编译产物就是一个可以直接双击的 NetSpeed.app。

License: MIT Platform Release

面板预览

安装

方式一:下载编译好的应用(推荐)

到 Releases 下载 NetSpeed-1.0.0-macos-arm64.zip,解压后把 NetSpeed.app 拖进「应用程序」。

首次打开会被系统拦下。 本应用是 ad-hoc 签名、未做 Apple 公证(公证需要付费开发者账号),系统会提示「无法验证开发者」或「已损坏,应移到废纸篓」。这不是应用有问题——任何人自己编译出来的 macOS 应用都是这个待遇。两种解决办法:

  • 右键点图标 → 选「打开」→ 在弹窗里再点一次「打开」。 只需做一次。

  • 或者在终端执行一次,清掉整个包的隔离标记:

    xattr -dr com.apple.quarantine /Applications/NetSpeed.app

方式二:自己编译(本机产物没有隔离标记,不会被 Gatekeeper 拦)

git clone https://github.com/SimplifeX/netspeed-macos.git
cd netspeed-macos
./build.sh install     # 编译并装到 /Applications

构建逻辑就是一条 swiftc 命令,外加 lipo 与 codesign:没有 Makefile、没有 SwiftPM 工程、没有第三方依赖,build.sh 只有 90 行。./build.sh 出 dist/NetSpeed.app,./build.sh run 编译并启动,ARCHS="arm64 x86_64" ./build.sh 出通用二进制(需要 Intel 机器也能跑时)。

环境要求:macOS 13 Ventura 或更新,Apple Silicon(M 系列)。源码构建需要 Xcode Command Line Tools(xcode-select --install)。

显示

菜单栏上默认两行,实时刷新:

↑1.2M
↓12.4M

菜单栏实际渲染

拆成两行是为了省宽度:同样内容横排要 90pt 上下,两行只要 40pt 上下。面板里「菜单栏排两行」可以关掉,回到 ↓12.4M ↑1.2M 的单行排。

点开是完整面板:

区块 内容
顶部 当前承载流量的网卡名 + IPv4(点一下复制)
速率 上/下行大数字,等宽数字不跳动
曲线 近 60 秒走势 + 窗口内峰值
会话 本次运行的累计接收 / 发送 / 时长
网卡明细 每个网卡的实时速率,主接口打勾
设置 刷新间隔、单位、菜单栏显示项、菜单栏排两行、菜单栏边距、计速网卡、开机自启

只在统计系统默认路由所在的那张网卡,不是所有网卡求和——开了 VPN 时 utun0 和 en0 承载同一份流量,求和会翻倍。面板里可以手动锁定某张网卡。

只统计网络接口的原始收发字节,不含进程归属(想看「哪个 App 在占带宽」用活动监视器,面板右下角有直达按钮);上行包含 TCP ACK,所以纯下载时上行不会是 0。

左右留白由应用自己定。 默认 3pt:状态项整块 42pt,实际左空 4pt;面板里「菜单栏边距」可以切回系统标准的 8pt(整块 52pt,左空 9pt)。

留白 状态项整块 实际左空
标准 8pt 52pt 9.0pt
窄 3pt(默认) 42pt 4.0pt

留白对比

做法是不走 SwiftUI 的 MenuBarExtra,应用自己持有 NSStatusItem、自己把两行标签画成一张模板图写进 button.image、自己把宽度定为「图宽 + 左右各 pad」。⚠️ 直接设 statusItem.length 没用:把它设成 40pt,菜单栏上实际占 56pt,系统会在内容视图外面再加一圈 8pt;只有压按钮 frame,内容视图才会贴着按钮走。顺带标签刷新也就不再经过 SwiftUI,数值一变只重画一张小位图。

资源占用

开销的大头不是读网卡,是面板视图被反复重新测量。

菜单栏应用每秒刷新一次数据。如果面板里的视图一直挂着并订阅采样数据,主线程每个 tick 都要沿 NSHostingView.updateConstraints → _sizeThatFits → AttributeGraph 把整棵树重算一遍——面板关着也一样(MenuBarExtra 的 .window 面板就是这样,采样栈里能看到 PopUpButton.body.getter / ForEach / HStack)。「面板没打开就不费电」并不成立。

同一支二进制,只切换「面板内容是否常驻」的 A/B 结果:

CPU(面板关闭,15 秒窗口,3 轮) 峰值内存
面板内容常驻 1.18 / 2.00 / 1.95 %(中位 1.95%) 98 MB
关闭即拆(本应用的做法) 0.45 / 0.75 / 0.83 %(中位 0.75%) 81 MB

三轮中位值之比 2.6×。为此做了三件事:

  1. 面板关掉就把内容拆了。 用 if visibility.isVisible 把面板视图包住,采样与设置对象只在展开时才挂上,关着的时候没有任何视图订阅采样数据。重建一次几毫秒,且只发生在点击时。
  2. 标签刷新绕开 SwiftUI,并且先比字符串再量字宽。 数值没变的那几秒里 CoreText 一次都不动,文本格式化也不重复算。
  3. 削减每 tick 的重复系统调用。 sysctl 缓冲区复用、SCDynamicStore 句柄缓存、地址查询从「每接口一次」改成每 tick 批量一次、地址格式化走 inet_ntop 而非 getnameinfo、定时器回调直接 MainActor.assumeIsolated 不再每秒分配一个任务。

剩下的 ~0.5% 里约 0.2% 是本底(采样间隔放到 5 秒也还有 0.215%),另外 ~0.3% 是轮询本身:每秒一次 sysctl + getifaddrs 读网卡计数器。网络栈没有「字节数变了」的通知,要再往下压只能改事件驱动,只能保留轮询。

绝对数字跟机器负载强相关,看倍率比看绝对值有意义。应用自带 --cpu [秒数] 让进程量自己(走 getrusage,不依赖外部工具),--keep-panel-alive --cpu 20 复现上表第一行。另有 --selftest / --diagnose / --render / --shot 等自检开关,./verify.sh 把取数链路、状态栏存在、面板版式、双行更窄、占位稳定串起来跑一遍。

MIT © 2026 Simplife

About

A lightweight macOS menu bar network speed monitor — native Swift + SwiftUI, zero third-party dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages