Files
simslim-mirror/gui/Models.swift
Interlap ae365e3868 perf: slim booted simulators offline and run launchctl transitions in parallel
Three changes from the SwiftSimSlim report in #50, all in the Go backend and
the SwiftUI front end.

**The shell batching never ran.** `runBatch` spawned `/bin/sh -c` inside the
simulator to walk a chunk of labels, but no iOS runtime ships a shell —
`RuntimeRoot/bin` holds exactly `df` and `launchctl`, from iOS 18.3 through
27.1. Every spawn failed with LaunchdSimError 111, `parseBatchOK` returned
nothing, and all 170 labels fell through to the serial per-label retry pass.
Delete the script and its helpers; run the direct spawns through a pool of 8
instead. That is both less code and the actual speedup: `on --no-reboot` over
170 labels on a freshly erased iPhone 17 Pro / iOS 26.5 goes 2m14s -> 37.5s.

**A booted device now takes the offline store too.** An override only takes
effect at the next boot, so a booted device already owed a shutdown and a
boot. `ensure` spends them up front and hands off to `ensureOffline`, which
replaces every launchctl spawn for the same two state changes. Measured 16.8s
to slim a booted simulator, 15.0s to restore one. It also reads the live state
first and returns early when the profile already matches, so re-applying an
unchanged profile costs one read and no reboot at all (0.7s).

**The GUI no longer freezes on a global busy flag.** Batches reserve every
device up front and run two at a time, and controls gate on the devices they
would act on rather than on "anything is running", so work on one simulator
leaves the others live. `BatchProgress` carries the names in flight instead of
a single current name.
2026-09-21 10:12:52 +02:00

210 lines
4.8 KiB
Swift

import Foundation
enum SlimmingMode: String, CaseIterable, Identifiable {
case memory = "Memory Use"
case disk = "Disk Size"
var id: String { rawValue }
}
struct SimulatorDevice: Decodable, Identifiable, Equatable {
let udid: String
let name: String
let state: String
let osVersion: String
let managedDisabled: Int?
let managedTotal: Int
let statusError: String?
let memory: SimulatorMeasurement?
let memoryError: String?
var id: String { udid }
var isBooted: Bool { state == "Booted" }
}
struct SlimCategory: Decodable, Identifiable, Equatable {
let id: String
let name: String
let description: String
let downside: String
let approxMemoryMB: Int
let labels: [String]
let serviceDescriptions: [String: String]?
let alwaysEnabled: [AlwaysEnabledService]?
var alwaysEnabledServices: [AlwaysEnabledService] {
alwaysEnabled ?? []
}
var approximateMemoryText: String {
"Uses ~\(approxMemoryMB) MB RAM"
}
func serviceDescription(for label: String) -> String {
serviceDescriptions?[label] ?? "Apple background service."
}
}
struct AlwaysEnabledService: Decodable, Identifiable, Equatable {
let label: String
let reason: String
var id: String { label }
}
struct SimulatorMeasurement: Decodable, Equatable {
let processes: Int
let bytes: Int64
var memoryText: String {
ByteCountFormatter.string(fromByteCount: bytes, countStyle: .memory)
}
}
struct SimulatorDiskMeasurement: Decodable, Equatable {
let bytes: Int64
var sizeText: String {
ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
}
}
struct DiskCleanupCategory: Decodable, Identifiable, Equatable {
let id: String
let name: String
let description: String
let downside: String
let recovery: String
let risk: String
let defaultSelected: Bool
let canClean: Bool
}
struct SimulatorDiskCleanupMeasurement: Decodable, Equatable {
let id: String
let bytes: Int64
let targets: Int
var sizeText: String {
ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
}
}
struct SimulatorDiskStorageMeasurement: Decodable, Identifiable, Equatable {
let id: String
let name: String
let description: String
let bytes: Int64
var sizeText: String {
ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
}
var systemImage: String {
switch id {
case "installed-apps": return "app.dashed"
case "documents": return "doc"
case "app-data": return "externaldrive"
case "user-media": return "photo.on.rectangle"
default: return "folder"
}
}
}
struct SimulatorDiskCleanupPlan: Decodable, Equatable {
let udid: String
let totalBytes: Int64
let cleanableBytes: Int64
let categories: [SimulatorDiskCleanupMeasurement]
let storage: [SimulatorDiskStorageMeasurement]
func bytes(for categoryID: String) -> Int64 {
categories.first(where: { $0.id == categoryID })?.bytes ?? 0
}
func storageBytes(for storageID: String) -> Int64 {
storage.first(where: { $0.id == storageID })?.bytes ?? 0
}
}
struct SimulatorDiskCleanupResult: Decodable, Equatable {
let udid: String
let categoryIds: [String]
let beforeBytes: Int64
let afterBytes: Int64
let reclaimedBytes: Int64
let wasBooted: Bool
let bootStateRestored: Bool
var reclaimedText: String {
ByteCountFormatter.string(fromByteCount: reclaimedBytes, countStyle: .file)
}
}
struct SimulatorMutationResult: Decodable, Equatable {
let action: String
let udid: String
let name: String?
let sourceUdid: String?
}
enum ServiceSlimState: Equatable {
case stock
case partial(disabled: Int, total: Int)
case full(total: Int)
case unknown
var title: String {
switch self {
case .stock: return "Stock"
case .partial(let disabled, let total): return "Partial (\(disabled)/\(total))"
case .full(let total): return "Fully slim (\(total)/\(total))"
case .unknown: return "Unknown while shutdown"
}
}
var systemImage: String {
switch self {
case .stock: return "circle.fill"
case .partial: return "circle.lefthalf.filled"
case .full: return "circle"
case .unknown: return "questionmark.circle"
}
}
}
struct BatchProgress: Equatable {
let completed: Int
let total: Int
/// Names of the simulators in flight right now; a batch runs more than one.
let running: [String]
let action: String
var fraction: Double {
guard total > 0 else { return 0 }
return Double(completed) / Double(total)
}
var runningText: String {
running.isEmpty ? "\(action)…" : "\(action) \(running.joined(separator: ", "))…"
}
}
struct ActivityEntry: Identifiable, Equatable {
enum Level {
case info
case success
case failure
}
let id = UUID()
let date = Date()
let level: Level
let message: String
}
struct PresentedError: Identifiable {
let id = UUID()
let message: String
}