Files
2025-11-29 18:28:37 +08:00

14 KiB

System APIs

macOS system integration: file system, notifications, services, and automation.

<file_system> <standard_directories>

let fileManager = FileManager.default

// App Support (persistent app data)
let appSupport = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let appFolder = appSupport.appendingPathComponent("MyApp", isDirectory: true)

// Documents (user documents)
let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!

// Caches (temporary, can be deleted)
let caches = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!

// Temporary (short-lived)
let temp = fileManager.temporaryDirectory

// Create directories
try? fileManager.createDirectory(at: appFolder, withIntermediateDirectories: true)

</standard_directories>

<file_operations>

// Read
let data = try Data(contentsOf: fileURL)
let string = try String(contentsOf: fileURL)

// Write
try data.write(to: fileURL, options: .atomic)
try string.write(to: fileURL, atomically: true, encoding: .utf8)

// Copy/Move
try fileManager.copyItem(at: source, to: destination)
try fileManager.moveItem(at: source, to: destination)

// Delete
try fileManager.removeItem(at: fileURL)

// Check existence
let exists = fileManager.fileExists(atPath: path)

// List directory
let contents = try fileManager.contentsOfDirectory(
    at: folderURL,
    includingPropertiesForKeys: [.isDirectoryKey, .fileSizeKey],
    options: [.skipsHiddenFiles]
)

</file_operations>

<file_monitoring>

import CoreServices

class FileWatcher {
    private var stream: FSEventStreamRef?
    private var callback: () -> Void

    init(path: String, onChange: @escaping () -> Void) {
        self.callback = onChange

        var context = FSEventStreamContext()
        context.info = Unmanaged.passUnretained(self).toOpaque()

        let paths = [path] as CFArray
        stream = FSEventStreamCreate(
            nil,
            { _, info, numEvents, eventPaths, _, _ in
                guard let info = info else { return }
                let watcher = Unmanaged<FileWatcher>.fromOpaque(info).takeUnretainedValue()
                DispatchQueue.main.async {
                    watcher.callback()
                }
            },
            &context,
            paths,
            FSEventStreamEventId(kFSEventStreamEventIdSinceNow),
            0.5,  // Latency in seconds
            FSEventStreamCreateFlags(kFSEventStreamCreateFlagFileEvents)
        )

        FSEventStreamSetDispatchQueue(stream!, DispatchQueue.global())
        FSEventStreamStart(stream!)
    }

    deinit {
        if let stream = stream {
            FSEventStreamStop(stream)
            FSEventStreamInvalidate(stream)
            FSEventStreamRelease(stream)
        }
    }
}

// Usage
let watcher = FileWatcher(path: "/path/to/watch") {
    print("Files changed!")
}

</file_monitoring>

<security_scoped_bookmarks> For sandboxed apps to retain file access:

class BookmarkManager {
    func saveBookmark(for url: URL) throws -> Data {
        // User selected this file via NSOpenPanel
        let bookmark = try url.bookmarkData(
            options: .withSecurityScope,
            includingResourceValuesForKeys: nil,
            relativeTo: nil
        )
        return bookmark
    }

    func resolveBookmark(_ data: Data) throws -> URL {
        var isStale = false
        let url = try URL(
            resolvingBookmarkData: data,
            options: .withSecurityScope,
            relativeTo: nil,
            bookmarkDataIsStale: &isStale
        )

        // Start accessing
        guard url.startAccessingSecurityScopedResource() else {
            throw BookmarkError.accessDenied
        }

        // Remember to call stopAccessingSecurityScopedResource() when done

        return url
    }
}

</security_scoped_bookmarks> </file_system>

```swift import UserNotifications

class NotificationService { private let center = UNUserNotificationCenter.current()

func requestPermission() async -> Bool {
    do {
        return try await center.requestAuthorization(options: [.alert, .sound, .badge])
    } catch {
        return false
    }
}

func scheduleNotification(
    title: String,
    body: String,
    at date: Date,
    identifier: String
) async throws {
    let content = UNMutableNotificationContent()
    content.title = title
    content.body = body
    content.sound = .default

    let components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: date)
    let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)

    let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
    try await center.add(request)
}

func scheduleImmediateNotification(title: String, body: String) async throws {
    let content = UNMutableNotificationContent()
    content.title = title
    content.body = body
    content.sound = .default

    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
    let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

    try await center.add(request)
}

func cancelNotification(identifier: String) {
    center.removePendingNotificationRequests(withIdentifiers: [identifier])
}

}

</local_notifications>

<notification_handling>
```swift
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate {
    func applicationDidFinishLaunching(_ notification: Notification) {
        UNUserNotificationCenter.current().delegate = self
    }

    // Called when notification arrives while app is in foreground
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification
    ) async -> UNNotificationPresentationOptions {
        [.banner, .sound]
    }

    // Called when user interacts with notification
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse
    ) async {
        let identifier = response.notification.request.identifier
        // Handle the notification tap
        handleNotificationAction(identifier)
    }
}

</notification_handling>

<launch_at_login>

import ServiceManagement

class LaunchAtLoginManager {
    var isEnabled: Bool {
        get {
            SMAppService.mainApp.status == .enabled
        }
        set {
            do {
                if newValue {
                    try SMAppService.mainApp.register()
                } else {
                    try SMAppService.mainApp.unregister()
                }
            } catch {
                print("Failed to update launch at login: \(error)")
            }
        }
    }
}

// SwiftUI binding
struct SettingsView: View {
    @State private var launchAtLogin = LaunchAtLoginManager()

    var body: some View {
        Toggle("Launch at Login", isOn: Binding(
            get: { launchAtLogin.isEnabled },
            set: { launchAtLogin.isEnabled = $0 }
        ))
    }
}

