정보

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)
}

 

 

 

🔥 클로저란?

클로저는 코드에서 사용될 수 있는 독립적인 코드 블록입니다. 이름 없는 함수라고 볼 수 있으며, 주로 함수 내부에서 인수로 전달하거나 결과로 반환하는 데 사용됩니다.

 

클로저는 다음 세 가지 형태로 나타날 수 있습니다:

  1. 전역 함수(Global Function): 이름이 있고 값을 캡처하지 않습니다.
  2. 중첩 함수(Nested Function): 이름이 있고 외부 함수의 값을 캡처할 수 있습니다.
  3. 클로저 표현식(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") }가 실행됩니다.

 

코드 흐름

  1. performTask 함수 호출 시, 클로저 { print("Task completed") }가 completion 매개변수로 전달됩니다.
  2. performTask 함수 내부에서 print("Task started")가 실행됩니다.
  3. 이후 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

 

 

🔥  코드 설명 🔥

  1. 매개변수
    • add: Bool:
      이 함수는 하나의 매개변수 add를 받습니다.
      • 타입: Bool
      • 의미: true면 덧셈을 수행하는 클로저를 반환하고, false면 뺄셈을 수행하는 클로저를 반환합니다.
  2. 반환값
    • (Int, Int) -> Int:
      이 함수는 두 개의 Int를 매개변수로 받아 하나의 Int를 반환하는 클로저를 반환합니다.
      • 예: return { $0 + $1 }는 두 숫자를 더하는 클로저입니다.
  3. 삼항 연산자
    • add ? { $0 + $1 } : { $0 - $1 }
      • add가 true면 { $0 + $1 } (덧셈 클로저)를 반환하고,
      • add가 false면 { $0 - $1 } (뺄셈 클로저)를 반환합니다.

 

전체 실행 흐름

  1. chooseOperation(add: true) 호출:
    • add가 true이므로 덧셈 클로저 { $0 + $1 }가 반환됩니다.
  2. 반환된 클로저가 operation 변수에 저장됩니다.
  3. 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에서 클로저는 변수나 상수에 할당 가능, 함수의 매개변수로 전달 가능, 함수의 반환값으로 사용 가능하기 때문에 일급 객체입니다. 이 특성 덕분에 클로저는 고차 함수와 콜백 같은 유연한 코드를 작성하는 데 핵심적인 역할을 합니다.