λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ₯– Bread Basics/Swift

Swift 곡식 λ¬Έμ„œ 정리 - ν΄λ‘œμ € (Closures)

by BreadDev 2025. 4. 11.
728x90

μ•ˆλ…•ν•˜μ„Έμš”. 이번 ν¬μŠ€νŠΈμ—μ„œλŠ” ν΄λ‘œμ €(Closures)에 λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. 

πŸ“Œ ν΄λ‘œμ €λž€?

ν΄λ‘œμ €λŠ” μ½”λ“œμ—μ„œ μ „λ‹¬ν•˜κ³  μ‚¬μš©ν•  수 μžˆλŠ” 자체 ν¬ν•¨λœ κΈ°λŠ₯ λΈ”λ‘μž…λ‹ˆλ‹€. Cλ‚˜ Objective-C의 블둝(blocks), λ‹€λ₯Έ μ–Έμ–΄μ˜ λžŒλ‹€(lambdas)와 μœ μ‚¬ν•œ κ°œλ…μž…λ‹ˆλ‹€.

ν΄λ‘œμ €(Closure): 독립적인 μ½”λ“œ λΈ”λ‘μœΌλ‘œ, ν•¨μˆ˜μ²˜λŸΌ μž‘λ™ν•˜μ§€λ§Œ 이름이 없을 μˆ˜λ„ 있으며 μ£Όλ³€ μ»¨ν…μŠ€νŠΈμ˜ 값을 μΊ‘μ²˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Swiftμ—μ„œ ν΄λ‘œμ €λŠ” μ„Έ κ°€μ§€ ν˜•νƒœλ‘œ λ‚˜νƒ€λ‚©λ‹ˆλ‹€:

  1. μ „μ—­ ν•¨μˆ˜: 이름이 있고 μ–΄λ–€ 값도 μΊ‘μ²˜ν•˜μ§€ μ•ŠλŠ” ν΄λ‘œμ €
  2. 쀑첩 ν•¨μˆ˜: 이름이 있고 λ‘˜λŸ¬μ‹Ό ν•¨μˆ˜μ—μ„œ 값을 μΊ‘μ²˜ν•  수 μžˆλŠ” ν΄λ‘œμ €
  3. ν΄λ‘œμ € ν‘œν˜„μ‹: 이름이 μ—†κ³  μ£Όλ³€ μ»¨ν…μŠ€νŠΈμ—μ„œ 값을 μΊ‘μ²˜ν•  수 μžˆλŠ” κ²½λŸ‰ ꡬ문

πŸ“Œ ν΄λ‘œμ € ν‘œν˜„μ‹ ꡬ문

ν΄λ‘œμ € ν‘œν˜„μ‹μ€ κ°„κ²°ν•œ 인라인 ꡬ문으둜 ν΄λ‘œμ €λ₯Ό μž‘μ„±ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€. κΈ°λ³Έ ꡬ문은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

{ (parameters) -> ReturnType in
    statements
}

이 κ°œλ…μ„ μ„€λͺ…ν•˜κΈ° μœ„ν•΄ λ°°μ—΄ μ •λ ¬ 예제λ₯Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€:

// μ •λ ¬ν•  λ°°μ—΄
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// 일반 ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•œ μ •λ ¬
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

// ν΄λ‘œμ € ν‘œν˜„μ‹μ„ μ‚¬μš©ν•œ μ •λ ¬
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

sorted(by:): λ°°μ—΄μ˜ μš”μ†Œλ₯Ό μ •λ ¬ν•˜λŠ” λ©”μ„œλ“œλ‘œ, μ •λ ¬ 기쀀을 μ •μ˜ν•˜λŠ” ν΄λ‘œμ €λ₯Ό 인자둜 λ°›μŠ΅λ‹ˆλ‹€.

πŸ“Œ ν΄λ‘œμ € μ΅œμ ν™” 기법

SwiftλŠ” ν΄λ‘œμ € μ½”λ“œλ₯Ό 더 κ°„κ²°ν•˜κ²Œ μž‘μ„±ν•  수 μžˆλŠ” μ—¬λŸ¬ μ΅œμ ν™” 기법을 μ œκ³΅ν•©λ‹ˆλ‹€:

1. νƒ€μž… μΆ”λ‘ 

μ»¨ν…μŠ€νŠΈμ—μ„œ νƒ€μž…μ„ μœ μΆ”ν•  수 μžˆμ„ λ•ŒλŠ” νƒ€μž… λͺ…μ‹œλ₯Ό μƒλž΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })

νƒ€μž… μΆ”λ‘ (Type Inference): Swiftκ°€ μ½”λ“œμ˜ μ»¨ν…μŠ€νŠΈλ₯Ό 기반으둜 λ³€μˆ˜λ‚˜ μƒμˆ˜μ˜ νƒ€μž…μ„ μžλ™μœΌλ‘œ κ²°μ •ν•˜λŠ” κΈ°λŠ₯

2. 단일 ν‘œν˜„μ‹μ˜ μ•”μ‹œμ  λ°˜ν™˜

