Swift는 Objective-C의 강력함과 현대 언어의 안전성을 결합한 Apple의 프로그래밍 언어입니다. 다른 많은 언어와 달리 Swift는 고유한 구성을 제공합니다: 자동 복사가 포함된 값 유형, 컴파일러 수준 격리가 있는 Actors, 속성 로직 재사용을 위한 Property Wrappers, 그리고 고전적 상속의 대안으로 프로토콜 지향 프로그래밍.Apple Swift 문서에 따르면, 이러한 기능은 코드를 더 안전하고 효율적으로 만듭니다.
주요 포인트
Swift에서 struct는 값 유형이고 class는 참조 유형입니다. 이 근본적인 차이는 할당, 함수 전달 및 메모리 관리 중 동작을 결정합니다. Struct는 각 할당 시 완전히 복사되고, class는 참조로 전달됩니다—힙 객체에 대한 포인터만 복사됩니다.
Struct는 자동으로 값 의미론을 구현합니다—각 인스턴스는 독립적입니다. Struct 속성의 변경은 var를 통해서만 가능하며, 속성을 수정하는 메서드는 mutating으로 표시해야 합니다. Struct는 상속을 지원하지 않지만 프로토콜을 준수할 수 있습니다. Swift 표준 라이브러리는 String, Array, Dictionary, Int, Bool에 struct를 사용합니다—모든 기본 유형은 값 유형입니다.
struct User {
let id: Int
var name: String
mutating func updateName(_ newName: String) {
name = newName
}
}
var user1 = User(id: 1, name: "Alice")
var user2 = user1
user2.updateName("Bob")
// user1.name === "Alice", user2.name === "Bob"
// 완전한 복사 — 독립적인 인스턴스
Class는 힙에 저장되며 참조 동등성을 지원합니다. 여러 변수가 동일한 객체를 참조할 수 있습니다, 하나의 참조를 통한 변경은 다른 모든 참조에서도 볼 수 있습니다. Class는 상속을 지원하고, 메서드 재정의(override)를 허용하며, 리소스 정리를 위한 deinit이 있습니다. Swift에서는 struct보다 class가 덜 자주 사용됩니다—Apple은 struct로 시작하고 상속이나 참조 의미론이 필요한 경우에만 class로 전환할 것을 권장합니다.
class Car {
let model: String
var speed: Int
init(model: String, speed: Int) {
self.model = model
self.speed = speed
}
func accelerate(_ amount: Int) {
speed += amount
}
}
let car1 = Car(model: "Tesla", speed: 0)
let car2 = car1
car2.accelerate(50)
// car1.speed === 50, car2.speed === 50
// 동일한 참조 — 변경 사항이 모든 곳에 표시됨
struct와 class의 선택은 데이터 의미론에 따라 다릅니다. 구성, 데이터 모델 및 상태에는 struct를 사용하세요—복사가 더 안전하고 예측 가능합니다. 식별성이 있는 객체(사용자 세션, 데이터베이스 연결)와 상속이 필요한 경우 class를 사용하세요.
Actor는 변경 가능한 상태를 동시 액세스로부터 보호하는 참조 유형입니다. Swift 컴파일러는 actor 변수가 동일한 actor 컨텍스트 내에서만 액세스될 수 있음을 보장합니다—외부 액세스는 컴파일 오류를 발생시킵니다. 이는 수동 잠금 없이 언어 수준에서 데이터 경합을 방지합니다.
모든 actor 속성과 메서드는 기본적으로 격리됩니다. 외부에서 actor 메서드를 호출하려면 await가 필요합니다—Swift는 실행을 actor로 전환하고 다시 되돌립니다. Actor는 액세스를 순차적으로 동기화합니다: 다른 작업의 요청이 대기열에 추가됩니다. 이는 actor 내에서 일관된 상태를 보장합니다. 비격리 메서드(nonisolated)는 await 없이 호출할 수 있지만 변경 가능한 속성에 액세스할 수 없습니다.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
nonisolated func accountInfo() -> String {
return "은행 계좌"
}
}
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — 원자적, 경합 조건 없음
Actor는 여러 작업이 동시에 데이터를 읽고 쓰는 상태 관리자, 캐시 및 네트워크 서비스에 유용합니다. NSLock 또는 DispatchQueue를 통한 수동 동기화와 달리 actor는 컴파일 시간에 확인되므로 실수할 수 없습니다. 격리 오류는 런타임이 아닌 컴파일 시간에 나타납니다.
Property Wrapper는 속성 선언을 변경하지 않고 속성에 동작을 추가하는 어노테이션입니다. SwiftUI와 Combine은 뷰 상태 관리를 위한 내장 래퍼를 제공합니다. 컴파일러는 속성 액세스를 래핑하는 코드를 생성하여 관찰, 동기화 또는 변환 로직을 추가합니다.
@Published는 ObservableObject를 준수하는 클래스에서 변경 사항을 자동으로 게시하는 데 사용됩니다. Combine 구독자는 각 업데이트 시 알림을 받습니다. @State는 SwiftUI 뷰의 로컬 상태입니다. @State가 변경되면 SwiftUI가 자동으로 뷰를 다시 그립니다. @Binding은 데이터를 소유하지 않고 부모 뷰와 자식 뷰 사이에 양방향 연결을 만듭니다.
import SwiftUI
import Combine
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@State private var greeting: String = "안녕하세요"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("증가", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("카운트: \(count)")
}
}
사용자 정의 Property Wrapper는 @propertyWrapper와 wrappedValue 필드가 있는 구조체를 사용하여 생성됩니다. 이를 통해 유효성 검사, 정규화, UserDefaults 저장 등의 로직을 재사용할 수 있습니다. Property Wrapper는 상용구 코드를 크게 줄이고 어노테이션을 통해 개발자의 의도를 명확히 합니다.
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Settings {
@Clamped(range: 0...100) var volume: Int = 50
}
var s = Settings()
s.volume = 150
// s.volume === 100 — 값이 자동으로 제한됨
Generics는 타입 안전성을 잃지 않고 유연한 코드를 작성하기 위한 Swift의 강력한 메커니즘입니다. 제네릭 함수와 유형은 제약 조건을 충족하는 모든 유형에서 작동합니다. 프로토콜 지향 프로그래밍(POP)은 protocol + extension이 고전적 클래스 상속을 대체하고 동작 구성을 제공하는 Swift 패러다임입니다.
Swift는 프로토콜(where 절)로 제네릭 매개변수를 제한할 수 있습니다. 연관 유형이 있는 프로토콜은 프로토콜에 대한 제네릭 등가물입니다: 구체적 유형은 준수 시점에 결정됩니다. 이는 타입 안전 컬렉션 및 알고리즘의 기초입니다. Swift 표준 라이브러리는 Generics를 적극적으로 사용합니다: Array, Dictionary, Optional — 모두 제네릭 유형입니다.
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
mutating func set(_ value: Value, for key: Key)
}
struct MemoryCache<K: Hashable, V>: Cacheable {
private var storage: [K: V] = [:]
typealias Key = K
typealias Value = V
func get(_ key: K) -> V? {
return storage[key]
}
mutating func set(_ value: V, for key: K) {
storage[key] = value
}
}
func firstMatch<T: Equatable>(in array: [T], target: T) -> Int? {
for (index, item) in array.enumerated() {
if item == target {
return index
}
}
return nil
}
let numbers = [10, 20, 30, 40]
let index = firstMatch(in: numbers, target: 30)
// index === 2 — 모든 Equatable 유형에 대한 하나의 제네릭 메서드
프로토콜 extension은 POP의 핵심 메커니즘입니다. extension에서 프로토콜 메서드의 기본 구현을 제공할 수 있습니다. 프로토콜을 준수하는 유형은 이 구현을 무료로 얻고 필요시 재정의할 수 있습니다. 이를 통해 클래스 상속 없이 동작 계층을 구축할 수 있습니다—Swift에는 없는 다중 상속의 대안입니다.
protocol Drivable {
var speed: Double { get set }
func drive()
}
extension Drivable {
func drive() {
if speed > 0 {
print("시속 \(speed)km로 주행 중")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("시속 \(speed)km로 비행 중")
}
}
let bike = Bicycle()
bike.drive() // "시속 15.0km로 주행 중" — 기본
let plane = Plane()
plane.drive() // "시속 900.0km로 비행 중" — 재정의됨
Extension과 함께 POP는 여러 프로토콜을 통한 구성을 가능하게 합니다—유형이 여러 프로토콜을 준수하고 구현을 얻을 수 있습니다. 이는 전체 계층이 고정된 단일 클래스 상속보다 더 유연합니다. Swift 표준 라이브러리는 POP 위에 구축됩니다: CustomStringConvertible, Equatable, Hashable, Codable — 모두 extension이 있는 프로토콜입니다.
Enum은 Swift에서 다른 언어보다 훨씬 더 강력한 구조입니다. 각 케이스는 연관 값(임의 유형의 데이터 집합)을 가질 수 있습니다. Extension 및 패턴 매칭과 결합하여 enum은 상태, 작업 결과 및 유한 상태 머신 모델링의 기초가 됩니다.
연관 값을 통해 각 케이스가 고유한 데이터를 저장할 수 있습니다. 예를 들어, loading 케이스는 데이터가 없고, success는 결과를 포함하며, failure는 오류를 포함합니다. Switch를 통한 패턴 매칭은 연관 값을 추출하고 각 케이스를 처리합니다. 컴파일러는 완전성을 확인합니다(exhaustive switch)—이것은 잊혀진 상태를 제거합니다.
enum NetworkResult<T> {
case loading
case success(T)
case failure(Error)
}
enum MediaFile {
case image(url: URL, width: Int, height: Int)
case video(url: URL, duration: Double)
case audio(url: URL, bitrate: Int)
}
func handle(_ result: NetworkResult<String>) {
switch result {
case .loading:
print("로딩 중...")
case .success(let data):
print("수신됨: \(data)")
case .failure(let error):
print("오류: \(error.localizedDescription)")
}
}
let file = MediaFile.video(url: URL(string: "https://example.com/video.mp4")!, duration: 120)
if case .video(let url, let duration) = file {
print("비디오 \(url) 재생 시간 \(duration)초")
}
Swift는 enum에 extension을 통해 메서드, 계산된 속성 및 프로토콜 준수를 추가할 수 있습니다. 원래 선언을 변경할 필요가 없습니다—코드는 깨끗하고 모듈화된 상태를 유지합니다. Extension의 메서드는 연관 값으로 작업하고, 파생 데이터를 계산하며, 비즈니스 로직을 구현할 수 있습니다.
extension MediaFile {
var fileName: String {
switch self {
case .image(let url, _, _),
.video(let url, _),
.audio(let url, _):
return url.lastPathComponent
}
}
var description: String {
switch self {
case .image(_, let w, let h):
return "이미지 \(w)x\(h)"
case .video(_, let d):
return "비디오 \(d)초"
case .audio(_, let b):
return "오디오 \(b) kbps"
}
}
}
extension MediaFile: Equatable {
static func == (lhs: MediaFile, rhs: MediaFile) -> Bool {
return lhs.fileName == rhs.fileName
}
}
let files: [MediaFile] = [file]
files.forEach { print($0.description) }
// "비디오 120.0s"
연관 값 + Extension이 있는 Enum은 Kotlin의 sealed 클래스나 다른 언어의 판별된 유니온을 대체합니다. Swift는 이 조합을 Result(표준 라이브러리의 표준 유형), Optional(Optional은 .none과 .some이 있는 enum입니다) 및 SwiftUI의 상태 처리에 사용합니다. 패턴 매칭은 모든 상태가 처리됨을 보장하므로 런타임 오류가 불가능합니다.
자주 묻는 질문
Struct — 값 유형, 할당 시 복사되며 상속을 지원하지 않습니다. Class — 참조 유형, 참조로 전달되며 상속, deinit 및 식별 검사(===)를 지원합니다. Apple은 struct로 시작하고 참조 의미론이 필요한 경우에만 class로 전환할 것을 권장합니다.
Actor — 스레드 안전 참조 유형. 컴파일러는 다른 컨텍스트에서 변경 가능한 상태에 대한 동시 액세스를 금지합니다. 여러 작업에서 액세스하는 상태 관리자, 캐시 및 서비스에 Actor를 사용하세요. Actor 메서드 호출에는 await가 필요합니다.
주요 항목: @State — 로컬 뷰 상태; @Binding — 소유권 없는 데이터 연결; @Published — ObservableObject에서 변경 게시; @StateObject — ObservableObject 소유; @EnvironmentObject — 환경에서 종속성. 사용자 정의 래퍼는 @propertyWrapper를 통해 생성됩니다.
POP — 클래스 상속 대신 protocol + extension이 동작을 정의하는 패러다임입니다. 유형이 여러 프로토콜을 준수하고 해당 구현을 무료로 얻습니다. 상속보다 구성을 사용하여 더 유연하고 안전합니다. Swift 표준 라이브러리(Equatable, Codable)는 POP 위에 구축됩니다.
Swift의 enum은 연관 값을 지원합니다—각 케이스는 다른 유형의 임의 데이터를 저장할 수 있습니다. 패턴 매칭(switch)과 결합하여 상태, 오류 및 도메인 주도 설계를 위한 강력한 모델을 제공합니다. Optional과 Result는 연관 값이 있는 표준 Swift enum입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.