+++
date = '2025-12-23T13:55:15+08:00'
draft = false
title = '开发日志：Swift简易命令行闹钟程序 – 1'
tags = ['技术栈']
license = 'MIT Licence'
description = '可弹窗，可在Touch Bar上关闭。'
slug = '00B'
+++
2025年11月1日，我通过AI协助，开发了一个名为[NewTesuto](NewTesuto.zip)的SwiftUI闹钟程序，但代码臃肿（1000+行），图形界面难用且资源占用大，有时还会神秘地不响，让我错过早八。  
11月23日，我进行了简单维护，但这个程序依然不令我满意。  
今天，我开始轻量级命令行程序：[Tesuto-Alarm-Dec-2025](Tesuto-Alarm-Dec-2025_12_23.zip)的开发。
## 完整代码
```swift
import UniformTypeIdentifiers
import SwiftUI
import Combine
class Alarm: Codable, ObservableObject
{
    let name: String
    let hour: Int
    let minute: Int
    var triggered: Bool
}
let app = NSApplication.shared
NSApp.setActivationPolicy(.accessory)
let sound = NSSound(contentsOf: URL(fileURLWithPath: ("~/Tesuto-Alarm-Dec-2025/sound.mp3" as NSString).expandingTildeInPath), byReference: false)
if sound != nil
{
    print("\(Date()): 读取声音文件成功")
    print("尝试播放：（2秒后停止）")
    sound?.play()
    DispatchQueue.main.asyncAfter(deadline: .now() + 2)
    {
        sound?.stop()
    }
}
else
{
    print("\(Date()): 读取声音文件失败")
}
var alarms: [Alarm] = []
func readJSON(url: URL) -> Bool
{
    do
    {
        let json = try Data(contentsOf: url)
        let loadedData = try JSONDecoder().decode([Alarm].self, from: json)
        alarms = loadedData
        print("\(Date()): 读取JSON成功")
        print("闹钟数据：")
        for alarm in alarms
        {
            print("\n名称：\(alarm.name)")
            print("时间：\(alarm.hour):\(alarm.minute)")
        }
        return true
    }
    catch
    {
        print("\(Date()): 读取JSON失败(\(error))")
        return false
    }
}
func perform(for alarm: Alarm)
{
    let window = NSAlert()
    window.messageText = "闹钟提醒"
    window.informativeText = "\(alarm.name) - \(alarm.hour):\(alarm.minute)"
    window.addButton(withTitle: "关闭\(alarm.name)")
    print("\(Date()): 触发\(alarm.name) - \(alarm.hour):\(alarm.minute)")
    sound?.play()
    Task
    {
        try await Task.sleep(nanoseconds: 1 * 60 * 1_000_000_000)
        sound?.stop()
        print("\(Date()): 超时自动关闭\(alarm.name) - \(alarm.hour):\(alarm.minute)")
        return
    }
    window.runModal()
    sound?.stop()
    print("\(Date()): 手动关闭\(alarm.name) - \(alarm.hour):\(alarm.minute)")
}
let timer = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
var cancellables = Set<AnyCancellable>()
_ = readJSON(url: URL(fileURLWithPath: ("~/Tesuto-Alarm-Dec-2025/alarms.json" as NSString).expandingTildeInPath))
timer.sink
{
    _ in
    let now = Date()
    let calendar = Calendar.current
    let currentSecond = calendar.component(.second, from: now)
    if currentSecond < 3
    {
        let currentHour = calendar.component(.hour, from: now)
        let currentMinute = calendar.component(.minute, from: now)
        for alarm in alarms
        {
            if alarm.hour == currentHour && alarm.minute == currentMinute && !alarm.triggered
            {
                perform(for: alarm)
                alarm.triggered.toggle()
                DispatchQueue.main.asyncAfter(deadline: .now() + 5)
                {
                    alarm.triggered.toggle()
                    print("\(Date()): 清除\(alarm.name)的triggered标识")
                }
            }
        }
    }
}
.store(in: &cancellables)
RunLoop.current.run()
```
## 注
正正好好一百行，且代码亲手写成，再也不会看不懂了。
## 待解决
疑似包含非标准输出(STDOUT)（声音播放？），不能生成日志文件，不能以系统服务在后台运行。