TRENDSHIFT #19 딥다이브 · 2026-06-04

goose 딥다이브
— WHOOP 5.0 데이터를 '내 폰 안에서만' 해석하는 로컬 우선 헬스 앱

웨어러블 밴드를 쓰면 내 심박·수면·체온 데이터가 제조사 서버로 흘러가 그쪽 앱으로만 볼 수 있습니다. goose는 이 흐름을 끊습니다. WHOOP 5.0 밴드에 블루투스로 직접 붙어 원시 패킷을 받아오고, 그걸 Rust로 만든 계산 엔진으로 해석해 수면·회복·스트레인·스트레스 같은 지표를 전부 아이폰 안에서 그려냅니다. 데이터가 기기를 떠나지 않는 local-first(로컬 우선) 설계가 핵심입니다. (저장소: b-nnett/goose · Rust 73% + Swift 26% · iOS 26 SDK · 알파 PoC — 2026-06-13 TestFlight 베타 예정 · WHOOP 비공식·비제휴 프로젝트)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

goose가 정확히 무엇을 하는 물건인가

gooseWHOOP 5.0 피트니스 밴드의 데이터를 제조사 클라우드를 거치지 않고, 아이폰 안에서 직접 받아 해석하는 오픈소스 iOS 앱입니다. 밴드와는 블루투스(BLE)로 통신하고, 받아온 원시 데이터(패킷)는 Rust로 짠 계산 코어에 넘겨 수면 단계·회복 점수·스트레인(부하)·스트레스·심폐 부하·에너지 같은 건강 지표로 바꿉니다.

구조를 한 문장으로 줄이면 이렇습니다. SwiftUI 앱(화면)Rust 코어(두뇌)단 하나의 함수로 대화하고, 그 사이를 JSON 문자열이 오갑니다. 화면은 "수면 점수 계산해줘"라는 요청을 JSON으로 만들어 Rust에 던지고, Rust가 계산해서 JSON으로 답을 돌려주는 식이죠. 이렇게 UI는 Swift, 무거운 계산·검증은 Rust로 역할을 깔끔하게 나눈 것이 이 프로젝트의 정체성입니다.

한 컷 비유

"내 밴드, 내 데이터, 내 폰 안의 실험실"

보통의 웨어러블은 '밴드가 모은 데이터를 본사 공장(클라우드)으로 보내, 가공된 결과만 돌려받는' 방식입니다. 내 심박 원본이 어디서 어떻게 처리되는지 나는 알 수 없죠.

goose는 그 공장을 통째로 내 폰 안에 들여놓습니다. 밴드에서 받은 원시 패킷이 한 번도 인터넷을 타지 않고, 폰 안의 Rust 엔진이 직접 해석합니다. 데이터 주권을 사용자에게 돌려주려는 시도입니다.

중요한 단서: goose는 아직 알파(alpha) 단계의 개념 증명(PoC)입니다. README가 솔직하게 밝히듯, "Xcode가 뭔지, Rust 코어를 어떻게 빌드하는지 모른다면 이 빌드는 당신을 위한 것이 아니다"라고 적혀 있습니다. 일반 사용자용 앱이 아니라, 개발자가 '이 정도 규모의 프로젝트가 실현 가능한지' 평가하라고 공개한 코드라는 뜻입니다. 그래서 우리는 이걸 '완성품'이 아니라 '잘 설계된 아키텍처 표본'으로 공부합니다.

2왜 주목받는가

트렌딩 이유와 기존 방식 대비 강점

2025~2026년 개발자 커뮤니티의 큰 흐름 두 가지가 goose 안에서 만납니다. 하나는 "내 데이터는 내 기기에"라는 local-first·프라이버시 운동, 다른 하나는 "성능이 중요한 부분만 Rust로 빼고 UI는 네이티브로"라는 Rust↔네이티브 조합입니다. 웨어러블이라는 친숙한 주제에 이 둘을 정직하게 적용한 점이 화제가 됐습니다. TrendShift는 별 개수가 아니라 언급(mention) 모멘텀으로 순위를 매기기 때문에, 갓 공개된 알파 프로젝트도 #19에 오를 수 있습니다.

