Swift解决‘顶层不允许表达式’错误:如何启动Mac应用?
问题:Swift Mac应用顶层表达式错误无法启动业务逻辑
开发无需额外框架的Swift Mac应用时,反复触发Expressions are not allowed at the top level错误。现有5个业务处理函数,需要调用首个函数启动应用流程,但Swift不允许顶层直接执行函数调用,尝试封装到其他函数、拆分文件等操作均无效。
提供的代码如下:
import Foundation struct Recipient: Codable { let name: String } struct Period: Codable { let startDate: String let endDate: String let recipient: Recipient } struct Rotation: Codable { let periods: [Period] } struct Schedule: Codable { let finalTimeline: Rotation } class DataProcessor { let apiKey = "" let scheduleID = "" let date = "2023-05-25" // Change this to the desired date func fetchDataAndProcess() { let urlString = "" guard let url = URL(string: urlString) else { print("Invalid API endpoint URL.") return } var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue(apiKey, forHTTPHeaderField: "Authorization") let semaphore = DispatchSemaphore(value: 0) let task = URLSession.shared.dataTask(with: request) { data, response, error in defer { semaphore.signal() } if let error = error { print("Error: \(error)") return } guard let data = data else { print("No data received.") return } do { let schedule = try JSONDecoder().decode(Schedule.self, from: data) let rotations = schedule.finalTimeline.periods // Process the rotations and perform calculations var resultArray: [[String: String]] = [] for period in rotations { let startDate = formatDate(period.startDate) let endDate = formatDate(period.endDate) let days = calculateDays(startDate: startDate, endDate: endDate) let pay = calculatePay(days: days) let name = period.recipient.name let result: [String: String] = [ "Start Date": startDate, "Days": days, "Pay": "\(pay) EUR", "End Date": endDate, "Name": name ] resultArray.append(result) } // Save the results to a file let fileURL = URL(fileURLWithPath: "result.csv") saveResultArray(resultArray, to: fileURL) { success, url in if success { print("Results saved to: \(url)") } else { print("Failed to save results.") } } } catch { print("Error decoding JSON: \(error)") return } } task.resume() semaphore.wait() } // Function to format date as dd.mm.yy func formatDate(_ dateString: String) -> String { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" formatter.timeZone = TimeZone(abbreviation: "UTC") if let date = formatter.date(from: dateString) { formatter.dateFormat = "dd.MM.yy" formatter.timeZone = TimeZone.current return formatter.string(from: date) } return "" } // Function to calculate number of days between two dates func calculateDays(startDate: String, endDate: String) -> String { let formatter = DateFormatter() formatter.dateFormat = "dd.MM.yy" formatter.timeZone = TimeZone.current if let startDate = formatter.date(from: startDate), let endDate = formatter.date(from: endDate) { let calendar = Calendar.current let components = calendar.dateComponents([.day], from: startDate, to: endDate) if let days = components.day { return String(days) } } return "" } // Function to calculate pay based on the number of days func calculatePay(days: String) -> Int { if let days = Int(days) { return days * 30 } return 0 } // Function to save result array to a CSV file func saveResultArray(_ resultArray: [[String: String]], to fileURL: URL, completion: @escaping (Bool, URL) -> Void) { var csvText = "Start Date,Days,Pay,End Date,Name\n" for result in resultArray { let startDate = result["Start Date"] ?? "" let days = result["Days"] ?? "" let pay = result["Pay"] ?? "" let endDate = result["End Date"] ?? "" let name = result["Name"] ?? "" let newLine = "\(startDate),\(days),\(pay),\(endDate),\(name)\n" csvText.append(newLine) } do { try csvText.write(to: fileURL, atomically: true, encoding: .utf8) completion(true, fileURL) } catch { completion(false, fileURL) } } } let dataProcessor = DataProcessor() dataProcessor.fetchDataAndProcess()
解决方案
情况1:切换为Command Line Tool项目
如果应用无需GUI,仅需后台数据处理,直接创建Command Line Tool类型的Xcode项目,顶层函数调用是合法的,现有代码可直接运行。
情况2:保留标准Mac App项目
若必须为带GUI或后台运行的Mac应用,需通过@main标记的入口触发业务逻辑:
方法1:基于SwiftUI的App协议入口
替换原顶层调用代码,添加以下结构:
@main struct DataProcessingApp: App { var body: some Scene { WindowGroup { // 无需GUI可留空,或添加提示文本 Text("Processing data...") .onAppear { let processor = DataProcessor() processor.fetchDataAndProcess() } } } }
应用启动后,视图加载完成时会自动执行fetchDataAndProcess方法。
方法2:基于AppKit的Delegate入口
创建遵循NSApplicationDelegate的类并标记@main:
@main class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { let processor = DataProcessor() processor.fetchDataAndProcess() } }
应用启动完成后会触发applicationDidFinishLaunching方法,在此执行业务逻辑。
关键优化提示
- 避免主线程阻塞:现有代码用
DispatchSemaphore等待网络请求,会导致应用无响应。建议移除信号量,改用异步回调处理后续逻辑,或把处理逻辑移至后台队列。 - 修正文件路径:
URL(fileURLWithPath: "result.csv")会保存到当前工作目录,Mac应用建议使用用户文档目录:
let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let fileURL = documentsDir.appendingPathComponent("result.csv")
内容的提问来源于stack exchange,提问作者Andriy Ivanchenko
相关产品推荐
相关产品推荐