ν΄λ‘œμ €κ°€ 단일 ν‘œν˜„μ‹λ§Œ ν¬ν•¨ν•˜λŠ” 경우 return ν‚€μ›Œλ“œλ₯Ό μƒλž΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })

3. μΆ•μ•½ 인수 이름

인라인 ν΄λ‘œμ €μ—μ„œλŠ” $0, $1, $2 λ“±κ³Ό 같은 μΆ•μ•½ 인수 이름을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

reversedNames = names.sorted(by: { $0 > $1 })

μΆ•μ•½ 인수 이름(Shorthand Argument Names): ν΄λ‘œμ €μ—μ„œ μžλ™μœΌλ‘œ μ œκ³΅λ˜λŠ” $0, $1 λ“±μ˜ νŒŒλΌλ―Έν„° μ΄λ¦„μœΌλ‘œ, 각각 첫 번째, 두 번째 인자λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€.

4. μ—°μ‚°μž λ©”μ„œλ“œ

비ꡐ μ—°μ‚°μžλŠ” κ·Έ 자체둜 (String, String) -> Bool νƒ€μž…μ˜ ν•¨μˆ˜μ΄λ―€λ‘œ 직접 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

reversedNames = names.sorted(by: >)

πŸ“Œ ν›„ν–‰ ν΄λ‘œμ € (Trailing Closures)

ν•¨μˆ˜μ˜ λ§ˆμ§€λ§‰ μΈμžκ°€ ν΄λ‘œμ €μΌ λ•Œ, ν•¨μˆ˜ 호좜 κ΄„ν˜Έ 뒀에 ν΄λ‘œμ €λ₯Ό μž‘μ„±ν•˜λŠ” λ¬Έλ²•μž…λ‹ˆλ‹€:

// ν›„ν–‰ ν΄λ‘œμ € ꡬ문
reversedNames = names.sorted() { $0 > $1 }

// ν΄λ‘œμ €κ°€ μœ μΌν•œ 인자라면 κ΄„ν˜Έλ„ μƒλž΅ κ°€λŠ₯
reversedNames = names.sorted { $0 > $1 }

ν›„ν–‰ ν΄λ‘œμ €(Trailing Closure): ν•¨μˆ˜ 호좜 μ‹œ λ§ˆμ§€λ§‰ μΈμžκ°€ ν΄λ‘œμ €μΈ 경우, κ΄„ν˜Έ 밖에 ν΄λ‘œμ €λ₯Ό μž‘μ„±ν•  수 μžˆλŠ” 문법

ν›„ν–‰ ν΄λ‘œμ €λŠ” ν΄λ‘œμ €κ°€ κΈΈκ±°λ‚˜ λ³΅μž‘ν•  λ•Œ 특히 μœ μš©ν•©λ‹ˆλ‹€:

let numbers = [16, 58, 510]

let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    // λ³΅μž‘ν•œ λ³€ν™˜ 둜직...
    return output
}

μ—¬λŸ¬ ν΄λ‘œμ €κ°€ μžˆλŠ” ν•¨μˆ˜μ˜ 경우:

func loadPicture(from server: Server, 
                 completion: (Picture) -> Void, 
                 onFailure: () -> Void) {
    // ν•¨μˆ˜ κ΅¬ν˜„...
}

// μ—¬λŸ¬ ν›„ν–‰ ν΄λ‘œμ € μ‚¬μš©
loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print("사진을 λ‹€μš΄λ‘œλ“œν•  수 μ—†μŠ΅λ‹ˆλ‹€.")
}

πŸ“Œ κ°’ 캑처 (Capturing Values)

ν΄λ‘œμ €λŠ” μ •μ˜λœ μ»¨ν…μŠ€νŠΈμ˜ μƒμˆ˜μ™€ λ³€μˆ˜λ₯Ό μΊ‘μ²˜ν•˜μ—¬ λ‚˜μ€‘μ— μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이것은 ν΄λ‘œμ €κ°€ μƒμ„±λœ ν™˜κ²½μ˜ 값을 "κΈ°μ–΅"ν•  수 있게 ν•΄μ€λ‹ˆλ‹€:

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30

κ°’ 캑처(Capturing Values): ν΄λ‘œμ €κ°€ μžμ‹ μ΄ μ •μ˜λœ ν™˜κ²½μ˜ λ³€μˆ˜λ‚˜ μƒμˆ˜λ₯Ό μ°Έμ‘°ν•˜μ—¬ μ €μž₯ν•˜λŠ” 것

이 μ˜ˆμ œμ—μ„œ incrementer ν•¨μˆ˜λŠ” runningTotalκ³Ό amount λ³€μˆ˜λ₯Ό μΊ‘μ²˜ν•˜μ—¬, makeIncrementer ν•¨μˆ˜κ°€ λ°˜ν™˜λœ 후에도 이 값듀을 κΈ°μ–΅ν•˜κ³  μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ“Œ ν΄λ‘œμ €λŠ” μ°Έμ‘° νƒ€μž… (Closures Are Reference Types)