이 프로젝트가 긁어주는 실제 가려움

기존 방식의 불편함
내 건강 데이터인데 내가 통제할 수 없다

웨어러블의 원시 데이터는 제조사 서버로 가고, 사용자는 가공된 점수만 봅니다. 구독을 끊으면 데이터 접근이 막히기도 하고, 어떤 알고리즘으로 점수가 나왔는지도 블랙박스입니다. 분석을 직접 바꾸거나 검증하는 건 불가능에 가깝죠.

goose의 해법
밴드에 직접 붙고, 계산도 오픈소스로 내 폰에서

BLE로 밴드와 직접 통신해 원시 패킷을 가져오고, 그 해석 로직(수면 단계 판정, 회복 점수 등)을 읽고 검증할 수 있는 Rust 코드로 공개합니다. 데이터는 기본적으로 로컬에만 저장되고, 미래의 백엔드/AI 기능은 별도 동의 절차를 거치도록 못 박았습니다.

특히 인상적인 '데이터 정직성' 원칙

이 프로젝트가 단순한 취미 코드와 다른 결정적 지점은 가짜 데이터를 절대 보여주지 않겠다는 규율입니다. recovery-todo.md에는 이렇게 적혀 있습니다 — "회복 지표는 fixture(샘플)·예시 값을 쓰지 말 것. 신뢰할 수 있는 로컬 데이터가 생기기 전까지는 0이나 빈 상태(empty state)를 보여줄 것." 화면을 그럴듯하게 채우려고 임시 더미값을 넣는 흔한 유혹을 코드 규칙으로 금지한 셈입니다. 헬스 데이터처럼 신뢰가 생명인 분야에서 배울 만한 태도입니다.

경쟁 구도에서의 위치

비교 축제조사 공식 앱
(WHOOP 등)
범용 헬스 대시보드
(클라우드 동기화형)
goose
데이터 처리 위치제조사 클라우드외부 서버아이폰 로컬
원시 패킷 접근불가(블랙박스)불가가능(디버그·export)
알고리즘 공개비공개대개 비공개오픈소스 Rust
인터넷 필요필수필수기본 동작은 불필요
대상 사용자일반 소비자일반 소비자현재는 개발자(알파)
성숙도상용 제품상용 제품알파 PoC
짚고 넘어가기 — 합법성과 윤리

goose는 WHOOP과 제휴·연관이 없는 독립 프로젝트이며, WHOOP의 소스코드를 포함하거나 참조하지 않습니다. 밴드가 블루투스로 외부에 노출하는 서비스/데이터를 받아 로컬에서 해석할 뿐이고, 제품명은 "호환된다"는 사실을 설명하려는 용도로만 씁니다. 즉 '리버스 엔지니어링'이라기보다 기기가 공개한 신호를 내가 직접 읽어 쓰는 쪽에 가깝습니다. 그래도 상용 기기를 다루는 만큼, 학습 목적의 표본으로 접근하는 것이 안전합니다.

3기술 스택 전체 지도

프론트엔드(SwiftUI 앱) · 코어(Rust) · 그 사이를 잇는 FFI · 빌드 인프라

goose는 두 개의 언어로 된 한 몸입니다. 사람이 보는 화면은 전부 Swift/SwiftUI, 데이터를 해석하는 두뇌는 전부 Rust. 그리고 둘을 잇는 가느다란 다리(FFI)가 있습니다. 언어 비율이 Rust 73% / Swift 26%인 이유는, 화면보다 '데이터를 어떻게 정확히 해석하느냐'에 코드의 무게가 실려 있기 때문입니다.

① 프론트엔드 — SwiftUI 앱 (GooseSwift/)

사용자가 만지는 모든 것. 탭 4개(Home·Health·Coach·More)로 구성된 네이티브 iOS 앱입니다.

