-
DSL과 SwiftUI: 도메인 특화 언어의 이해와 활용Apple🍎/Swift 2025. 5. 24. 23:02
들어가며
우리가 일상적으로 사용하는 프로그래밍 언어들은 대부분 범용적인 목적으로 설계되었습니다. Swift, Java, Python 같은 언어들은 웹 개발부터 모바일 앱, 데이터 분석까지 다양한 분야에서 활용할 수 있죠. 하지만 때로는 특정 분야에서 더 직관적이고 효율적으로 문제를 해결할 수 있는 전용 언어가 필요합니다. 이것이 바로 DSL(Domain Specific Language)의 개념입니다.
SwiftUI는 이러한 DSL의 훌륭한 예시입니다. UI 구성이라는 특정 도메인에 특화되어 설계된 언어로, 개발자가 더 직관적이고 선언적인 방식으로 사용자 인터페이스를 구성할 수 있게 해줍니다. 이 글에서는 DSL의 기본 개념부터 시작해서 SwiftUI가 어떻게 DSL로 작동하는지, 그리고 이를 가능하게 하는 @resultBuilder와 @ViewBuilder의 역할까지 깊이 있게 살펴보겠습니다.
DSL이란 무엇인가?
DSL의 정의와 특징
DSL(Domain Specific Language)은 특정 응용 분야나 도메인에 특화된 컴퓨터 언어입니다. 범용 프로그래밍 언어(GPL: General Purpose Language)와 달리, DSL은 특정 문제 영역을 효과적으로 해결하기 위해 설계됩니다.
우리는 이미 일상에서 많은 DSL을 사용하고 있습니다. SQL은 데이터베이스 조작에 특화된 언어이고, CSS는 웹 스타일링에, HTML은 문서 구조 표현에 특화되어 있습니다. 이들 각각은 해당 도메인에서 매우 직관적이고 표현력이 뛰어납니다.
-- SQL: 데이터베이스 도메인 DSL SELECT name, age FROM users WHERE age > 18 ORDER BY name ASC
이 SQL 문장을 보면, 데이터베이스에 익숙한 사람이라면 "18세 이상 사용자의 이름과 나이를 이름 순으로 정렬해서 가져와라"라는 의미를 즉시 이해할 수 있습니다. 만약 이를 일반적인 프로그래밍 언어로 표현한다면 훨씬 복잡해질 것입니다.
DSL의 장점
DSL이 제공하는 주요 장점들을 살펴보겠습니다.
표현력(Expressiveness): DSL은 해당 도메인의 개념을 직접적으로 표현할 수 있어 코드가 매우 간결해집니다. 복잡한 개념을 몇 줄의 코드로 표현할 수 있죠.
가독성(Readability): 도메인 전문가가 보기에 자연스럽고 이해하기 쉽습니다. 비개발자도 어느 정도 이해할 수 있는 경우가 많습니다.
생산성(Productivity): 해당 도메인의 문제 해결에 집중할 수 있어 개발 속도가 빨라집니다. 반복적인 코드를 줄이고 핵심 로직에 집중할 수 있습니다.
오류 감소(Error Reduction): 도메인에 특화된 제약 조건을 언어 차원에서 제공하므로 실수를 줄일 수 있습니다.
External DSL vs Internal DSL
DSL은 구현 방식에 따라 두 가지로 나눌 수 있습니다.
External DSL은 완전히 새로운 문법을 가진 독립적인 언어입니다. JSON, XML, SQL 등이 이에 해당합니다. 이들은 자체적인 파서와 인터프리터를 가지고 있습니다.
{ "name": "홍길동", "age": 30, "skills": ["Swift", "iOS", "SwiftUI"] }
Internal DSL은 기존 호스트 언어(여기서는 Swift) 안에서 특정 도메인을 위한 API나 문법을 제공하는 방식입니다. SwiftUI가 바로 이런 Internal DSL의 좋은 예시입니다.
GPL vs DSL: Swift와 SwiftUI 비교
Swift: 범용 프로그래밍 언어
Swift는 범용 프로그래밍 언어입니다. iOS 앱 개발부터 서버 사이드 개발, 시스템 프로그래밍까지 다양한 분야에서 활용할 수 있죠. 이런 유연성이 GPL의 큰 장점입니다.
// Swift로 계산기 클래스 구현 class Calculator { private var result: Double = 0 func add(_ value: Double) -> Calculator { result += value return self } func multiply(_ value: Double) -> Calculator { result *= value return self } func getResult() -> Double { return result } } // 사용 예시 let calculator = Calculator() .add(10) .multiply(2) .add(5) print(calculator.getResult()) // 25.0
이 코드는 Swift의 범용적 특성을 잘 보여줍니다. 객체 지향 프로그래밍, 함수형 프로그래밍 등 다양한 패러다임을 지원하며, 어떤 종류의 애플리케이션이든 만들 수 있습니다.
SwiftUI: UI 도메인 특화 언어
반면 SwiftUI는 UI 구성이라는 특정 도메인에 특화된 DSL입니다. UI를 구성하는 데 필요한 개념들이 언어의 핵심 요소로 내장되어 있습니다.
// SwiftUI로 동일한 계산기 UI 구현 struct CalculatorView: View { @State private var result: Double = 0 @State private var currentInput: String = "" var body: some View { VStack(spacing: 20) { // 결과 표시 영역 Text(String(result)) .font(.largeTitle) .foregroundColor(.primary) .frame(maxWidth: .infinity, alignment: .trailing) .padding() .background(Color.gray.opacity(0.1)) .cornerRadius(8) // 입력 영역 TextField("숫자를 입력하세요", text: $currentInput) .textFieldStyle(RoundedBorderTextFieldStyle()) .keyboardType(.decimalPad) // 버튼 영역 HStack(spacing: 15) { Button("더하기") { addToResult() } .buttonStyle(.borderedProminent) Button("곱하기") { multiplyResult() } .buttonStyle(.borderedProminent) Button("초기화") { resetCalculator() } .buttonStyle(.bordered) } } .padding() } private func addToResult() { if let value = Double(currentInput) { result += value currentInput = "" } } private func multiplyResult() { if let value = Double(currentInput) { result *= value currentInput = "" } } private func resetCalculator() { result = 0 currentInput = "" } }
이 SwiftUI 코드를 보면 UI 구성에 필요한 개념들이 언어의 핵심 요소로 제공되는 것을 알 수 있습니다. VStack, HStack, Text, Button 등은 모두 UI 도메인에 특화된 구성 요소들입니다.
선언적 vs 명령적 프로그래밍
SwiftUI의 가장 큰 특징 중 하나는 선언적 프로그래밍 패러다임을 따른다는 것입니다. 이는 DSL의 일반적인 특성이기도 합니다.
명령적 프로그래밍은 "어떻게(How)" 할 것인지에 초점을 맞춥니다. 단계별로 무엇을 해야 하는지 명시합니다.
// UIKit (명령적 방식) let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 20 stackView.distribution = .fill let titleLabel = UILabel() titleLabel.text = "계산기" titleLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold) titleLabel.textAlignment = .center stackView.addArrangedSubview(titleLabel) let resultLabel = UILabel() resultLabel.text = "0" resultLabel.font = UIFont.systemFont(ofSize: 36) resultLabel.textAlignment = .right resultLabel.backgroundColor = UIColor.systemGray6 stackView.addArrangedSubview(resultLabel) let addButton = UIButton(type: .system) addButton.setTitle("더하기", for: .normal) addButton.backgroundColor = UIColor.systemBlue addButton.setTitleColor(.white, for: .normal) addButton.layer.cornerRadius = 8 stackView.addArrangedSubview(addButton) view.addSubview(stackView)
선언적 프로그래밍은 "무엇을(What)" 원하는지에 초점을 맞춥니다. 최종 결과가 어떤 모습이어야 하는지 기술합니다.
// SwiftUI (선언적 방식) VStack(spacing: 20) { Text("계산기") .font(.title) .fontWeight(.bold) Text("0") .font(.largeTitle) .frame(maxWidth: .infinity, alignment: .trailing) .padding() .background(Color.gray.opacity(0.1)) Button("더하기") { // 액션 } .foregroundColor(.white) .background(Color.blue) .cornerRadius(8) }
선언적 방식이 훨씬 간결하고 의도가 명확하게 드러나는 것을 알 수 있습니다. 이는 UI라는 도메인의 특성을 잘 활용한 결과입니다.
@resultBuilder: DSL 제작의 핵심 도구
@resultBuilder의 등장 배경
Swift 5.4에서 도입된 @resultBuilder는 개발자가 Swift 언어 내에서 자신만의 DSL을 만들 수 있게 해주는 강력한 도구입니다. 이전에는 Function Builder라고 불렸지만, 더 명확한 의미를 전달하기 위해 이름이 변경되었습니다.
@resultBuilder가 없다면 SwiftUI와 같은 선언적 문법을 구현하기 매우 어려웠을 것입니다. 여러 개의 View를 자연스럽게 나열하고, 조건문과 반복문을 섞어서 사용하는 것이 불가능했겠죠.
@resultBuilder의 동작 원리
@resultBuilder는 컴파일 타임에 특별한 문법 변환을 수행합니다. 개발자가 작성한 코드를 분석해서 미리 정의된 메서드 호출로 변환하는 것이죠.
// 간단한 문자열 빌더 예제 @resultBuilder struct StringBuilder { // 여러 문자열을 하나로 합치기 static func buildBlock(_ components: String...) -> String { return components.joined(separator: " ") } // 조건부 문자열 처리 static func buildOptional(_ component: String?) -> String { return component ?? "" } // if-else 문 처리 static func buildEither(first component: String) -> String { return component } static func buildEither(second component: String) -> String { return component } // 배열(반복문) 처리 static func buildArray(_ components: [String]) -> String { return components.joined(separator: ", ") } }
이제 이 StringBuilder를 사용해서 문자열 DSL을 만들 수 있습니다.
@StringBuilder func createGreeting(name: String, showTime: Bool) -> String { "안녕하세요" name + "님" if showTime { "현재 시간은" DateFormatter().string(from: Date()) } "좋은 하루 되세요!" } let greeting = createGreeting(name: "홍길동", showTime: true) // 결과: "안녕하세요 홍길동님 현재 시간은 2025-05-24 좋은 하루 되세요!"
@resultBuilder의 변환 과정
위의 코드가 컴파일될 때 실제로는 다음과 같이 변환됩니다:
func createGreeting(name: String, showTime: Bool) -> String { return StringBuilder.buildBlock( "안녕하세요", name + "님", StringBuilder.buildOptional(showTime ? StringBuilder.buildBlock( "현재 시간은", DateFormatter().string(from: Date()) ) : nil ), "좋은 하루 되세요!" ) }
이렇게 @resultBuilder는 자연스러운 문법을 복잡한 메서드 호출로 변환해주는 역할을 합니다.
변환과정 자세히 알아보기
더보기컴파일러의 시각에서 보는 변환 과정
Swift 컴파일러가 @resultBuilder를 만나면 마치 번역가처럼 작동합니다. 우리가 쓴 "자연어 같은 코드"를 "기계가 이해할 수 있는 메서드 호출"로 번역하는 것이죠.
1단계: 구문 분석 (Syntax Analysis)
컴파일러는 먼저 우리 코드의 구조를 파악합니다. 예제를 다시 살펴보면서 컴파일러가 어떻게 "읽어내는지" 보겠습니다.
@StringBuilder func createGreeting(name: String, showTime: Bool) -> String { "안녕하세요" // 1. 문자열 리터럴 name + "님" // 2. 표현식 결과 (String) if showTime { // 3. 조건문 시작 "현재 시간은" // 4. 조건부 문자열 1 DateFormatter().string(from: Date()) // 5. 조건부 문자열 2 } // 6. 조건문 종료 "좋은 하루 되세요!" // 7. 문자열 리터럴 }
컴파일러는 이 코드를 보고 다음과 같이 구조를 인식합니다: "StringBuilder라는 result builder를 사용하는 함수가 있고, 그 안에 여러 개의 문자열 표현식과 하나의 if문이 있구나"
2단계: 구문 요소 식별과 매핑
이제 컴파일러는 각 구문 요소를 적절한 builder 메서드에 매핑합니다. 이때 미리 정의된 규칙을 따릅니다:
// StringBuilder에 정의된 메서드들을 다시 보면 @resultBuilder struct StringBuilder { // 여러 요소를 순서대로 나열 → buildBlock 사용 static func buildBlock(_ components: String...) -> String // if문으로 조건부 요소 → buildOptional 사용 static func buildOptional(_ component: String?) -> String // if-else문 → buildEither 사용 static func buildEither(first component: String) -> String static func buildEither(second component: String) -> String }
컴파일러는 코드의 각 부분을 다음과 같이 해석합니다.
순차적 나열 (1, 2, 7번)
"안녕하세요" name + "님" "좋은 하루 되세요!"
→ "이건 여러 요소를 순서대로 나열한 거니까 buildBlock을 써야겠다"
조건문 (3-6번)
if showTime { "현재 시간은" DateFormatter().string(from: Date()) }
→ "이건 조건부 코드니까 buildOptional을 써야겠다. 그리고 조건문 안의 여러 요소는 또 다른 buildBlock으로 묶어야겠다"
3단계: 중첩 구조 처리 (Nested Structure Processing)
여기서 흥미로운 점은 컴파일러가 중첩된 구조를 어떻게 처리하는지입니다. 조건문 안의 두 문자열을 먼저 처리하고, 그 결과를 조건문 전체에 적용합니다.
// 먼저 조건문 내부 처리 if showTime { "현재 시간은" // 내부 요소 1 DateFormatter().string(from: Date()) // 내부 요소 2 } // 컴파일러가 변환: 내부 먼저 buildBlock으로 묶기 if showTime { StringBuilder.buildBlock( "현재 시간은", DateFormatter().string(from: Date()) ) } // 그 다음 조건문 전체를 buildOptional로 감싸기 StringBuilder.buildOptional( showTime ? StringBuilder.buildBlock( "현재 시간은", DateFormatter().string(from: Date()) ) : nil )
4단계: 최종 변환 (Final Transformation)
모든 부분을 분석한 후, 컴파일러는 전체를 하나의 큰 buildBlock 호출로 묶습니다:
// 최종 변환된 코드 func createGreeting(name: String, showTime: Bool) -> String { return StringBuilder.buildBlock( "안녕하세요", // 첫 번째 요소 name + "님", // 두 번째 요소 StringBuilder.buildOptional( // 세 번째 요소 (조건부) showTime ? StringBuilder.buildBlock( "현재 시간은", DateFormatter().string(from: Date()) ) : nil ), "좋은 하루 되세요!" // 네 번째 요소 ) }
컴파일러가 사용하는 변환 규칙들
컴파일러는 미리 정의된 패턴 매칭 규칙을 사용해서 이런 변환을 수행합니다. 각 구문 패턴이 어떤 메서드로 변환되는지 살펴보겠습니다.
규칙 1: 순차적 나열 → buildBlock
// 이런 패턴을 보면 expression1 expression2 expression3 // 이렇게 변환 BuilderType.buildBlock(expression1, expression2, expression3)
규칙 2: 조건문 → buildOptional 또는 buildEither
// if 문 (else 없음) if condition { content } // 변환 → BuilderType.buildOptional(condition ? content : nil) // if-else 문 if condition { trueContent } else { falseContent } // 변환 → BuilderType.buildEither(first: condition ? trueContent : falseContent) // 또는 BuilderType.buildEither(second: !condition ? falseContent : trueContent)
규칙 3: 반복문 → buildArray
// for 문이나 배열 처리 for item in items { processItem(item) } // 변환 → BuilderType.buildArray(items.map { item in processItem(item) })
실제 SwiftUI에서의 복잡한 변환 예제
이제 더 복잡한 SwiftUI 예제로 이 과정을 확인해보겠습니다.
// 우리가 작성한 복잡한 SwiftUI 코드 @ViewBuilder var complexView: some View { Text("제목") if user.isLoggedIn { Text("환영합니다, \(user.name)님!") if user.hasNotifications { Button("알림 확인") { } } } else { Text("로그인이 필요합니다") Button("로그인") { } } ForEach(items) { item in Text(item.title) } }
컴파일러가 이를 어떻게 변환하는지 단계별로 봅시다:
1단계: 가장 안쪽 조건문부터 처리
// user.hasNotifications 조건문 ViewBuilder.buildOptional( user.hasNotifications ? Button("알림 확인") { } : nil )
2단계: 로그인 상태 조건문의 각 분기 처리
// if user.isLoggedIn 분기 (true case) ViewBuilder.buildBlock( Text("환영합니다, \(user.name)님!"), ViewBuilder.buildOptional( user.hasNotifications ? Button("알림 확인") { } : nil ) ) // else 분기 (false case) ViewBuilder.buildBlock( Text("로그인이 필요합니다"), Button("로그인") { } )
3단계: 전체 if-else를 buildEither로 감싸기
ViewBuilder.buildEither( first: user.isLoggedIn ? ViewBuilder.buildBlock( Text("환영합니다, \(user.name)님!"), ViewBuilder.buildOptional( user.hasNotifications ? Button("알림 확인") { } : nil ) ) : nil, second: !user.isLoggedIn ? ViewBuilder.buildBlock( Text("로그인이 필요합니다"), Button("로그인") { } ) : nil )
4단계: 최종적으로 모든 것을 buildBlock으로 묶기
// 최종 변환된 코드 (의사 코드) var complexView: some View { return ViewBuilder.buildBlock( Text("제목"), // 첫 번째 요소 ViewBuilder.buildEither( // 두 번째 요소 (복잡한 조건문) first: user.isLoggedIn ? ViewBuilder.buildBlock( Text("환영합니다, \(user.name)님!"), ViewBuilder.buildOptional( user.hasNotifications ? Button("알림 확인") { } : nil ) ) : nil, second: !user.isLoggedIn ? ViewBuilder.buildBlock( Text("로그인이 필요합니다"), Button("로그인") { } ) : nil ), ForEach(items) { item in // 세 번째 요소 (반복문) Text(item.title) } ) }
컴파일러가 이 모든 걸 어떻게 알까?
여기서 정말 흥미로운 점은 "컴파일러가 어떻게 이런 복잡한 변환 규칙을 알고 있느냐"는 것입니다. 답은 Swift 언어 명세에 이런 규칙들이 명시되어 있기 때문입니다.
Swift 컴파일러에는 @resultBuilder를 처리하는 전용 모듈이 있고, 이 모듈이 다음과 같은 작업을 수행합니다.
- 구문 트리 분석: 코드를 추상 구문 트리(AST)로 파싱
- 패턴 매칭: 각 구문 패턴을 미리 정의된 규칙과 매칭
- 메서드 검색: 해당 @resultBuilder 타입에서 적절한 메서드 찾기
- 코드 생성: 원본 코드를 메서드 호출로 대체하는 새로운 AST 생성
이 모든 과정이 컴파일 타임에 일어나므로, 런타임에는 변환된 코드만 실행됩니다. 그래서 성능 손실 없이 자연스러운 DSL 문법을 사용할 수 있는 것이죠.
여기서 하나 주의할 점은 @resultBuilder가 하는 일은 구조 변환이지 값 평가가 아니다라는 점입니다. 컴파일 과정에서 변환이 일어난다는 말 때문에 헷갈릴 수 있는데 실제 값 평가는 런타임에 이루어지며 어떤 구조로 조합할지에 대해서만 사전에 변환 작업이 이루어집니다.
@ViewBuilder: SwiftUI DSL의 핵심 엔진
@ViewBuilder의 역할
@ViewBuilder는 SwiftUI에서 제공하는 특별한 @resultBuilder입니다. SwiftUI의 선언적 UI 구성을 가능하게 하는 핵심 요소죠. 여러 개의 View를 자연스럽게 조합하고, 조건부 렌더링과 반복 렌더링을 직관적으로 표현할 수 있게 해줍니다.
@ViewBuilder 없이는 불가능한 것들
@ViewBuilder가 없다면 SwiftUI에서 다음과 같은 코드를 작성할 수 없을 것입니다.
// 이런 자연스러운 코드가 불가능했을 것 VStack { Text("제목") Text("부제목") if user.isLoggedIn { Text("환영합니다, \(user.name)님!") Button("로그아웃") { logout() } } else { Button("로그인") { showLoginSheet = true } } ForEach(items) { item in ItemRow(item: item) } }
대신 모든 것을 명시적으로 컨테이너에 감싸야 했을 것입니다.
// @ViewBuilder 없이 작성해야 했을 코드 VStack { TupleView(( Text("제목"), Text("부제목"), Group { if user.isLoggedIn { VStack { Text("환영합니다, \(user.name)님!") Button("로그아웃") { logout() } } } else { Button("로그인") { showLoginSheet = true } } }, ForEach(items) { item in ItemRow(item: item) } )) }
@ViewBuilder의 내부 동작
@ViewBuilder가 어떻게 작동하는지 좀 더 자세히 살펴보겠습니다.
// 우리가 작성하는 코드 VStack { Text("첫 번째") Text("두 번째") if showThird { Text("세 번째") } }
이 코드는 컴파일 타임에 다음과 같이 변환됩니다:
// 실제 변환된 코드 VStack { ViewBuilder.buildBlock( Text("첫 번째"), Text("두 번째"), ViewBuilder.buildOptional(showThird ? Text("세 번째") : nil) ) }
ViewBuilder의 핵심 메서드들
SwiftUI의 ViewBuilder는 다양한 상황을 처리하기 위한 메서드들을 제공합니다.
@resultBuilder public struct ViewBuilder { // 단일 View 처리 public static func buildBlock<Content>(_ content: Content) -> Content where Content : View // 두 개 View 조합 public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View // 세 개 View 조합 (최대 10개까지 오버로드됨) public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View // 조건부 View (옵셔널) public static func buildOptional<Content>(_ content: Content?) -> Content? where Content : View // if-else 문 처리 public static func buildEither<TrueContent, FalseContent>(first content: TrueContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View public static func buildEither<TrueContent, FalseContent>(second content: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View // 배열/반복문 처리 public static func buildArray<Content>(_ components: [Content]) -> ForEach<Range<Int>, Int, Content> where Content : View }
결론
DSL(Domain Specific Language)은 특정 도메인의 문제를 그 도메인의 언어로 자연스럽게 표현할 수 있게 해주는 강력한 도구입니다. SwiftUI는 UI 구성이라는 도메인에 특화된 훌륭한 DSL의 예시이며, 이를 통해 개발자는 더 직관적이고 선언적인 방식으로 사용자 인터페이스를 구성할 수 있습니다.
Swift의 @resultBuilder는 이러한 DSL을 언어 내부에서 구현할 수 있게 해주는 메타 프로그래밍 도구입니다. 컴파일 타임에 자연스러운 문법을 복잡한 메서드 호출로 변환하여, 마치 새로운 언어를 사용하는 것 같은 경험을 제공합니다.
@ViewBuilder는 SwiftUI DSL의 핵심 엔진으로, 여러 View의 조합, 조건부 렌더링, 반복 렌더링 등을 자연스럽게 처리할 수 있게 해줍니다. 이를 통해 복잡한 UI 로직도 매우 직관적이고 읽기 쉬운 코드로 표현할 수 있습니다.
실제 프로젝트에서는 @resultBuilder를 활용하여 다양한 도메인별 DSL을 만들 수 있습니다. 레이아웃 구성, 폼 검증, 네트워크 요청 체이닝, 상태 관리 등 각각의 도메인에 특화된 DSL을 만들면 코드의 가독성과 유지보수성을 크게 향상시킬 수 있습니다.
DSL의 핵심은 "해당 도메인의 전문가가 자연스럽게 이해할 수 있는 언어"를 만드는 것입니다. 이를 통해 코드는 단순한 명령어의 나열이 아닌, 도메인 지식을 표현하는 언어가 됩니다. SwiftUI와 @resultBuilder는 이러한 철학을 Swift 생태계에서 실현할 수 있게 해주는 훌륭한 도구들입니다.
앞으로 iOS 개발을 할 때, 단순히 SwiftUI의 기능을 사용하는 것을 넘어서 DSL의 관점에서 접근해보시기 바랍니다. 여러분만의 도메인별 DSL을 만들어 보고, 팀 내에서 공통으로 사용할 수 있는 선언적 API를 설계해보세요. 이를 통해 더 표현력 있고 유지보수하기 쉬운 코드를 작성할 수 있을 것입니다.
'Apple🍎 > Swift' 카테고리의 다른 글
좀 더 Low 하게 가보자~ Swift 저수준 메모리 관리 (0) 2025.04.25 Swift 동시성 모델 (0) 2025.04.18 DispatchQueue와 DispatchWorkItem 비교하기 (0) 2025.04.17 Swift에서의 데이터 레이스 방지를 위한 동기화 기법 (0) 2025.04.16 Swift 6 : Typed Throws ( 에러도 타입을 줘서 더 명확히 처리하자.) (0) 2025.04.14