MiniSim is a macOS menu bar utility for launching iOS simulators and Android emulators. Written in Swift and AppKit.
# Build the project
xcodebuild -scheme MiniSim -configuration Debug build
# Build for release
xcodebuild -scheme MiniSim -configuration Release build
# Clean build
xcodebuild -scheme MiniSim clean build# Run all tests
xcodebuild test -scheme MiniSim -destination 'platform=macOS'
# Run a single test file
xcodebuild test -scheme MiniSim -destination 'platform=macOS' \
-only-testing:MiniSimTests/DeviceParserTests
# Run a single test method
xcodebuild test -scheme MiniSim -destination 'platform=macOS' \
-only-testing:MiniSimTests/DeviceParserTests/testIOSSimulatorParserSwiftLint is integrated via SPM. Config in .swiftlint.yml.
# Run SwiftLint
swiftlint
# Auto-fix issues
swiftlint --fixMiniSim/
βββ Model/ # Data models (Device, Command, Platform, etc.)
βββ Service/ # Business logic and services
β βββ CustomErrors/ # Custom error types
β βββ Terminal/ # Terminal integration
βββ Views/ # SwiftUI views
β βββ Onboarding/ # Onboarding flow
β βββ CustomCommands/ # Custom commands UI
β βββ ParametersTable/ # Parameters management
βββ Extensions/ # Swift extensions
βββ MenuItems/ # NSMenu item implementations
βββ Components/ # Reusable UI components
βββ AppleScript Commands/ # AppleScript integration
- Sort imports alphabetically (enforced by SwiftLint
sorted_imports) - Group Foundation/AppKit imports first, then third-party
import AppKit
import Foundation
import UserNotifications- Types:
PascalCase(e.g.,DeviceService,ActionFactory) - Variables/functions:
camelCase - Enums:
PascalCasefor type,camelCasefor cases - Protocols: Suffix with
Protocolfor dependency injection (e.g.,ShellProtocol,ADBProtocol) idandIDare allowed as identifier names
- Use protocols for dependency injection and testability
- Prefer
structfor data models,classfor services with state - Use
final classwhen inheritance is not needed
protocol DeviceServiceCommon {
var shell: ShellProtocol { get set }
var device: Device { get }
func deleteDevice() throws
}
struct Device: Hashable, Codable {
var name: String
var identifier: String?
var booted: Bool
}- Define custom errors as enums conforming to
ErrorandLocalizedError - Group related errors in
Service/CustomErrors/ - Provide meaningful
errorDescriptionfor user-facing messages
enum DeviceError: Error, Equatable {
case deviceNotFound
case xcodeError
case unexpected(code: Int)
}
extension DeviceError: LocalizedError {
public var errorDescription: String? {
switch self {
case .deviceNotFound:
return NSLocalizedString("Selected device was not found...", comment: "")
}
}
}- Use MVVM pattern with nested
ViewModelclass - ViewModels extend from view type (e.g.,
CustomCommands.ViewModel) - Use
@Publishedfor observable state
extension CustomCommands {
class ViewModel: ObservableObject {
@Published var commands: [Command] = []
@Published var showForm = false
}
}- File naming:
TypeName+Feature.swift(e.g.,UserDefaults+Configuration.swift) - One extension purpose per file
- Use factory classes for creating platform-specific implementations
- Follow
ActionFactorypattern for iOS/Android differentiation
class AndroidActionFactory: ActionFactory {
static func createAction(for tag: SubMenuItems.Tags, ...) -> any Action {
switch tag {
case .copyName: return CopyNameAction(device: device)
}
}
}- Test files mirror source structure in
MiniSimTests/ - Use
@testable import MiniSim - Create stub/mock classes for dependencies (see
Mocks/ShellStub.swift) - Override class methods in nested test classes for mocking
class ADBTests: XCTestCase {
var shellStub: ShellStub!
override func setUp() {
shellStub = ShellStub()
ADB.shell = shellStub
}
}sorted_imports- alphabetical import orderingimplicit_return- omit return in single-expression closurestrailing_closure- use trailing closure syntaxvertical_whitespace_closing_braces- no blank lines before closing bracesshorthand_optional_binding- useif let xinstead ofif let x = xweak_delegate- delegates should be weak
nesting- nested types are allowed
- Add JSDoc-style comments for new public functions
- Focus on "why" not "what" for complex logic
- No comments in JSX/SwiftUI describing what renders
- Use
Thread.assertBackgroundThread()for background-only operations - Main thread assertions available via
Thread+Asserts.swift
KeyboardShortcuts- Global keyboard shortcutsLaunchAtLogin- Login item managementSparkle- Auto-updatesShellOut- Shell command executionCodeEditor- Code editing in preferencesSymbolPicker- SF Symbol pickerSettings- Preferences windowAcknowList- AcknowledgementsSwiftLint- Linting (build plugin)