사전 Swift
구현체

Swift

gabury1고친 사람 github-actions[bot]

Swift 는 iPhone 과 Mac 에서 도는 앱을 짜는 프로그래밍 언어입니다. Apple 이 자기 기기의 앱을 짜라고 만들었습니다. 코드는 미리 기계어로 컴파일되어 가상 머신 없이 돕니다. 비어 있을 수 있는 값을 타입에 표시하게 해서 빈 값을 건드리는 실수를 컴파일할 때 걸러 줍니다.

쉽고 빠른 이해

Swift 는 Apple 기기의 앱을 짜는 언어입니다. 서버의 Java 코드와 달리 가상 머신 없이 기계어로 바로 돕니다.

앱은 화면이 잠깐만 끊겨도 티가 납니다. 그래서 메모리 청소 때문에 멈추는 일이 없어야 합니다. 빈 값을 건드려 앱이 꺼지는 일도 줄여야 합니다.

어떻게 도는가:

  1. 컴파일러가 소스를 휴대폰 프로세서의 기계어로 미리 바꿉니다
  2. 값이 없을 수 있는 변수는 타입 뒤에 물음표를 붙입니다. 그 변수는 확인한 뒤에만 씁니다
  3. 객체를 가리키는 수를 세다가 0 이 되는 순간 바로 지웁니다

대가도 있습니다. 서로를 가리키는 두 객체는 저절로 안 지워져서 짜는 사람이 고리를 끊어야 합니다. 실행 파일은 운영체제와 프로세서마다 따로 빌드합니다.

여러 운영체제에서 실행 파일 하나를 그대로 돌려야 한다면 Swift 로 얻는 것이 적습니다. Java 로 쌓아 둔 서버 코드와 라이브러리가 많을 때도 그렇습니다.

상세

이 절은 Swift 를 백엔드 개발자에게 익숙한 Java 와 견주며 봅니다. 먼저 소스가 실행 파일이 되는 길을 봅니다. 이어서 빈 값과 복사와 메모리, 타입을 묶는 방식을 Swift 가 어떻게 다루는지 짧은 코드로 하나씩 짚습니다. Java 쪽 모습을 옆에 두면 Swift 의 문법이 왜 그렇게 생겼는지가 드러납니다.

소스에서 실행 파일까지

Swift 코드는 .swift 로 끝나는 소스 파일에 적습니다. swiftc 라는 컴파일러가 이 파일을 기계어로 바꿉니다. 기계어는 프로세서가 받아서 곧장 실행하는 명령입니다.

Java 는 한 단계를 더 거칩니다. javac 가 먼저 바이트코드를 담은 클래스 파일을 만듭니다. 그 파일은 JVM(Java Virtual Machine, 자바 가상 머신)이 읽습니다. JVM 은 실행하는 동안 그 파일을 기계어로 옮깁니다. 켠 직후에는 이 옮기는 일 때문에 조금 느립니다.

flowchart TD
    subgraph Swift
        S["app.swift · Swift 소스"] --> SC["swiftc"]
        SC --> SB["기계어 실행 파일"]
    end
    subgraph Java
        J["App.java · Java 소스"] --> JC["javac"]
        JC --> JB["클래스 파일"]
        JB --> V["JVM"]
    end
    SB --> P["프로세서"]
    V --> P

그림에서 Swift 쪽에는 가상 머신 칸이 없습니다. 이렇게 실행하기 전에 기계어로 다 옮겨 두는 방식을 AOT 컴파일(Ahead-Of-Time compilation, 미리 하는 컴파일)이라고 부릅니다. 켜자마자 제 속도로 돕니다. 가상 머신을 따로 깔 필요도 없습니다.

대신 만든 실행 파일은 그 운영체제와 프로세서에서만 돕니다. Mac 에서 돌릴 파일과 Linux 서버에서 돌릴 파일을 따로 빌드합니다. Java 의 클래스 파일 하나가 JVM 만 있으면 어디서나 도는 것과 갈리는 대목입니다.

