// nilかもしれない値はOptional型で宣言する
var userImage: UIImage? = nil // ?がOptionalを示す
// 使う前に必ずアンラップしなければならない
if let image = userImage {
// ここでは image が確実に存在する
displayImage(image)
} else {
showPlaceholder()
}
// Optional無視して直接使おうとすると?
let w = userImage.size.width
// → Compile error:
// Value of optional type 'UIImage?'
// must be unwrapped first
// → 実行前に、コンパイル時点で止まる。
> SwiftUI is a state machine.
> @State = current state register
> body = output function
> view update = state transition
>
> // 1985年に書いた状態遷移図が、
> // iPhoneの上で動いている。
In autumn 2025, I decided to build an iOS app for kitemir.jp's virtual try-on feature.
The reasoning was simple. A smartphone app would reach more people. Being able to virtually try on clothes while standing in front of a store — that changes the shopping experience entirely. That alone was worth building.
The day I installed Xcode, my first thought was: "This thing is huge." Same feeling as the first time I saw a mainframe OS. Menus, windows, panels spread across the entire screen. No idea where to begin.
But I'd felt that before. And I'd eventually mastered it. This time would be no different.
$ xcode-select --install
$ swift --version
// Apple Swift version 5.10
// Target: arm64-apple-macosx14.0
//
// My first new compiler in 40 years.
Swift Optionals — Ending 40 Years of Null Pointer Hell
In assembly language, null pointer checking was the developer's responsibility. Always. No exceptions.
; Null check in assembly
MOV AX, [ptr] ; Load pointer value
CMP AX, 0 ; Is it NULL?
JE null_error ; Jump to error handler if NULL
; ──────────────────────────────────────────
; Forget this? → Segmentation fault.
; No one tells you until runtime.
Even after moving to C, null reference bugs were an eternal problem. I can't count how many code reviews I spent specifically hunting for missing null checks.
When I first encountered Optional<T> in Swift, my reaction was: "What unnecessary verbosity."
// Values that might be nil must be declared Optional
var userImage: UIImage? = nil // ? marks it as Optional
// Must always unwrap before use
if let image = userImage {
// image is guaranteed non-nil here
displayImage(image)
} else {
showPlaceholder()
}
// Try to use it directly?
let w = userImage.size.width
// → Compile error:
// Value of optional type 'UIImage?'
// must be unwrapped first
// → Stopped before runtime. Every time.
What I'd spent 40 years enforcing through human attention — the type system now guarantees. Even if you forget, something stops you. Before the program even runs.
> INSIGHT: Optional<T> is not overhead.
> It's 40 years of NULL pointer bugs,
> eradicated at compile time.
>
> // The compiler became my code reviewer.
SwiftUI — Declaring "What Should Be," Not "How to Do It"
Assembly is the ultimate imperative language. You instruct the CPU: do this, then this, then this. Drawing a pixel on screen meant calculating coordinates, setting color values, writing to the frame buffer — every step explicit.
SwiftUI was the opposite philosophy entirely.
// Just declare what the screen should look like
struct HomeView: View {
@State var products: [Garment] = []
var body: some View {
VStack {
Text("Virtual Try-On")
.font(.title)
.foregroundColor(.purple)
LazyVGrid(columns: columns) {
ForEach(products) { product in
ProductCard(garment: product)
}
}
}
}
}
// No "draw a rectangle." No "update this label."
// Declare what should exist.
// @State changes → UI automatically updates.
At first, it felt wrong. The screen gets drawn without a "draw" command. State propagates automatically. What is happening?
Then I recognized the pattern.
State machine.
Same architecture as the state transition diagrams I wrote for control programs decades ago. @State is the current state register. body is the output function: "when in this state, the output should be this." State transition triggers automatic output update.
> SwiftUI is a state machine.
> @State = current state register
> body = output function
> view update = state transition
>
> // The same diagram I drew in 1985
> // is running on an iPhone.
The .pbxproj Trap — The Invisible Linker Script
The biggest pitfall I hit during iOS development: the Xcode project file (.pbxproj).
In assembly and C development, adding a source file meant explicitly registering it — in the Makefile, in the linker script. "The file exists" and "the file is included in the build" were always separate things.
Swift turned out to be exactly the same. Dropping a .swift file into a folder in Finder doesn't mean Xcode knows about it. You need to manually add it to .pbxproj in four separate places.
// Inside .pbxproj (partial)
/* PBXBuildFile section */
ABC123 /* NewView.swift in Sources */ = {
isa = PBXBuildFile;
fileRef = DEF456 /* NewView.swift */;
};
/* PBXFileReference section */
DEF456 /* NewView.swift */ = {
isa = PBXFileReference;
lastKnownFileType = sourcecode.swift;
path = NewView.swift;
sourceTree = "<group>";
};
// + Add to group section
// + Add to Sources build phase
// ──────────────────────────────────────
// 4 total locations. Miss one and...
The error message was brutally unhelpful: "Cannot find type 'NewView' in scope." The file existed. The code was correct. Why wouldn't it build?
Three hours later, I found the cause. And I remembered something from forty years earlier.
A junior engineer — me — forgetting to add a file to the OBJECTS list in a linker script, then spending an afternoon debugging "symbol not found." The exact same mistake. At age 68.
ld: symbol not found (1985)
Cannot find type in scope (2025)
40 years later, the build system still says: "Register your files properly."
The Road to App Store — Smells Like Device Driver Certification
Shipping a device driver required certification. Test against specifications. Pass the review. Only then could it ship inside a product. That was the process, and it was completely normal to us.
App Store submission was similar, yet different. Apple Developer Program registration. Bundle ID configuration. Certificates, Identifiers, Profiles. Signing key management. Provisioning profile setup in Xcode.
But the underlying principle is the same: "Prove it's trustworthy before it ships."
The first time my build ran on a real device — when SplashView appeared on an actual iPhone — I felt the same achievement I felt decades ago.
@main
struct KitemirVTOApp: App {
var body: some Scene {
WindowGroup {
SplashView()
// ↑ When this appeared on real hardware,
// I almost cried.
}
}
}
> // First launch on real hardware:
> Build Succeeded
> Installing "KitemirVTO"...
> Launched
>
> // Age 68. Started from assembly language.
> // Running an iOS app.
$ swift build --target KitemirVTO
Build complete! (47 warnings, 0 errors)
// 47 warnings. Fixed every one.
// 0 errors.
// Next: App Store review.
LDR R0, =XCODE — 새로운 컴파일러와의 만남
2025년 가을, kitemir.jp의 가상 피팅 기능을 iPhone 앱으로 제공하기로 결정했다.
이유는 단순했다. 스마트폰 앱이라면 더 많은 사람에게 닿을 수 있다. 외출 중에 마음에 드는 옷을 그 자리에서 가상 피팅할 수 있다면, 쇼핑 경험이 완전히 달라진다. 그것만으로도 만들 가치가 있다.
Xcode를 설치한 날, 첫 인상은 "크다"였다. 메인프레임 OS를 처음 봤을 때와 같은 감각이다. 화면 가득 펼쳐진 메뉴, 창, 패널. 어디서부터 손을 대야 할지 몰랐다.
그때도 결국 마스터했다. 이번도 그럴 수밖에 없다.
$ xcode-select --install
$ swift --version
// Apple Swift version 5.10
// Target: arm64-apple-macosx14.0
//
// 40년 만의, 새로운 컴파일러다.
Swift Optionals — 40년 분의 Null Pointer 지옥을 끝내는 타입 시스템
어셈블러 세계에서 포인터의 NULL 체크는 '개발자의 책임'이었다. 언제나. 예외 없이.
; 어셈블러에서의 NULL 체크
MOV AX, [ptr] ; 포인터 값을 로드
CMP AX, 0 ; NULL인지 판정
JE null_error ; NULL이면 에러 핸들러로
; ──────────────────────────────────────────
; 잊으면? → 세그먼테이션 폴트.
; 런타임까지 아무도 가르쳐 주지 않는다.
C 언어로 이행한 후에도 NULL 참조 버그는 영원한 숙제였다. 코드 리뷰에서 NULL 체크 누락을 찾는 작업을 몇 천 번이나 했는지 모른다.
Swift에 Optional<T>라는 개념이 있다는 것을 알았을 때, 처음에는 "왜 이렇게 번거롭지"라고 생각했다.
// nil일 수 있는 값은 Optional 타입으로 선언
var userImage: UIImage? = nil // ?가 Optional을 나타냄
// 사용하기 전에 반드시 언래핑해야 함
if let image = userImage {
// 여기서는 image가 반드시 존재한다
displayImage(image)
} else {
showPlaceholder()
}
// 직접 사용하려고 하면?
let w = userImage.size.width
// → 컴파일 에러:
// Value of optional type 'UIImage?'
// must be unwrapped first
// → 런타임 전에, 컴파일 시점에 멈춘다.
40년간 인간의 주의력에 맡겼던 것을 타입 시스템이 보증한다. 잊어도 누군가가 막아준다. 그것도 실행 전에.
> INSIGHT: Optional<T>는 오버헤드가 아니다.
> 40년 분의 NULL 포인터 버그를
> 컴파일 시점에 근절하는 것이다.
>
> // 컴파일러가 나의 코드 리뷰어가 되었다.
SwiftUI의 선언적 UI — "어떻게"가 아닌 "어떠해야 하는가"
어셈블러는 궁극의 명령형 언어다. CPU에 "다음엔 이것을 하라"고 한 명령씩 지시한다. 화면에 픽셀을 그릴 때도 좌표를 계산하고, 색을 설정하고, 버퍼에 써 넣는 절차를 모두 명시한다.
SwiftUI는 발상이 정반대였다.
// "이 화면은 이러해야 한다"고 선언하기만 한다
struct HomeView: View {
@State var products: [Garment] = []
var body: some View {
VStack {
Text("가상 피팅")
.font(.title)
.foregroundColor(.purple)
LazyVGrid(columns: columns) {
ForEach(products) { product in
ProductCard(garment: product)
}
}
}
}
}
// "어떻게 그릴까"를 명령하지 않는다.
// "무엇을 표시해야 하는가"만 선언한다.
// @State가 바뀌면 UI는 자동으로 갱신된다.
처음에는 어색했다. 그런데 어느 순간 깨달았다.
이건 상태 기계(state machine)다.
제어 프로그램에서 그렸던 '상태 전이도'와 같은 구조다. @State는 현재 상태 레지스터. body는 "이 상태일 때 출력은 이러해야 한다"는 규칙. 상태가 바뀌면 전이가 발생하고 출력(UI)이 갱신된다.
> SwiftUI is a state machine.
> @State = 현재 상태 레지스터
> body = 출력 함수
> 뷰 갱신 = 상태 전이
>
> // 1985년에 그린 상태 전이도가
> // iPhone 위에서 살아 있다.
.pbxproj의 함정 — 40년 전 링커 스크립트와 같은 얼굴
개발 중 가장 빠졌던 함정이 Xcode 프로젝트 파일(.pbxproj)이었다.
어셈블러·C 개발에서는 소스 파일을 추가하면 Makefile에 명시적으로 써야 했다. 링커 스크립트에도 등록이 필요했다. "파일이 존재한다"와 "빌드에 포함된다"는 항상 별개의 이야기였다.
Swift도 마찬가지였다. Finder로 .swift 파일을 폴더에 추가해도 Xcode 프로젝트는 인식하지 못한다. .pbxproj 파일 4군데에 수동으로 추가해야 한다.
// .pbxproj 내부(일부 발췌)
/* PBXBuildFile section */
ABC123 /* NewView.swift in Sources */ = {
isa = PBXBuildFile;
fileRef = DEF456 /* NewView.swift */;
};
/* PBXFileReference section */
DEF456 /* NewView.swift */ = {
isa = PBXFileReference;
lastKnownFileType = sourcecode.swift;
path = NewView.swift;
sourceTree = "<group>";
};
// + 그룹 섹션에도 추가
// + Sources 빌드 페이즈에도 추가
// ──────────────────────────────────────
// 총 4군데. 하나라도 누락하면...
에러 메시지는 최악이었다. "Cannot find type 'NewView' in scope." 파일은 있다. 코드도 맞다. 왜 빌드가 안 되지?
3시간 후 원인을 알았을 때, 40년 전이 떠올랐다.
링커 스크립트의 OBJECTS 목록에 파일 추가를 잊어서 "symbol not found"로 막혔던 신입 시절. 68세에 같은 실수를 하고 있었다.
ld: symbol not found(1980년대)
Cannot find type in scope(2025년)
40년이 지나도 빌드 시스템은 말한다. "제대로 등록해."
App Store로 가는 길 — 디바이스 드라이버 인증과 같은 냄새
디바이스 드라이버를 출하하려면 인증이 필요했다. 사양에 적합한지 검사 기관이 테스트한다. 통과해야 비로소 제품에 탑재할 수 있다. 당시에는 당연한 프로세스였다.
App Store 제출도 비슷했다. Apple Developer Program 등록. Bundle ID 설정. Certificates, Identifiers, Profiles. 서명용 비밀 키 관리. 프로비저닝 프로파일 설정.
그러나 본질은 같다. "신뢰할 수 있는 코드임을 증명하고 나서 출하한다."
처음으로 실기에서 앱이 동작했을 때 — SplashView가 iPhone에 표시되었을 때 — 그때와 같은 달성감이 있었다.
@main
struct KitemirVTOApp: App {
var body: some Scene {
WindowGroup {
SplashView()
// ↑ 이게 실기에 표시됐을 때,
// 눈물이 날 뻔했다.
}
}
}