분류기술역할
UI 프레임워크SwiftUI선언형으로 건강 대시보드·추세 그래프·시트(sheet) 구성
블루투스CoreBluetoothWHOOP 5.0 밴드 스캔·연결·동기화 (GooseBLEClient.swift)
건강 연동HealthKit수면 데이터 가져오기(import) + 운동 기록 쓰기(write)
앱 상태GooseAppModel앱 전역 상태·BLE 소유권·생명주기·코어 요약 관리
위젯ActivityKit (Live Activity)운동 중 잠금화면/다이내믹 아일랜드 실시간 표시

② 코어 — Rust 계산 엔진 (Rust/core/)

이 프로젝트의 진짜 본체. 패킷 파싱부터 수면 단계 판정, 회복 점수, 검증·내보내기까지 모든 '판단'이 여기 모여 있습니다. Cargo.toml을 뜯어보면 규모가 드러납니다.

분류크레이트(라이브러리)역할
패킷 무결성crc32fast받은 패킷이 깨지지 않았는지 CRC32 체크섬으로 검증
로컬 저장소rusqlite (bundled SQLite)패킷·지표를 폰 안의 SQLite DB에 저장 (서버 없음)
직렬화serde, serde_jsonSwift와 주고받는 JSON 인코딩/디코딩
해시sha2, hex데이터 지문(fingerprint)·중복 판정
에러 처리thiserror타입 안전한 에러 정의(GooseError)
디버그 채널tungstenite (WebSocket)개발 중 코어 상태를 실시간 들여다보는 디버그 서버
내보내기zip원시 데이터를 압축 묶음으로 export

릴리스 빌드 설정도 임베디드/모바일을 의식했습니다 — opt-level=3(최대 최적화), lto="thin"(링크 타임 최적화), codegen-units=1(최적화 우선), panic="abort"(패닉 시 즉시 종료해 바이너리 축소), strip="debuginfo"(디버그 심볼 제거). 앱에 들어갈 정적 라이브러리를 작고 빠르게 만들기 위한 전형적인 조합입니다.

③ 둘을 잇는 다리 — FFI (C ABI)

Swift와 Rust는 서로의 함수를 직접 부를 수 없습니다. 그래서 둘 다 이해하는 C 언어 규약(C ABI)을 중간 다리로 씁니다. 놀랍도록 단순합니다 — 헤더 파일(goose_core_bridge.h)에 함수가 딱 3개뿐입니다.

// goose_core_bridge.h — Swift와 Rust를 잇는 전체 인터페이스
char *goose_core_version_json(void);              // 버전 정보(JSON)
char *goose_bridge_handle_json(const char *req);  // 요청(JSON) → 응답(JSON)
void  goose_bridge_free_string(char *value);      // Rust가 만든 문자열 메모리 해제

핵심은 가운데 함수 하나입니다. "JSON 문자열을 넣으면 JSON 문자열이 나온다" — 모든 기능(수면 계산, 패킷 가져오기, 검증 등)이 이 단일 창구를 통합니다. 함수를 기능마다 수십 개 만드는 대신, 메서드 이름을 JSON 안에 담아 보내는 방식이죠. (자세한 동작은 4장에서)

④ 빌드 인프라 — Cargo + Xcode 결합

분류기술역할
Rust 빌드Cargo + rustup 타깃aarch64-apple-ios(실기기), aarch64-apple-ios-sim·x86_64-apple-ios(시뮬레이터)
빌드 연결Scripts/build_ios_rust.shXcode 빌드 단계에서 자동 실행 → Rust를 컴파일해 .a 정적 라이브러리 생성
증분 빌드타깃/프로파일 마커 + find -newer소스가 안 바뀌었으면 재빌드 건너뛰기
앱 빌드xcodebuild / XcodeSwift 컴파일 + Rust .a 링크 → 최종 앱

빌드 산출물(.a 아카이브)은 저장소에 커밋하지 않고 빌드할 때마다 로컬에서 생성합니다. 소스 트리를 깨끗하게 유지하려는 의도입니다.

4아키텍처 심화 분석

밴드에서 화면까지, 데이터가 흐르는 길 + 핵심 설계 패턴

전체 데이터 흐름 한 장

