본문으로 건너뛰기

Swift Weekly #159: 서로 다른 플랫폼의 앱을 한 데스크톱에서 실행하기

Fatbobman's Swift Weekly #159

weekly.fatbobman.com

핵심 요약

  1. Cutefish의 Reef는 Mach-O 로더와 Apple 프레임워크를 Linux에서 재구현해 수정하지 않은 Mac 앱을 직접 실행하며, 현재 구현의 성능 문제도 함께 다룬다.
  2. SimRun은 iOS 시뮬레이터용 앱을 macOS의 개별 창에서 실행하고 검사·자동화 기능을 제공하지만, Apple Silicon과 Xcode가 필요하며 SIP를 꺼야 한다.
  3. Mac Android Subsystem은 LineageOS 가상 환경의 앱을 Mac 창에 표시한다. 세 프로젝트는 구현 방식은 달라도 앱을 호스트 시스템에 자연스럽게 통합하려 한다.

도입

이번 호는 서로 다른 운영체제용 앱을 한 데스크톱에서 실행하려는 세 프로젝트를 중심으로 구성됐다. Cutefish는 Linux에서 Mac 앱을 위한 호환 계층을 구현하고, SimRun은 시뮬레이터용 iOS 앱을 macOS 창에서 실행한다. Mac Android Subsystem은 가상화한 Android 환경을 백그라운드에서 구동한다. 이와 함께 촉각·오디오 처리, SwiftUI와 AppKit의 뷰 생명주기, 실시간 음성 AI, 코딩 에이전트 관련 글도 소개한다.

상세 요약

플랫폼 경계를 넘는 세 프로젝트

이번 호의 중심은 서로 다른 운영체제용 앱을 데스크톱 환경에 통합하는 세 프로젝트다.

  • Cutefish와 Reef는 가상화나 명령어 에뮬레이션 대신 호환 계층을 구현한다. Reef는 Mach-O 로더, Objective-C Runtime, Foundation, AppKit, Core Graphics, SwiftUI를 재구현해 수정하지 않은 Mac 앱을 Linux에서 실행하는 것을 목표로 한다. 현재 구현에는 로컬 상태가 바뀔 때 뷰 트리 전체를 다시 만들거나 스크롤 중 중복 렌더링이 발생하는 성능 문제가 있다.
  • SimRun은 Xcode가 iOS 시뮬레이터용으로 빌드한 앱을 시뮬레이터 기기 전체를 띄우지 않고 macOS의 독립 창에서 실행한다. 명령줄 인터페이스, 스크린샷, 뷰 계층 검사, 실험적인 JavaScript 자동화 API를 제공하며 창 없이 앱을 실행할 수도 있다. Apple Silicon Mac, Xcode, 시뮬레이터 런타임이 필요하고 SIP를 비활성화해야 한다. 시뮬레이터 기반 앱 자동화와 검사에 관한 사례는 RocketSim과 RocketTrace로 구현하는 에이전트 기반 iOS 개발에서도 다룬다.
  • **Mac Android Subsystem(MAS)**은 LineageOS, QEMU, Apple Hypervisor Framework로 Android 환경 전체를 백그라운드에서 실행하고, scrcpy로 앱을 Mac의 개별 창에 표시한다. APK를 두 번 클릭해 설치할 수 있고 앱별 Dock 아이콘, Spotlight 검색, ⌘-Tab 전환, 알림 센터, 양방향 클립보드를 지원한다. 초기 프리릴리스이며 Apple Silicon과 macOS 15 이상이 필요하고 Google Play 스토어와 Google 서비스는 포함하지 않는다.

앱 개발과 실행의 세부 사례

촉각 피드백 관련 글은 UIKit의 Feedback Generators, SwiftUI의 .sensoryFeedback, Core Haptics를 비교한다. 시스템 피드백, 사용자 정의 패턴, 동적 매개변수, AHAP 파일을 다루며, 시뮬레이터에서 촉각을 체험할 수 없는 문제를 보완하기 위해 iPhone에서 패턴을 비교하고 Swift 코드를 확인하는 Haptics Explorer도 소개한다.

배경 음악과 음성 내레이션을 함께 재생할 때는 음성 시작·종료에 맞춰 음악 음량을 즉시 바꾸면 변화가 급격하거나 자연스러운 말의 간격마다 음량이 오르내릴 수 있다. 이를 완화하기 위해 음악을 빠르게 낮추고 천천히 복원하는 비대칭 페이드, Generation Token, 지연 시간을 활용한다.

SwiftUI와 AppKit을 함께 쓸 때는 dismantleNSView가 호출된 뒤에도 중첩된 NSHostingView가 후속 레이아웃 과정에서 자식의 makeNSView를 다시 호출할 수 있다. 사례에서는 이 동작으로 이미 Canvas에 넘긴 Surface를 보이지 않는 호스트가 다시 가져갔다. 해결 과정에서 최소 재현과 버전 이분 탐색을 수행했고, 분해 단계에서 내부 Hosting View를 명시적으로 제거하는 한편 공유 Surface의 소유권 규칙도 보완했다.

에이전트, 음성 AI, 개발 환경

Fud AI의 Live Voice는 25개 앱 동작을 하나의 JSON 도구 계약으로 정의해 iOS, Android, Cloudflare Worker가 같은 인터페이스를 사용하도록 한다. Gemini Live, OpenAI Realtime, xAI, Qwen의 오디오 형식·도구 호출·시각 입력 차이는 별도 Adapter가 처리한다. 도구는 읽기, 탐색, 쓰기, 파괴적 작업으로 나뉜다. 쓰기 작업에는 8초의 실행 취소 시간이 있고, 파괴적 작업은 사용자 확인을 요구한다. 기본 설정에서는 일반 쓰기도 확인을 거친다.

다른 글은 코딩 에이전트가 테스트와 검증을 수행하는 방식, 모바일 기기·음성·Live Activities를 개발 진입점으로 삼는 구상, Claude Code의 초기 컨텍스트 사용량을 다룬다. /context all로 프롬프트 입력 전 점유량을 확인할 수 있으며, 시스템 프롬프트와 도구 정의 외에도 CLAUDE.md, Rules, Skills, 사용자 정의 Agents가 포함된다. 지연 로딩 MCP 도구는 초기 점유량에 포함되지 않는다. /skill-doctor와 /doctor prompt-audit로 중복되거나 오래된 지침을 찾고, 실제 환경을 측정한 뒤 정리 대상을 결정하라는 내용이다. 컨텍스트 점유량과 요청 비용은 같지 않으며 Prompt Caching은 반복 내용의 비용을 줄일 수 있다.

결론

세 프로젝트는 앱을 실행하는 데 그치지 않고 창, Dock, 검색, 알림, 클립보드 같은 호스트 환경과의 접점을 다룬다. 다만 현재 제약도 분명하다. Reef는 뷰 갱신과 스크롤 성능 문제가 있고, SimRun은 특정 Mac·Xcode 환경과 SIP 비활성화를 요구하며, MAS는 초기 프리릴리스이고 Google 서비스가 없다. 이번 호의 사례는 실행 방식과 통합 수준을 구분해 평가해야 함을 보여준다.