빈 값을 담는 옵셔널

Java 에서는 객체를 가리키는 변수라면 무엇이든 null 일 수 있습니다. 그런 변수로 메서드를 부르면 실행 도중에 NullPointerException 이 납니다. 컴파일러는 이 실수를 미리 알려 주지 않습니다.

Swift 는 비어 있을 수 있는 값을 따로 된 타입으로 둡니다. 이것을 옵셔널이라고 부릅니다. 타입 뒤에 물음표를 붙여 String? 처럼 적습니다. 빈 상태는 nil 이라고 씁니다.

swift
var a: String = "hi"
a = nil            // 컴파일 오류
var b: String? = "hi"
b = nil            // 통과

물음표 없는 String 에는 nil 을 넣을 수 없습니다. 그래서 그 변수를 쓰는 코드는 빈 값을 걱정하지 않아도 됩니다.

옵셔널은 그냥은 못 씁니다. 값이 들었는지 먼저 확인하고 꺼내야 합니다. 흔한 방법은 if let 입니다. 값이 있을 때만 새 변수에 꺼내 담습니다.

swift
let name: String? = "swift"
if let n = name {
    print(n.count)   // 5
}

딕셔너리에서 값을 찾을 때도 옵셔널이 나옵니다. 딕셔너리는 키로 값을 찾는 자료형입니다. Swift 에서는 [String: Int] 처럼 키와 값의 타입을 적습니다. 찾는 키가 없을 수 있어서 찾은 결과는 옵셔널로 나옵니다.

swift
let ages = ["kim": 30, "lee": 25]
print(ages["kim"] ?? 0)    // 30
print(ages["park"] ?? 0)   // 0

?? 는 비었을 때 쓸 값을 정해 두는 연산자입니다. 왼쪽이 nil 이면 오른쪽 값을 씁니다.

확인 없이 꺼내는 길도 열려 있습니다. 변수 뒤에 느낌표를 붙인 name! 은 값을 바로 꺼냅니다. 값이 없으면 프로그램이 거기서 멈춥니다.

복사되는 값 타입

Java 에서 객체를 변수에 넣으면 객체를 가리키는 참조가 들어갑니다. 두 변수가 같은 객체를 가리키게 되어, 한쪽에서 고치면 다른 쪽에서도 바뀐 값이 보입니다. 이런 타입을 참조 타입이라고 부릅니다.

Swift 는 타입을 만들 때 둘 중 하나를 고릅니다. class 로 만들면 참조 타입입니다. struct 로 만들면 값 타입입니다. 값 타입은 다른 변수에 넣을 때 내용이 복사됩니다.

swift
struct Point { var x: Int }
var p1 = Point(x: 1)
var p2 = p1        // 복사된다
p2.x = 9
print(p1.x)        // 1

p2 를 고쳐도 p1 은 1 그대로입니다. 넣는 순간 둘이 따로 된 값이 되었기 때문입니다.

Swift 의 문자열과 배열과 딕셔너리도 값 타입입니다. 함수에 배열을 넘기면 받은 쪽이 고친 내용이 넘긴 쪽으로 새어 나오지 않습니다. Java 에서 목록을 넘길 때 방어용으로 복사해 두던 일이 줄어듭니다.

넣을 때마다 내용을 전부 복사하면 느립니다. 그래서 이 타입들은 복사해도 처음에는 내용을 같이 쓰다가, 한쪽이 고치는 순간에 복사합니다. 이 방식을 쓰기 시 복사라고 부릅니다.

메모리를 치우는 ARC

참조 타입의 객체는 다 쓰고 나면 치워야 합니다. Java 에서는 가비지 컬렉션이 이 일을 합니다. 런타임이 틈틈이 살아 있는 객체를 찾습니다. 찾지 못한 나머지는 지웁니다. 치우는 동안 프로그램이 잠깐 서기도 합니다.