[ WHOOP 5.0 밴드 ] │ ① BLE(블루투스 저전력)로 원시 패킷 전송 ▼ ┌─────────────────────────────────────────────┐ │ GooseBLEClient.swift (CoreBluetooth) │ ← Swift 영역 │ 스캔 · 연결 · 패킷 수신 · 동기화 │ └───────────────┬─────────────────────────────┘ │ ② 패킷을 JSON 요청으로 포장 ▼ ┌─────────────────────────────────────────────┐ │ GooseRustBridge.swift (FFI 래퍼) │ │ JSON 문자열 ──► goose_bridge_handle_json() ──┼──┐ └─────────────────────────────────────────────┘ │ ③ C ABI 경계 통과 ▼ ┌─────────────────────────────────────────────┐ │ Rust 코어 (libgoose_core.a) │ ← Rust 영역(두뇌) │ protocol → capture_import → sanitize → │ │ correlation → metrics(sleep/recovery/strain) │ │ → store(SQLite) │ └───────────────┬─────────────────────────────┘ │ ④ 계산 결과를 JSON으로 반환 ▼ ┌─────────────────────────────────────────────┐ │ HealthView / CoachView (SwiftUI) │ │ 점수 · 추세 그래프 · 코치 설명 렌더 │ └─────────────────────────────────────────────┘ │ ⑤ 선택적으로 HealthKit에 수면/운동 기록 ▼ [ Apple 건강 앱 ] ※ 인터넷·외부 서버 경유 없음

핵심 패턴 ① — "함수 하나로 모든 것": JSON-RPC over FFI

언어가 다른 두 세계를 잇는 가장 흔한 실수는 기능마다 FFI 함수를 만드는 것입니다. 그러면 함수가 수십 개로 불어나고, 타입 변환 코드도 그만큼 늘어납니다. goose는 정반대로 갑니다 — 창구를 하나로 통일하고, "무엇을 할지"는 JSON 안에 적어 보냅니다. 마치 웹 API가 하나의 엔드포인트에 메서드 이름을 담아 호출하는 RPC와 같은 발상입니다.

// Swift가 Rust에 보내는 요청 봉투 (GooseRustBridge.swift)
{
  "schema": "goose.bridge.request.v1",
  "request_id": "goose-swift-1717459200.5-7",  // 타임스탬프+카운터로 추적
  "method": "sleep.compute",                     // 무엇을 할지
  "args": { ... }                                // 입력값
}

// Rust가 돌려주는 응답 봉투
{
  "ok": true,                 // 성공/실패 플래그
  "result": { ... },          // 결과(성공 시)
  "error": { "message": ... },// 에러(실패 시)
  "timing": { "method": "sleep.compute", "method_elapsed_us": 1234 }
}

Swift 쪽 래퍼는 이 봉투를 만들고, 문자열로 바꿔 goose_bridge_handle_json()에 넘긴 뒤, 돌아온 문자열을 다시 객체로 파싱합니다. 이때 반드시 지켜야 하는 규칙이 하나 있습니다.

핵심 개념
FFI 메모리 소유권 (ownership across the boundary)
Rust가 만들어 돌려준 문자열의 메모리는 Swift가 마음대로 해제하면 안 됩니다. 만든 쪽(Rust)이 해제해야 하죠. 그래서 Swift는 결과를 다 읽은 뒤 goose_bridge_free_string()을 호출해 Rust에게 "이제 지워도 돼"라고 알립니다. 이걸 빼먹으면 메모리 누수, 잘못 부르면 크래시가 납니다. goose는 Swift의 defer로 "함수가 끝나면 무조건 free" 를 보장합니다.
// 소유권을 안전하게 넘겨받는 Swift 패턴
var responsePointer: UnsafeMutablePointer<CChar>?
request.withCString { pointer in
  responsePointer = goose_bridge_handle_json(pointer)
}
guard let responsePointer else { throw .nullResponse }
defer { goose_bridge_free_string(responsePointer) }  // ★ 끝나면 무조건 해제
let responseText = String(cString: responsePointer)