ν•¨μˆ˜μ™€ ν΄λ‘œμ €λŠ” μ°Έμ‘° νƒ€μž…μž…λ‹ˆλ‹€. 즉, ν΄λ‘œμ €λ₯Ό λ³€μˆ˜λ‚˜ μƒμˆ˜μ— ν• λ‹Ήν•  λ•Œ μ‹€μ œλ‘œλŠ” ν΄λ‘œμ €μ— λŒ€ν•œ μ°Έμ‘°κ°€ ν• λ‹Ήλ©λ‹ˆλ‹€:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // 40
incrementByTen()     // 50

μ°Έμ‘° νƒ€μž…(Reference Type): μΈμŠ€ν„΄μŠ€κ°€ λ³΅μ‚¬λ˜λŠ” λŒ€μ‹  참쑰둜 μ „λ‹¬λ˜λŠ” νƒ€μž…. 같은 ν΄λ‘œμ €λ₯Ό μ°Έμ‘°ν•˜λŠ” μ—¬λŸ¬ λ³€μˆ˜λŠ” 같은 ν΄λ‘œμ € μΈμŠ€ν„΄μŠ€λ₯Ό κ°€λ¦¬ν‚΅λ‹ˆλ‹€.

πŸ“Œ νƒˆμΆœ ν΄λ‘œμ € (Escaping Closures)

ν΄λ‘œμ €κ°€ ν•¨μˆ˜μ˜ 인자둜 μ „λ‹¬λ˜μ—ˆμ§€λ§Œ ν•¨μˆ˜κ°€ λ°˜ν™˜λœ 후에도 호좜될 수 μžˆλ‹€λ©΄ 이λ₯Ό "νƒˆμΆœ ν΄λ‘œμ €"라고 ν•©λ‹ˆλ‹€:

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

νƒˆμΆœ ν΄λ‘œμ €(@escaping): ν•¨μˆ˜κ°€ λ°˜ν™˜λœ 후에도 싀행될 수 μžˆλŠ” ν΄λ‘œμ €. ν•¨μˆ˜ μ™ΈλΆ€ λ³€μˆ˜μ— μ €μž₯λ˜κ±°λ‚˜ 비동기 μž‘μ—…μ— μ‚¬μš©λ  λ•Œ ν”νžˆ ν•„μš”ν•©λ‹ˆλ‹€.

νƒˆμΆœ ν΄λ‘œμ €μ—μ„œ selfλ₯Ό μ°Έμ‘°ν•  λ•ŒλŠ” νŠΉλ³„ν•œ μ£Όμ˜κ°€ ν•„μš”ν•©λ‹ˆλ‹€:

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }  // self λͺ…μ‹œμ  μ‚¬μš©
        someFunctionWithNonescapingClosure { x = 200 }    // self μ•”μ‹œμ  μ‚¬μš© κ°€λŠ₯
    }
}

Swift 5.3λΆ€ν„°λŠ” ν΄λ‘œμ € 캑처 λͺ©λ‘μ„ μ‚¬μš©ν•΄ selfλ₯Ό μΊ‘μ²˜ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€:

someFunctionWithEscapingClosure { [self] in x = 100 }

πŸ“Œ μžλ™ ν΄λ‘œμ € (Autoclosures)

μžλ™ ν΄λ‘œμ €λŠ” ν•¨μˆ˜μ— 인자둜 μ „λ‹¬λ˜λŠ” ν‘œν˜„μ‹μ„ μžλ™μœΌλ‘œ ν΄λ‘œμ €λ‘œ κ°μ‹ΈλŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€:

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// 일반 ν΄λ‘œμ €
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) })

// μžλ™ ν΄λ‘œμ €
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))  // ν΄λ‘œμ € ꡬ문 없이 ν‘œν˜„μ‹λ§Œ 전달

μžλ™ ν΄λ‘œμ €(@autoclosure): 인자둜 μ „λ‹¬λœ ν‘œν˜„μ‹μ„ μžλ™μœΌλ‘œ ν΄λ‘œμ €λ‘œ λž˜ν•‘ν•˜λŠ” 속성. μ½”λ“œλ₯Ό 더 κ°„κ²°ν•˜κ²Œ λ§Œλ“€μ§€λ§Œ, λ‚¨μš©ν•˜λ©΄ 가독성이 λ–¨μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

μžλ™ ν΄λ‘œμ €μ˜ κ°€μž₯ 큰 νŠΉμ§•μ€ μ½”λ“œ 싀행을 μ§€μ—°μ‹œν‚¨λ‹€λŠ” μ μž…λ‹ˆλ‹€. ν΄λ‘œμ €κ°€ μ‹€μ œλ‘œ 호좜될 λ•ŒκΉŒμ§€ λ‚΄λΆ€ μ½”λ“œλŠ” μ‹€ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

νƒˆμΆœμ΄ ν•„μš”ν•œ μžλ™ ν΄λ‘œμ €λŠ” @autoclosure와 @escaping 속성을 λͺ¨λ‘ μ‚¬μš©ν•©λ‹ˆλ‹€:

func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    // κ΅¬ν˜„...
}

μ΄μƒμœΌλ‘œ Swift의 ν΄λ‘œμ €μ— λŒ€ν•œ μ„€λͺ…을 λ§ˆμΉ˜κ² μŠ΅λ‹ˆλ‹€.