Swift 시작하기 - 화면 없이 앱 로직부터
이 장에서 배우는 것
이 책은 습관 기록 앱(물 마시기, 운동, 독서)의 두뇌에 해당하는 부분만 만든다. 버튼이나 화면은 만들지 않는다. 하루 기록을 쌓고, 연속 달성일을 세고, 통계를 내는 계산을 Swift 코드로 짜고, 장마다 조금씩 키운다. 이 장에서는 그 출발점으로 Swift 가 어디에 쓰이는지 살피고, 도구를 준비하고, 가장 작은 프로그램을 컴파일해서 실행한다. 프로그래밍 경험이 없어도 따라올 수 있도록 터미널에서 입력할 명령을 하나씩 적었다.
- Swift 가 쓰이는 분야와 Xcode, 명령줄 도구, Linux 툴체인의 차이를 설명할 수 있다.
swiftc로 소스 파일을 컴파일하고 만들어진 실행 파일을 실행할 수 있다.main.swift에서 최상위 코드가 위에서 아래로 실행되는 순서를 이해한다.print의 구분자와 줄 끝 설정, 문자열 보간으로 값을 문장에 끼워 넣을 수 있다.
문제 상황
습관 기록 앱을 만든다고 하자. 처음 떠오르는 일은 화면 디자인이지만, 앱이 실제로 하는 일의 대부분은 계산이다. 오늘 물을 몇 번 마셨는지 기록하고, 목표까지 몇 번 남았는지 계산하고, 며칠째 이어서 달성했는지 센다. 화면이 틀려도 숫자가 맞으면 고치기 쉽지만, 숫자가 틀리면 사용자는 앱을 믿지 않는다.
그래서 이 책은 로직을 먼저 만든다. 화면 없이 터미널에 글자를 찍는 프로그램으로 시작하면 코드 몇 줄과 출력 몇 줄만으로 결과를 바로 확인할 수 있다. 화면 프레임워크를 배우느라 로직 공부가 늦어지는 일도 없다. 이 방식으로 만든 로직은 나중에 어떤 화면에 붙여도 그대로 쓸 수 있다.
이 장의 목표는 작다. 습관 이름, 목표 횟수, 완료 횟수를 코드에 적고 "오늘 기록: 5/8", "달성률: 62%" 같은 줄을 터미널에 출력하는 것이다. 이 정도만 돌아가도 도구 설치, 컴파일, 실행의 한 바퀴를 모두 경험한 셈이다.
Swift 와 도구 준비
Swift 가 쓰이는 곳
Swift 는 Apple 이 만든 프로그래밍 언어이고 소스 코드가 공개되어 있다. 가장 널리 알려진 용도는 iPhone, iPad, Mac, Apple Watch, Apple Vision Pro 같은 Apple 기기의 앱이다. 하지만 언어 자체는 기기에 묶여 있지 않다. 터미널에서 실행하는 명령줄 도구, 서버에서 요청을 처리하는 프로그램, 작은 기기에 들어가는 임베디드 코드에도 쓰인다. 이 책의 예제는 모두 터미널에서 실행하므로 macOS 와 Linux 어느 쪽에서든 같은 코드가 같은 결과를 낸다.
이 책은 Swift 6.2 를 기준으로 한다. 언어의 세부는 버전마다 조금씩 달라지므로, 막히면 The Swift Programming Language 같은 공식 문서에서 사실을 확인하는 습관을 들이면 좋다.
설치 방법 세 가지
Swift 를 쓰려면 소스 코드를 기계가 실행할 수 있는 형태로 바꿔 주는 컴파일러(compiler)가 필요하다. 컴파일러와 주변 도구를 묶은 꾸러미를 툴체인(toolchain)이라 부른다. 환경에 따라 얻는 방법이 다르다.
| 환경 | 설치 대상 | 설치 방법 | 이 책에서의 용도 |
|---|---|---|---|
| macOS | Xcode | App Store 에서 설치 | 편집기와 컴파일러를 함께 쓴다 |
| macOS | 명령줄 도구 | 터미널에서 xcode-select --install | Xcode 없이 컴파일만 한다 |
| Linux | Swift 툴체인 | swift.org 설치 안내를 따른다 | 컴파일과 실행을 한다 |
Xcode 는 Apple 의 통합 개발 환경(IDE)이다. 편집기, 컴파일러, 디버거, 화면 미리보기가 한 프로그램에 들어 있다. 명령줄 도구(Command Line Tools)는 그중 터미널에서 쓰는 부분만 따로 설치하는 가벼운 방법이다. 이 책은 화면을 만들지 않으므로 명령줄 도구만으로 충분하다. Linux 에서는 swift.org 가 제공하는 툴체인을 설치한다. 어느 쪽이든 설치가 끝나면 터미널에서 다음 명령으로 확인한다.
swiftc --version
Swift 의 버전과 대상 플랫폼이 한 줄에서 몇 줄로 출력되면 준비가 끝난 것이다. 버전 숫자가 6.2 이상인지 확인한다. 소스 코드를 작성할 편집기는 아무 것이나 쓸 수 있다. 확장자가 .swift 인 일반 텍스트 파일이면 된다.
소스에서 실행까지: swiftc 와 main.swift
컴파일과 실행은 두 단계다
Swift 로 쓴 파일은 사람이 읽는 글이다. 컴퓨터는 이 글을 바로 실행하지 못한다. 컴파일(compile)은 소스 코드를 컴퓨터가 실행할 수 있는 파일로 번역하는 작업이다. swiftc 가 그 일을 하는 프로그램이고, 번역 결과로 실행 파일이 생긴다. 실행은 만들어진 파일을 터미널에서 불러내는 별도의 단계다.
그림처럼 소스를 고치면 다시 컴파일해야 결과가 바뀐다. 코드를 바꾸고도 이전 결과가 나온다면 컴파일을 건너뛰었는지부터 확인한다. 명령은 다음과 같다.
swiftc main.swift -o app
./app
-o app 는 만들어질 실행 파일의 이름을 app 으로 하라는 뜻이다. 이 옵션을 빼면 파일 이름에서 확장자를 뺀 main 이 기본 이름이 된다. ./app 의 ./ 는 현재 폴더에 있는 파일이라는 표시다. 터미널은 보안상 현재 폴더를 자동으로 뒤지지 않으므로 이 표시가 필요하다.
main.swift 의 최상위 코드
다른 언어를 접해 본 사람은 프로그램이 main 이라는 함수에서 시작하는 모습을 기억할 것이다. Swift 에서는 파일 이름이 main.swift 이면 파일 맨 위부터 적힌 문장이 곧 프로그램의 시작이다. 함수나 구조체 안에 넣지 않고 파일 바로 아래에 적은 문장을 최상위 코드(top-level code)라고 한다. 위에서 아래로 한 줄씩 차례로 실행되고, 마지막 줄을 실행하면 프로그램이 끝난다.
이 규칙에는 제약이 하나 있다. 실행 파일을 이루는 파일이 여러 개이면 최상위 코드는 main.swift 에서만 쓸 수 있다. 이 책은 앞으로 몇 장 동안 파일 하나만 쓰므로 신경 쓸 일이 적지만, 장이 진행되어 파일이 나뉘면 이 규칙이 다시 등장한다.
print 와 문자열 보간
print 의 기본 동작
print 는 괄호 안의 값을 터미널에 출력하고 줄을 바꾼다. 따옴표로 감싼 글을 문자열(string)이라 부른다. 쉼표로 값을 여러 개 주면 기본적으로 공백 하나를 끼워 이어 붙인다. 이 구분 문자와 줄 끝 문자는 이름을 붙여 바꿀 수 있다.
separator:값들 사이에 넣을 문자열이다. 기본값은 공백 하나다.terminator:출력 끝에 붙일 문자열이다. 기본값은 줄 바꿈이다.
문자열 안에서 역슬래시(\)는 특별한 문자를 쓰는 데 쓰인다. \n 은 줄 바꿈이고, \" 는 문자열을 닫는 따옴표가 아닌 글자로서의 따옴표다. 이렇게 역슬래시로 시작하는 표기를 이스케이프 시퀀스(escape sequence)라고 한다.
문자열 보간
출력할 문장 중간에 변수나 계산 결과를 넣고 싶을 때가 많다. 문자열 안에 \( 와 ) 로 감싼 식을 적으면 Swift 가 그 식의 값을 계산해서 글자로 바꿔 끼워 넣는다. 이 기능을 문자열 보간(string interpolation)이라 한다.
괄호 안에는 이름만이 아니라 계산식도 쓸 수 있다. 예를 들어 \(goalCount - doneCount) 는 뺄셈 결과를 끼워 넣는다. 문자열과 숫자를 + 로 직접 이어 붙이는 방식은 Swift 에서 오류가 되므로(뒤의 틀리는 사례에서 다룬다), 숫자를 문장에 넣을 때는 보간을 쓰는 것이 기본이다.
값에 이름 붙이기: 미리 보기
예제에서는 let 으로 값에 이름을 붙인다. let habitName = "물 마시기" 는 "물 마시기"라는 글에 habitName 이라는 이름을 붙여 둔다는 뜻이다. 한 번 붙인 값은 바뀌지 않는 상수(constant)다. 값이 바뀌는 경우와 왜 let 을 기본으로 삼는지는 다음 장에서 자세히 다룬다. 지금은 이름을 붙이면 같은 값을 여러 곳에서 쓸 수 있다는 점만 알면 된다.
또 하나, 예제의 8 과 5 는 소수점 없는 정수이고 Swift 는 이를 Int 타입으로 취급한다. 정수끼리 나누면 소수 부분은 버려진다. 이 성질은 달성률 계산에서 바로 확인할 수 있다.
| 쓰는 법 | 의미 | 예 | 출력 |
|---|---|---|---|
\(식) | 식의 값을 끼워 넣는다 | "\(3 + 1)일" | 4일 |
\n | 줄을 바꾼다 | "가\n나" | 두 줄로 출력 |
\" | 글자 따옴표 | "\"물\"" | "물" |
separator: | 값 사이 문자열 | print("가", "나", separator: "-") | 가-나 |
완성 코드
아래 내용을 main.swift 라는 이름으로 저장한다. 습관 기록 앱이 하루 요약을 보여 주는 가장 작은 형태다.
print("=== 습관 기록 앱 ===")
let habitName = "물 마시기"
let goalCount = 8
let doneCount = 5
print("습관: \(habitName)")
print("오늘 기록: \(doneCount)/\(goalCount)")
print("남은 횟수: \(goalCount - doneCount)")
print("달성률: \(doneCount * 100 / goalCount)%")
print("물 마시기", "운동", "독서", separator: " / ")
print("저장 중", terminator: "...")
print("완료")
print("첫 줄\n둘째 줄")
print("따옴표는 \"이렇게\" 쓴다")
줄별 해설
- 1행: 제목 줄을 출력한다. 따옴표 안의 글이 그대로 나오고 줄이 바뀐다.
- 3~5행: 습관 이름, 목표 횟수, 완료 횟수에 이름을 붙인다. 이후 코드는 숫자를 직접 적지 않고 이 이름을 쓴다. 목표를 8 에서 10 으로 바꾸고 싶으면 4행만 고치면 된다.
- 7행:
\(habitName)이 "물 마시기"로 바뀌어 "습관: 물 마시기"가 출력된다. - 8행: 보간이 두 번 쓰였다. 사이의
/는 평범한 글자다. - 9행: 괄호 안에서
8 - 5를 계산하고 결과 3 을 끼워 넣는다. - 10행:
5 * 100 / 8은 왼쪽부터 계산한다. 500 을 8 로 나누면 62.5 이지만 정수 나눗셈이므로 소수 부분이 버려져 62 가 된다. 뒤의%는 보간 괄호 밖에 있어 평범한 글자로 출력된다. - 12행: 값 세 개를
separator로 이어 붙여 "물 마시기 / 운동 / 독서"를 출력한다. 구분자는 값 사이에만 들어가고 끝에는 붙지 않는다. - 13~14행: 13행은 줄 끝 문자를
"..."로 바꿔 줄을 바꾸지 않는다. 그래서 14행의 "완료"가 같은 줄에 이어서 출력된다. - 15행:
\n위치에서 줄이 바뀌어 두 줄이 나온다. - 16행:
\"로 따옴표를 글자로 출력한다.
실행 결과
$ swiftc main.swift -o app
$ ./app
=== 습관 기록 앱 ===
습관: 물 마시기
오늘 기록: 5/8
남은 횟수: 3
달성률: 62%
물 마시기 / 운동 / 독서
저장 중...완료
첫 줄
둘째 줄
따옴표는 "이렇게" 쓴다
컴파일 단계에서는 아무 메시지도 나오지 않는다. 경고나 오류가 없다는 뜻이다. 프로그램은 마지막 줄을 실행한 뒤 스스로 끝난다.
실무에서 자주 틀리는 것
문자열과 숫자를 + 로 잇는다
다른 언어에서 넘어온 사람이 자주 쓰는 방식이다. Swift 는 문자열과 정수를 섞어 더하지 않는다.
// 틀린 코드
let doneCount = 5
print("완료 횟수: " + doneCount)
컴파일러가 문자열과 Int 에 + 를 적용할 수 없다는 오류를 낸다. 보간으로 고친다.
// 고친 코드
let doneCount = 5
print("완료 횟수: \(doneCount)")
정수 나눗셈으로 달성률을 구한다
비율을 구하려고 나눗셈을 먼저 하면 값이 0 이 된다.
// 틀린 코드
let goalCount = 8
let doneCount = 5
print("달성률: \(doneCount / goalCount * 100)%")
5 / 8 이 먼저 계산되어 소수 부분이 버려지고 0 이 된다. 결과는 "달성률: 0%"다. 오류도 경고도 없이 틀린 값이 나오므로 더 위험하다. 곱셈을 먼저 하도록 순서를 바꾼다.
// 고친 코드
let goalCount = 8
let doneCount = 5
print("달성률: \(doneCount * 100 / goalCount)%")
소수점까지 필요한 경우는 숫자 타입을 다룬 뒤에 다시 다룬다. 지금은 정수 나눗셈이 소수를 버린다는 사실을 기억하면 된다.
복사한 코드의 따옴표가 둥글다
웹 페이지나 문서 프로그램에서 코드를 복사하면 직선 따옴표가 둥근 따옴표로 바뀌어 붙는 경우가 있다.
// 틀린 코드
print(“안녕”)
겉으로는 비슷해 보여도 Swift 는 둥근 따옴표를 문자열의 경계로 인정하지 않고 오류를 낸다. 편집기에서 직선 따옴표로 다시 입력한다.
// 고친 코드
print("안녕")
main.swift 가 아닌 파일에 최상위 코드를 쓴다
파일을 둘로 나누고 두 번째 파일에 실행 문장을 적는 경우다.
// 틀린 구성: main.swift 와 report.swift 를 함께 컴파일
// report.swift
print("보고서를 만든다")
swiftc main.swift report.swift -o app 으로 컴파일하면 report.swift 에는 최상위 코드를 둘 수 없다는 오류가 난다. 실행 문장은 main.swift 로 옮긴다. 다른 파일에는 나중에 배울 함수나 타입 선언만 둔다. 이 장처럼 파일이 하나뿐이면 이 문제가 생기지 않는다.
한눈에 보기
| 주제 | 핵심 | 예 | 주의 |
|---|---|---|---|
| 도구 확인 | 버전 출력으로 설치를 검사한다 | swiftc --version | 6.2 이상인지 본다 |
| 컴파일 | 소스를 실행 파일로 번역한다 | swiftc main.swift -o app | 소스를 고치면 다시 컴파일한다 |
| 실행 | 현재 폴더의 파일을 지정한다 | ./app | ./ 를 빼먹지 않는다 |
| 최상위 코드 | main.swift 에서 위에서 아래로 실행된다 | 파일 맨 위의 print | 다른 파일에는 쓸 수 없다 |
| 문자열 보간 | 식의 값을 문장에 끼운다 | "\(doneCount)회" | 문자열에 숫자를 + 로 잇지 않는다 |
| 정수 나눗셈 | 소수 부분이 버려진다 | 500 / 8 은 62 | 곱셈을 먼저 한다 |
연습 문제
- 습관 이름 "운동", 목표 30분, 완료 20분을 상수로 만들고 "운동: 20분 / 30분"과 "남은 시간: 10분"을 보간으로 출력하라.
- 다음 코드의 출력을 예상하라.
print("a", "b", "c", separator: "-") print("x", terminator: "") print("y") - 다음 코드는 컴파일되지 않는다. 이유를 설명하고 고쳐라.
let goalCount = 8 print("목표: " + goalCount) - 독서 습관의 목표가 30쪽이고 12쪽을 읽었다. 달성률을 정수 퍼센트로 계산해 "독서 달성률: 40%"를 출력하는 코드를 작성하라. 숫자 40 을 직접 적지 말고 계산으로 구해야 한다.
정답과 해설
- 이름을 붙인 상수를 보간에 쓴다.
출력은 "운동: 20분 / 30분"과 "남은 시간: 10분"이다. 보간 괄호 안에서 뺄셈을 해도 된다는 점이 핵심이다.let habitName = "운동" let goalMinutes = 30 let doneMinutes = 20 print("\(habitName): \(doneMinutes)분 / \(goalMinutes)분") print("남은 시간: \(goalMinutes - doneMinutes)분") - 첫 줄은 구분자가 하이픈이므로
a-b-c가 출력되고 줄이 바뀐다. 둘째 줄은 줄 끝 문자가 빈 문자열이어서 줄이 바뀌지 않고, 셋째 줄의 y 가 같은 줄에 이어진다. 따라서 결과는 다음과 같다.a-b-c xy - 문자열과
Int는+로 이을 수 없기 때문이다. 보간으로 고친다.
출력은 "목표: 8"이다.let goalCount = 8 print("목표: \(goalCount)") - 곱셈을 먼저 하는 순서가 핵심이다. 12 * 100 은 1200 이고 30 으로 나누면 40 이다.
let goalPages = 30 let readPages = 12 print("독서 달성률: \(readPages * 100 / goalPages)%")readPages / goalPages * 100으로 쓰면 12 / 30 이 0 이 되어 "독서 달성률: 0%"가 나온다.