핵심 패턴 ② — 경계 비용을 '재는' 습관

흥미롭게도 goose의 브리지는 단순히 호출만 하지 않고, 매 호출의 시간 비용을 마이크로초 단위로 측정합니다. 요청을 JSON으로 만드는 데(encode), C 경계를 넘나드는 데(ffi round-trip), 응답을 파싱하는 데(decode) 각각 얼마가 걸렸는지를 기록하죠. 그리고 Rust가 알려주는 "순수 계산 시간(method_elapsed_us)"과 비교합니다.

왜 중요한가

두 언어를 오갈 때 진짜 비용은 '계산'이 아니라 경계를 넘는 행위(직렬화·복사)일 때가 많습니다. goose는 "Rust 계산은 0.2ms인데 JSON 인코딩에 1ms 썼다"처럼 병목이 다리에 있는지 두뇌에 있는지를 숫자로 보여줍니다. 성능을 추측이 아니라 측정으로 다루는 좋은 습관의 표본입니다.

핵심 패턴 ③ — 패킷을 '신뢰 가능한 지표'로 정제하는 파이프라인

Rust 코어의 lib.rs가 노출하는 40여 개 모듈을 보면, 원시 패킷이 화면에 뜨는 점수가 되기까지의 단계가 보입니다. 데이터를 함부로 믿지 않고 여러 검증 관문을 통과시킵니다.

protocol 패킷 구조 해석(파싱) + CRC32 무결성 검사 └▶ capture_import 원시 캡처를 코어 저장소로 들여오기 └▶ capture_sanitize 이상치·잡음 제거(정제) └▶ capture_correlation 여러 신호의 시간축 정렬·상관 └▶ metrics sleep / recovery / strain / stress / energy 산출 │ ├ sleep_validation 수면 단계 라벨 타당성 검증 │ ├ recovery_rollup 회복 점수 집계 │ ├ energy_rollup 에너지(배터리) 집계 │ └ step_motion_estimator 걸음·동작 추정 └▶ store(SQLite) 로컬 DB 저장 + timeline 구성 └▶ export / privacy_lint 내보내기 + 민감정보 점검

특히 눈에 띄는 모듈들: metric_readiness(어떤 지표가 '계산 가능한 입력'을 갖췄는지 먼저 판정), privacy_lint(내보낼 데이터에 민감정보가 섞이지 않았는지 자동 점검), openwhoop_reference(WHOOP 프로토콜에 대한 오픈소스 참고 구현과의 대조), property_tests(무작위 입력으로 알고리즘 성질을 검증하는 속성 기반 테스트). 데이터 정직성이라는 원칙이 모듈 구조에까지 새겨져 있습니다.

5디렉토리 구조 해부

어디에 무엇이 들어있나
goose/ ├─ GooseSwift.xcodeproj Xcode 프로젝트 파일 ├─ GooseSwift/ ◀ SwiftUI 앱 소스 (화면·로직) │ ├─ GooseSwiftApp.swift 앱 진입점 · 생명주기 · 딥링크 처리 │ ├─ RootView.swift 온보딩 게이트 · 동기화 토스트 호스트 │ ├─ AppShellView.swift 탭 셸(Home/Health/Coach/More) 연결 │ ├─ GooseAppModel.swift 앱 전역 상태 · BLE 소유권 · 코어 요약 │ ├─ GooseBLEClient.swift 블루투스 스캔/연결/동기화 │ ├─ GooseRustBridge.swift ◀ Rust C 브리지 Swift 래퍼(FFI 핵심) │ ├─ HealthView.swift, Health* 건강 대시보드 · 지표 페이지 · 추세 │ ├─ CoachView.swift, Coach* 코치 UI · 채팅 │ └─ MoreView.swift 설정 · 디버그 · 캡처 · 프라이버시 ├─ GooseWorkoutLiveActivityExtension/ ◀ 운동 Live Activity 위젯 ├─ Rust/ ◀ Rust 코어 + 플랫폼별 산출물 │ ├─ core/ 실제 Rust 소스 │ │ ├─ Cargo.toml 크레이트 정의 + 30여 개 진단 바이너리 │ │ ├─ src/lib.rs 40여 개 모듈 선언(두뇌의 목차) │ │ └─ include/goose_core_bridge.h C 브리지 헤더(함수 3개) │ ├─ iphoneos/libgoose_core.a (빌드 시 생성, 미커밋) │ └─ iphonesimulator/libgoose_core.a (빌드 시 생성, 미커밋) ├─ Scripts/build_ios_rust.sh ◀ Xcode 빌드 단계: Rust→.a 자동 빌드 ├─ docs/goose-swift-mvp/ MVP 계획 · 화면 계약 · 데이터 준비 문서 ├─ recovery-todo.md 회복 지표 잔여 작업(가짜값 금지 규칙) └─ README.md

