정보
Swift의 클로저와 함수의 차이점은 무엇인가요?
밤새는 탐험가89
2024. 11. 19. 07:29
Swift에서 클로저와 함수는 코드의 재사용성을 높이기 위해 사용되는 코드 블록입니다. 둘 다 비슷한 역할을 하지만 다음과 같은 차이점이 있습니다:
이름 유무
- 함수는 이름이 있는 코드 블록입니다.
func greet(name: String) -> String {
return "Hello, \(name)!"
}
- 클로저는 이름이 없는 코드 블록입니다. 주로 변수에 할당하거나 인자로 전달됩니다.
let greet: (String) -> String = { name in
return "Hello, \(name)!"
}
구문 축약
- 클로저는 간결성을 위해 매개변수 및 반환 타입 추론, 키워드 생략이 가능합니다.
let greet = { (name: String) in "Hello, \(name)!" }
선언 위치
- 함수는 특정 컨텍스트에서 독립적으로 선언됩니다.
- 예: 클래스, 구조체, 파일 레벨 등
- 클로저는 코드 블록 내부에서 즉석으로 정의될 수 있습니다.
- 예: 배열의 map 메서드나 completionHandler 등
let names = ["Alice", "Bob", "Charlie"]
let greetings = names.map { "Hello, \($0)!" }
캡처(Capture)
- 클로저는 외부 컨텍스트의 변수나 상수를 캡처할 수 있습니다.
func makeIncrementer(incrementAmount: Int) -> () -> Int {
var total = 0
return {
total += incrementAmount
return total
}
}
let incrementer = makeIncrementer(incrementAmount: 2)
print(incrementer()) // 2
print(incrementer()) // 4
- 함수는 기본적으로 외부 변수를 캡처하지 않지만, 클로저처럼 동작하려면 내부에서 클로저를 활용할 수 있습니다.
키워드
- 함수는 func 키워드를 사용하여 정의됩니다.
- 클로저는 {}를 사용하여 정의되며, 매개변수와 반환 타입을 명시하거나 생략할 수 있습니다.
사용 방식
- 함수는 명시적 호출과 더불어 객체의 메서드로 주로 사용됩니다.
- 클로저는 콜백(callback), 고차 함수의 매개변수로 주로 사용됩니다
func fetchData(completion: (String) -> Void) {
completion("Data loaded")
}
fetchData { data in
print(data)
}
🔥 클로저란?
클로저는 코드에서 사용될 수 있는 독립적인 코드 블록입니다. 이름 없는 함수라고 볼 수 있으며, 주로 함수 내부에서 인수로 전달하거나 결과로 반환하는 데 사용됩니다.
클로저는 다음 세 가지 형태로 나타날 수 있습니다:
- 전역 함수(Global Function): 이름이 있고 값을 캡처하지 않습니다.
- 중첩 함수(Nested Function): 이름이 있고 외부 함수의 값을 캡처할 수 있습니다.
- 클로저 표현식(Closure Expression): 이름이 없는 간단한 구문으로 작성된 클로저입니다.
클로저의 특징
- 변수와 상수 캡처(Capture)
- 클로저는 외부 컨텍스트에 정의된 변수나 상수를 캡처할 수 있습니다.
- 이를 통해 외부 변수의 값을 참조하거나 수정할 수 있습니다.
func makeCounter() -> () -> Int {
var count = 0
let incrementer = {
count += 1
return count
}
return incrementer
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2
- 경량화된 문법
- 클로저는 매개변수 타입, 반환 타입, return 키워드 등을 생략하여 간결하게 작성할 수 있습니다.
let names = ["Anna", "Ben", "Charlie"]
let reversed = names.sorted { $0 > $1 }
print(reversed) // ["Charlie", "Ben", "Anna"]
- 참조 타입(Reference Type)
- 클로저는 참조 타입으로 동작합니다. 클로저가 변수나 상수를 캡처하면, 해당 변수는 클로저의 생명 주기 동안 유지됩니다.
- 탈출 클로저(Escaping Closure)
- 함수의 실행이 끝난 뒤에도 클로저가 호출될 수 있도록 허용하려면 @escaping 키워드를 사용합니다.
- 주로 비동기 작업에서 활용됩니다.
func performAsyncTask(completion: @escaping () -> Void) {
DispatchQueue.global().async {
// 비동기 작업 수행
completion()
}
}
- 자동 클로저(AutoClosure)
- 자동 클로저는 코드 블록을 자동으로 래핑하여 전달할 수 있도록 설계된 클로저입니다.
- @autoclosure 키워드를 사용하며, 주로 간단한 표현식 평가를 지연시키는 데 사용됩니다.
func logMessage(_ message: @autoclosure () -> String) {
print("Log: \(message())")
}
logMessage("This is a log message.")
클로저의 사용 사례
- 콜백 함수
- 클로저는 비동기 작업의 결과를 처리하는 콜백 함수로 자주 사용됩니다.
func fetchData(completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
let data = "Server Response"
completion(data)
}
}
fetchData { response in
print("Received: \(response)")
}
- 고차 함수
- Swift의 map, filter, reduce와 같은 고차 함수는 클로저를 매개변수로 사용합니다.
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled) // [2, 4, 6, 8, 10]
- UI 구성
- 클로저는 UIView.animate와 같은 UI 작업에서도 사용됩니다.
UIView.animate(withDuration: 0.5) {
view.alpha = 0.5
}
- 캡처를 활용한 상태 관리
- 클로저는 외부 상태를 캡처하여 동작을 제어할 수 있습니다.
func counter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let increment = counter()
print(increment()) // 1
print(increment()) // 2
클로저가 일급 객체(First-Class Citizen)인 이유는 무엇인가요?
일급 객체의 조건
- 변수나 상수에 할당 가능
- 클로저는 변수나 상수에 할당될 수 있습니다.
let greet = { (name: String) -> String in
return "Hello, \(name)!"
}
print(greet("Alice")) // "Hello, Alice!"
- 다른 함수의 매개변수로 전달 가능
- 클로저는 다른 함수에 매개변수로 전달될 수 있습니다. 이는 콜백(callback)이나 고차 함수에서 자주 사용됩니다.
func performTask(completion: () -> Void) {
print("Task started")
completion()
}
performTask {
print("Task completed")
}
🔥 performTask 함수 정의
- performTask 함수는 매개변수로 클로저를 받습니다.
- 여기서 completion: () -> Void는 클로저 타입을 나타내며, 매개변수로 아무것도 받지 않고(()), 반환값도 없는(Void) 클로저를 의미합니다.
func performTask(completion: () -> Void) {
print("Task started")
completion() // 전달받은 클로저를 호출
}
performTask 호출 시 클로저 전달
- performTask를 호출할 때, 클로저가 인자로 전달됩니다.
- 아래 부분에서 { print("Task completed") }가 completion 매개변수로 전달됩니다.
performTask {
print("Task completed")
}
전달된 클로저 실행
- 전달된 클로저는 performTask 함수 내부에서 completion()으로 실행됩니다.
- 실행 흐름은 다음과 같습니다:
- "Task started"가 출력됩니다.
- completion() 호출로 인해 전달된 클로저 { print("Task completed") }가 실행됩니다.
코드 흐름
- performTask 함수 호출 시, 클로저 { print("Task completed") }가 completion 매개변수로 전달됩니다.
- performTask 함수 내부에서 print("Task started")가 실행됩니다.
- 이후 completion()으로 전달된 클로저가 실행되어 "Task completed"가 출력됩니다.
매개변수로 전달되는 부분
다시 정리하자면, 이 코드는 클로저가 매개변수로 전달 가능하다는 특징을 보여줍니다.
- 매개변수: completion
- 전달된 클로저: { print("Task completed") }
전체 코드 흐름
func performTask(completion: () -> Void) {
print("Task started") // 1. "Task started" 출력
completion() // 2. 전달된 클로저 실행
}
performTask {
print("Task completed") // 3. 클로저 실행 시 "Task completed" 출력
}
// 출력:
// Task started
// Task completed
Swift에서 클로저가 일급 객체인 이유
Swift에서 클로저는 위의 조건을 모두 만족하므로 일급 객체로 간주됩니다. 다음은 이를 실제로 사용하는 방식입니다.
변수에 클로저를 할당
- 클로저는 변수나 상수에 저장될 수 있어, 재사용하거나 다른 컨텍스트에서 호출이 가능합니다.
let add = { (a: Int, b: Int) -> Int in
return a + b
}
print(add(2, 3)) // 5
고차 함수의 매개변수로 전달
- 클로저는 함수에 매개변수로 전달될 수 있어 유연하고 동적인 로직을 구현할 수 있습니다.
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 } // map에 클로저 전달
print(doubled) // [2, 4, 6, 8, 10]
함수에서 반환값으로 사용
- 클로저를 반환하면 실행 시점에 따라 동작을 변경할 수 있는 유연성을 제공합니다.
func chooseOperation(add: Bool) -> (Int, Int) -> Int {
return add ? { $0 + $1 } : { $0 - $1 }
}
let operation = chooseOperation(add: true)
print(operation(5, 3)) // 8
🔥 코드 설명 🔥
- 매개변수
- add: Bool:
이 함수는 하나의 매개변수 add를 받습니다.- 타입: Bool
- 의미: true면 덧셈을 수행하는 클로저를 반환하고, false면 뺄셈을 수행하는 클로저를 반환합니다.
- add: Bool:
- 반환값
- (Int, Int) -> Int:
이 함수는 두 개의 Int를 매개변수로 받아 하나의 Int를 반환하는 클로저를 반환합니다.- 예: return { $0 + $1 }는 두 숫자를 더하는 클로저입니다.
- (Int, Int) -> Int:
- 삼항 연산자
- add ? { $0 + $1 } : { $0 - $1 }
- add가 true면 { $0 + $1 } (덧셈 클로저)를 반환하고,
- add가 false면 { $0 - $1 } (뺄셈 클로저)를 반환합니다.
- add ? { $0 + $1 } : { $0 - $1 }
전체 실행 흐름
- chooseOperation(add: true) 호출:
- add가 true이므로 덧셈 클로저 { $0 + $1 }가 반환됩니다.
- 반환된 클로저가 operation 변수에 저장됩니다.
- operation(5, 3) 호출 시 클로저 { $0 + $1 }가 실행되어 8을 반환합니다.
확장 예제
다른 연산자를 추가하고 싶다면 switch를 사용해서 더 다양한 클로저를 반환할 수도 있습니다.
func chooseOperation(symbol: String) -> (Int, Int) -> Int {
switch symbol {
case "+":
return { $0 + $1 }
case "-":
return { $0 - $1 }
case "*":
return { $0 * $1 }
default:
return { $0 / $1 }
}
}
let multiply = chooseOperation(symbol: "*")
print(multiply(4, 2)) // 8
캡처(Capture) 가능
- 클로저는 외부 변수와 상수를 캡처하여, 실행 시점에도 해당 값을 유지합니다.
- 이는 일반적인 데이터 구조나 함수로는 어렵지만 클로저로 가능합니다.
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2
Swift에서 클로저는 변수나 상수에 할당 가능, 함수의 매개변수로 전달 가능, 함수의 반환값으로 사용 가능하기 때문에 일급 객체입니다. 이 특성 덕분에 클로저는 고차 함수와 콜백 같은 유연한 코드를 작성하는 데 핵심적인 역할을 합니다.