</launch_at_login>

```swift import AppKit

let workspace = NSWorkspace.shared

// Open URL in browser workspace.open(URL(string: "https://example.com")!)

// Open file with default app workspace.open(fileURL)

// Open file with specific app workspace.open( [fileURL], withApplicationAt: appURL, configuration: NSWorkspace.OpenConfiguration() )

// Reveal in Finder workspace.activateFileViewerSelecting([fileURL])

// Get app for file type if let appURL = workspace.urlForApplication(toOpen: fileURL) { print("Default app: (appURL)") }

// Get running apps let runningApps = workspace.runningApplications for app in runningApps { print("(app.localizedName ?? "Unknown"): (app.bundleIdentifier ?? "")") }

// Get frontmost app if let frontmost = workspace.frontmostApplication { print("Frontmost: (frontmost.localizedName ?? "")") }

// Observe app launches NotificationCenter.default.addObserver( forName: NSWorkspace.didLaunchApplicationNotification, object: workspace, queue: .main ) { notification in if let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { print("Launched: (app.localizedName ?? "")") } }

</nsworkspace>

<process_management>
```swift
import Foundation

// Run shell command
func runCommand(_ command: String) async throws -> String {
    let process = Process()
    process.executableURL = URL(fileURLWithPath: "/bin/zsh")
    process.arguments = ["-c", command]

    let pipe = Pipe()
    process.standardOutput = pipe
    process.standardError = pipe

    try process.run()
    process.waitUntilExit()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    return String(data: data, encoding: .utf8) ?? ""
}

// Launch app
func launchApp(bundleIdentifier: String) {
    if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleIdentifier) {
        NSWorkspace.shared.openApplication(at: url, configuration: NSWorkspace.OpenConfiguration())
    }
}

// Check if app is running
func isAppRunning(bundleIdentifier: String) -> Bool {
    NSWorkspace.shared.runningApplications.contains {
        $0.bundleIdentifier == bundleIdentifier
    }
}

</process_management>

```swift import AppKit

let pasteboard = NSPasteboard.general

// Write text pasteboard.clearContents() pasteboard.setString("Hello", forType: .string)

// Read text if let string = pasteboard.string(forType: .string) { print(string) }

// Write URL pasteboard.clearContents() pasteboard.writeObjects([url as NSURL])

// Read URLs if let urls = pasteboard.readObjects(forClasses: [NSURL.self]) as? [URL] { print(urls) }

// Write image pasteboard.clearContents() pasteboard.writeObjects([image])

// Monitor clipboard class ClipboardMonitor { private var timer: Timer? private var lastChangeCount = 0

func start(onChange: @escaping (String?) -> Void) {
    timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
        let changeCount = NSPasteboard.general.changeCount
        if changeCount != self.lastChangeCount {
            self.lastChangeCount = changeCount
            onChange(NSPasteboard.general.string(forType: .string))
        }
    }
}

func stop() {
    timer?.invalidate()
}

}

</clipboard>

<apple_events>
```swift
import AppKit

// Tell another app to do something (requires com.apple.security.automation.apple-events)
func tellFinderToEmptyTrash() {
    let script = """
    tell application "Finder"
        empty trash
    end tell
    """

    var error: NSDictionary?
    if let scriptObject = NSAppleScript(source: script) {
        scriptObject.executeAndReturnError(&error)
        if let error = error {
            print("AppleScript error: \(error)")
        }
    }
}

// Get data from another app
func getFinderSelection() -> [URL] {
    let script = """
    tell application "Finder"
        set selectedItems to selection
        set itemPaths to {}
        repeat with anItem in selectedItems
            set end of itemPaths to POSIX path of (anItem as text)
        end repeat
        return itemPaths
    end tell
    """

    var error: NSDictionary?
    if let scriptObject = NSAppleScript(source: script),
       let result = scriptObject.executeAndReturnError(&error).coerce(toDescriptorType: typeAEList) {
        var urls: [URL] = []
        for i in 1...result.numberOfItems {
            if let path = result.atIndex(i)?.stringValue {
                urls.append(URL(fileURLWithPath: path))
            }
        }
        return urls
    }
    return []
}

</apple_events>

```swift // Info.plist /* NSServices NSMessage processText NSPortName MyApp NSSendTypes public.plain-text NSReturnTypes public.plain-text NSMenuItem default Process with MyApp */

class ServiceProvider: NSObject { @objc func processText( _ pboard: NSPasteboard, userData: String, error: AutoreleasingUnsafeMutablePointer<NSString?> ) { guard let string = pboard.string(forType: .string) else { error.pointee = "No text found" as NSString return }

    // Process the text
    let processed = string.uppercased()

    // Return result
    pboard.clearContents()
    pboard.setString(processed, forType: .string)
}

}

// Register in AppDelegate func applicationDidFinishLaunching(_ notification: Notification) { NSApp.servicesProvider = ServiceProvider() NSUpdateDynamicServices() }

</providing_services>
</services>

<accessibility>
```swift
import AppKit

// Check if app has accessibility permissions
func hasAccessibilityPermission() -> Bool {
    AXIsProcessTrusted()
}

// Request permission
func requestAccessibilityPermission() {
    let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue(): true] as CFDictionary
    AXIsProcessTrustedWithOptions(options)
}

// Check display settings
let workspace = NSWorkspace.shared
let reduceMotion = workspace.accessibilityDisplayShouldReduceMotion
let reduceTransparency = workspace.accessibilityDisplayShouldReduceTransparency
let increaseContrast = workspace.accessibilityDisplayShouldIncreaseContrast