구조에서 읽히는 설계 의도가 분명합니다. 화면(GooseSwift)과 두뇌(Rust/core)가 폴더 단위로 완전히 분리되어 있고, 둘 사이는 GooseRustBridge.swiftgoose_core_bridge.h라는 좁고 명확한 한 지점으로만 연결됩니다. 또한 docs/goose-swift-mvp/에 각 탭의 "계약(contract)"과 수용 기준(acceptance check)을 문서로 남겨, 화면이 어떤 데이터를 어떤 상태로 보여줘야 하는지 미리 못 박아둔 점도 학습할 만합니다.

눈여겨볼 디테일
진단용 바이너리 30여 개 (Cargo.toml[[bin]])
앱에 들어가는 라이브러리 외에, Rust 코어에는 goose-sleep-window-validator, goose-algo-benchmark, goose-privacy-lint, goose-perf-budget 같은 커맨드라인 진단 도구가 30개 넘게 딸려 있습니다. 알고리즘을 앱과 분리해 터미널에서 단독으로 검증·벤치마크할 수 있게 한 것입니다. "UI 없이도 코어가 옳은지 증명할 수 있다"는 테스트 가능성(testability)의 모범 사례죠.

6학습 포인트 (기술별)

이 레포에서 무엇을 훔쳐 배울 수 있는가

① Rust ↔ Swift FFI — '단일 JSON 창구' 패턴

가장 값진 교보재입니다. 서로 다른 언어를 붙일 때, 함수를 잘게 쪼개는 대신 JSON-in/JSON-out 함수 하나 + 메모리 해제 함수 하나로 끝내는 설계를 실제 코드로 볼 수 있습니다. C ABI, extern "C", UnsafeMutablePointer, withCString, defer 기반 메모리 해제까지 한 흐름으로 익힙니다.

실습 아이디어: 빈 Rust 라이브러리에 handle_json(req)->resp 함수 하나만 만들고, 파이썬/Node의 ctypes·ffi로 호출해 "ping→pong" JSON을 왕복시켜 보기.

② CoreBluetooth — 실제 기기와 BLE로 대화하기

GooseBLEClient.swift는 스캔 → 연결 → 서비스 탐색 → 패킷 수신 → 동기화라는 BLE의 전형적 수명주기를 담고 있습니다. central/peripheral, GATT 서비스·특성(characteristic), 알림(notify) 구독 같은 개념을 현실 기기 맥락에서 배울 수 있습니다.

③ local-first 아키텍처 — 서버 없이 SQLite로

백엔드가 없습니다. rusqlite로 폰 안에 SQLite DB를 두고 모든 상태를 로컬에 보관합니다. "동기화 서버가 진실의 원천"이 아니라 "내 기기가 진실의 원천"인 설계를 공부하기 좋습니다.

④ 데이터 무결성·정직성 규율

CRC32 체크섬, sanitize/correlation 단계, metric_readiness(입력이 충분할 때만 계산), property test, 그리고 "신뢰 데이터 없으면 빈 상태"라는 규칙. 사용자를 속이지 않는 데이터 UI를 어떻게 코드 차원에서 강제하는지 배웁니다.

⑤ iOS Live Activity (ActivityKit)

