You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 23:25:02