Swift 는 ARC(Automatic Reference Counting, 자동 참조 카운팅)를 씁니다. 객체마다 자기를 가리키는 참조가 몇 개인지 세다가, 그 수가 0 이 되면 바로 지웁니다. 수를 늘리고 줄이는 코드는 컴파일러가 빌드할 때 끼워 넣습니다.

swift
class Order {}
var a: Order? = Order()  // 참조 1
var b = a                // 참조 2
a = nil                  // 참조 1
b = nil                  // 참조 0, 해제

객체는 마지막 줄에서 곧바로 사라집니다. 지워지는 때가 코드로 정해지므로, 메모리를 치우려고 앱이 잠깐 서는 일이 없습니다.

대가는 순환 참조입니다. 두 객체가 서로를 가리키면 둘 다 참조 수가 0 으로 내려가지 않습니다. 아무도 안 쓰는데 지워지지도 않는 메모리 누수가 됩니다.

이 고리는 한쪽 참조를 약한 참조로 두어 끊습니다. 약한 참조는 객체를 가리키기만 하고 참조 수를 늘리지 않습니다. Swift 에서는 변수 앞에 weak 를 붙여 표시합니다.

메모리 안전을 지키는 장치

메모리 안전은 프로그램이 제 몫이 아닌 메모리를 건드리지 못하게 막는 성질입니다. 배열 끝을 넘어 옆 데이터를 읽거나, 이미 돌려준 메모리에 다시 쓰는 일이 여기에 걸립니다. Swift 는 이 성질을 언어가 지켜 줍니다.

앞에서 본 두 장치가 그 일부입니다. 옵셔널은 빈 값을 건드리는 코드를 막습니다. ARC 는 아직 누가 쓰는 객체를 먼저 지우지 않습니다.

배열 번호도 확인합니다. 접근할 때마다 번호가 범위 안인지 보는 것을 경계 검사라고 합니다. 범위를 넘는 번호로 읽으면 옆 메모리를 가져오는 대신 프로그램이 멈춥니다.

swift
let nums = [10, 20, 30]
print(nums[1])     // 20
print(nums[5])     // 실행 중 멈춤

변수는 값을 넣기 전에 읽을 수 없습니다. 값을 안 넣은 변수를 읽는 코드는 컴파일러가 거부합니다. 그래서 전에 메모리에 남아 있던 엉뚱한 값이 새어 나오지 않습니다.

이 보호를 끄는 길도 있습니다. 이름이 Unsafe 로 시작하는 포인터 타입을 쓰면 C 처럼 메모리 주소를 직접 다룹니다. 이름에 표시가 남아서 위험한 코드가 어디 있는지 검색으로 찾을 수 있습니다.

Java 인터페이스에 해당하는 프로토콜

Swift 에서 인터페이스에 해당하는 것을 프로토콜이라고 부릅니다. 네트워크에서 말하는 프로토콜과는 다른 뜻입니다. 타입이 갖춰야 할 메서드를 적어 둔 약속입니다.

swift
protocol Shape {
    func area() -> Double
}
struct Square: Shape {
    var side: Double
    func area() -> Double { side * side }
}
print(Square(side: 3).area())  // 9.0

Square 는 struct 인데도 프로토콜을 따릅니다. Java 에서는 클래스만 인터페이스를 구현하지만, Swift 에서는 값 타입도 같은 약속을 지킵니다.

struct 는 클래스와 달리 다른 타입을 상속받지 못합니다. 그래서 여러 타입을 한 이름으로 묶을 때는 상속 대신 프로토콜을 씁니다. 이렇게 짜는 방식을 프로토콜 지향 프로그래밍이라고 부릅니다.

Swift 로 짜는 애플리케이션

Swift 는 iOS 와 macOS 앱을 짤 때 주로 쓰는 언어입니다. 앱 화면인 UI(User Interface, 사용자 인터페이스)는 Apple 의 SwiftUI 나 UIKit 로 그립니다. 둘 다 Swift 코드에서 부릅니다. 이 둘은 Apple 기기에서만 돕니다.