GooseWorkoutLiveActivityExtension은 운동 중 잠금화면·다이내믹 아일랜드에 실시간 정보를 띄우는 위젯입니다. 앱 본체와 익스텐션이 데이터를 공유하는 구조를 엿볼 수 있습니다.

⑥ 크로스-언어 빌드 파이프라인

build_ios_rust.sh 한 편이 작은 교과서입니다. 플랫폼별 Rust 타깃 선택(실기기 arm64 vs 시뮬레이터), xcrun으로 SDK 경로·clang·ar 찾기, 타깃별 환경변수(CC_*, CFLAGS_*, *_LINKER) 설정, 그리고 마커 파일 + find -newer로 불필요한 재빌드 건너뛰기까지. Xcode 빌드 단계에 외부 툴체인을 끼워 넣는 실전 기법입니다.

7하드웨어 / 시스템 요구사항

직접 빌드하려면 무엇이 필요한가
항목요구사항비고
개발 머신macOS + XcodeiOS 네이티브 빌드는 사실상 맥 전용
SDKiOS 26 SDK + iOS 26 시뮬레이터/기기비교적 최신 SDK 요구
서명Apple Developer 서명(com.goose.swift)실기기 설치 시 프로비저닝 필요
Rust 툴체인rustup + Cargoedition 2024 / rust-version 1.94
Rust 타깃aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-iosrustup target add로 설치
실제 기기WHOOP 5.0 밴드이 빌드는 5.0 세대만 지원
# iOS용 Rust 타깃 설치
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios

# 시뮬레이터 빌드 (커맨드라인)
xcodebuild -project GooseSwift.xcodeproj -scheme GooseSwift \
  -configuration Debug \
  -destination 'platform=iOS Simulator,name=iPhone 17' build
현실적 제약
알파 PoC라 '느리고 비어 있을 수' 있다

README가 직접 경고합니다 — 성능 최적화가 거의 안 되어 있어 앱이 상당히 버벅일 수 있고, 데이터 파이프라인이 진화 중이라 일부 지표는 입력 소스가 생기기 전까지 빈 값/사용 불가로 표시됩니다. 또 WHOOP 5.0 실물 밴드가 없으면 BLE 기능은 시험할 수 없습니다(코어 알고리즘은 fixture로 단독 검증 가능).

8직접 해볼 수 있는 실습 과제

밴드가 없어도 대부분 가능 — 난이도별 5개
난이도 ★☆☆ — 입문

1. "JSON 창구" 미니 브리지 만들기

Rust로 handle_json(req: &str) -> String 함수 하나를 만들고, {"method":"echo","args":{"msg":"hi"}}를 받으면 {"ok":true,"result":{"msg":"hi"}}를 돌려주게 합니다. 그다음 파이썬 ctypes로 이 .so/.dylib를 불러 왕복시켜 보세요. goose의 핵심 패턴을 가장 작게 재현하는 과제입니다.

난이도 ★☆☆ — 입문

2. CRC32로 '패킷 무결성' 체험하기

crc32fast로 임의의 바이트 묶음에 체크섬을 붙이고, 한 바이트만 바꿨을 때 체크섬이 달라지는 걸 확인합니다. goose가 밴드 패킷을 어떻게 "깨졌는지" 판별하는지 감을 잡습니다.

난이도 ★★☆ — 중급

3. 정제 파이프라인 흉내 내기

가짜 심박 시계열(노이즈·결측 포함)을 만들고 sanitize → correlation → metric_readiness 흐름을 직접 구현합니다. "입력이 충분할 때만 점수를 내고, 아니면 빈 상태"라는 규칙을 코드로 강제해 보세요. goose의 데이터 정직성 원칙을 체득합니다.

난이도 ★★☆ — 중급

4. CoreBluetooth로 주변 BLE 기기 스캔

WHOOP이 없어도 됩니다. 빈 SwiftUI 앱에서 CBCentralManager로 주변 BLE 기기를 스캔해 이름·RSSI를 목록에 뿌려 보세요. GooseBLEClient.swift의 첫 단계(스캔/발견)를 이해하는 가장 빠른 길입니다.

난이도 ★★★ — 고급

5. Rust 코어를 iOS 정적 라이브러리로 링크하기

작은 Rust 크레이트를 crate-type=["staticlib"]로 만들고, build_ios_rust.sh를 참고해 aarch64-apple-ios-sim 타깃으로 .a를 빌드한 뒤 SwiftUI 앱에 링크해 함수 하나를 호출합니다. goose 빌드 파이프라인 전체를 축소판으로 재현하는 종합 과제입니다.

9관련 기술 심화 학습 로드맵

goose를 온전히 소화하기 위한 4주 코스
주차주제학습 내용 · 산출물
1주차Rust 기초 + Cargo소유권·빌림·Result·thiserror · serde_json으로 직렬화 · [[bin]]로 작은 CLI 만들기
2주차FFI / C ABIextern "C", #[no_mangle], CString/포인터 · 메모리 소유권 규칙 · 파이썬에서 호출 → 실습 1 완성
3주차SwiftUI + CoreBluetooth탭/뷰/상태(@State·@Observable) · CBCentralManager 스캔/연결 → 실습 4 완성
4주차크로스-언어 빌드 + 통합Rust 정적 라이브러리를 Xcode에 링크 · 빌드 스크립트 작성 · JSON 브리지로 Swift↔Rust 왕복 → 실습 5 완성

여유가 된다면 5주차로 HealthKit(수면 import·운동 write)과 ActivityKit(Live Activity)을 붙여, goose의 주변부 기능까지 직접 만들어 보면 전체 그림이 완성됩니다.

10핵심 키워드 사전

이 문서에 나온 용어 빠르게 정리
용어
local-first (로컬 우선)
데이터의 원본과 처리를 사용자 기기에 두고, 클라우드는 (있다면) 보조로만 쓰는 설계. goose는 밴드 데이터를 외부 서버 없이 폰 안에서 해석·저장합니다.
용어
FFI (Foreign Function Interface)
한 언어에서 다른 언어로 짠 함수를 호출하는 통로. goose는 Swift→Rust 호출을 위해 둘 다 이해하는 C 규약(C ABI)을 다리로 씁니다.
용어
C ABI
함수 호출 규약·메모리 배치에 대한 C 언어의 표준. 거의 모든 언어가 이걸 이해하므로, 언어 간 연결의 '공용어' 역할을 합니다.
용어
BLE (Bluetooth Low Energy)
저전력 블루투스. 웨어러블·센서가 배터리를 아끼며 통신할 때 쓰는 방식. iOS에서는 CoreBluetooth로 다룹니다.
용어
GATT 서비스 / 특성(characteristic)
BLE 기기가 데이터를 노출하는 구조. '서비스'라는 묶음 안에 '특성'이라는 값들이 있고, 앱은 특성을 읽거나 알림(notify)으로 구독합니다.
용어
패킷(packet) · CRC32
패킷은 기기가 보내는 데이터 한 조각. CRC32는 그 조각이 전송 중 깨졌는지 빠르게 확인하는 체크섬(검증값)입니다.
용어
정적 라이브러리(.a) / staticlib
미리 컴파일된 코드 묶음을 앱 빌드 시 통째로 끼워 넣는 형태. goose는 Rust 코어를 libgoose_core.a로 만들어 Swift 앱에 링크합니다.
용어
defer (Swift)
"이 블록이 끝날 때 반드시 실행" 보장. goose는 defer로 Rust가 준 문자열 메모리 해제를 빠뜨리지 않게 합니다.
용어
property test (속성 기반 테스트)
정해둔 입력 몇 개가 아니라 무작위 입력 다수를 던져 "항상 성립해야 하는 성질"이 깨지는지 찾는 테스트. goose는 알고리즘 검증에 씁니다.
용어
Live Activity (ActivityKit)
앱을 열지 않아도 잠금화면·다이내믹 아일랜드에 실시간 정보를 띄우는 iOS 기능. goose는 운동 중 상태 표시에 사용합니다.

11참고 링크

더 깊이 파고들 자료