Swift 전에는 Objective-C 로 짰습니다. 한 앱 안에서 두 언어를 섞어 쓸 수 있어서, 오래된 코드를 버리지 않고 조금씩 옮겨 갑니다.

빌드는 대개 Xcode 에서 합니다. Xcode 는 Apple 이 내놓는 개발 도구 모음입니다. 이 도구는 macOS 에서만 돕니다. 그래서 iOS 앱을 빌드하려면 Mac 이 있어야 합니다.

서버도 짤 수 있습니다. Swift 는 오픈 소스로 공개되어 Linux 에서도 돕니다. Vapor 같은 웹 프레임워크도 있습니다. 서버에서 쓸 라이브러리와 운영 도구는 Java 쪽이 더 오래 쌓아 왔습니다.

고른 것과 내준 것

위의 장치들은 저마다 무언가를 내주고 얻은 것입니다. 한데 모으면 이렇습니다.

고른 것 얻는 것 내준 것
기계어로 미리 컴파일한다 가상 머신 없이 켜자마자 제 속도로 돈다 운영체제와 프로세서마다 따로 빌드한다
옵셔널을 타입에 둔다 빈 값을 건드리는 실수를 컴파일할 때 잡는다 값을 꺼내는 확인 코드가 늘어난다
컬렉션을 값 타입으로 둔다 넘긴 데이터가 받은 쪽에서 몰래 바뀌지 않는다 고칠 때 복사가 일어날 수 있다
ARC 로 메모리를 치운다 지워지는 때가 정해져 앱이 서지 않는다 순환 참조는 짜는 사람이 weak 로 끊는다
배열 번호를 확인한다 범위 밖을 읽으면 옆 메모리 대신 멈춤이 돌아온다 접근마다 확인하는 [[시간과 시계

그래서 Swift 는 화면이 끊기면 안 되는 Apple 기기의 앱에 맞춰진 언어입니다. 가상 머신과 멈춤을 걷어 내고, 그 대신 메모리 고리를 끊는 일을 짜는 사람에게 남겼습니다.

반대로 여러 운영체제에 같은 실행 파일 하나를 돌리고 싶거나, 이미 JVM 위에 쌓아 둔 서버 코드와 라이브러리가 많으면 Swift 가 주는 것이 적습니다.

관련 항목

Swift 코드를 실행 파일로 만드는 도구

swiftc · LLVM · Xcode · Swift Package Manager · 컴파일러 · AOT 컴파일 · 기계어

Swift 언어를 이루는 문법 요소

옵셔널 · 옵셔널 체이닝 · 값 타입 · 참조 타입 · 구조체 · 열거형 · 클로저 · 제네릭 · 타입 추론 · 프로토콜 지향 프로그래밍 · 익스텐션

Swift 표준 라이브러리가 주는 컬렉션

Swift 표준 라이브러리 · 배열 · 딕셔너리 · 집합 · 문자열

Swift 가 메모리를 다루는 장치

ARC · 참조 카운팅 · 약한 참조 · 순환 참조 · 메모리 누수 · 쓰기 시 복사 · 경계 검사 · 메모리 안전

Swift 가 동시 처리에 쓰는 장치

동시성 · 비동기 프로그래밍 · 액터 · 데이터 경쟁 · 스레드

Swift 로 짓는 애플리케이션과 그 프레임워크

iOS · macOS · SwiftUI · UIKit · Vapor · Linux

Swift 와 섞어 쓰거나 겨루는 프로그래밍 언어

Objective-C · Kotlin · Java · Rust · C++ · Dart

Swift 가 속하는 상위 분류

프로그래밍 언어 · 정적 타입 · 컴파일 언어 · 객체 지향 프로그래밍 · 함수형 프로그래밍 · 오픈 소스

다른 이름: 스위프트 · Swift 언어 · 스위프트 언어