Programming/Swift

[Swift] 문법 다시보기 - 클로저(closure)

devssun 2021. 10. 14. 00:00
728x90
반응형

클로저

  • 함수형 프로그래밍 패러다임을 접할 때 꼭 알아야할 개념

  • 클로저, 제네릭, 프로토콜, 모나드 등이 결합하여 강력한 스위프트

  • C 언어나 Objective-C의 블록 또는 람다와 유사

  • 클로저는 일정 기능을 하는 코드를 하나의 블록으로 모아놓은 것을 말함

    → 함수는 클로저의 한 형태이다

{ (**매개변수들**) -> **반환 타입** in
    **실행 코드**
}
// 클로저 표현 간소화

let reversed1: [String] = names.sorted { (first: String, second: String) -> Bool in
    return first > second
}

let reversed2: [String] = names.sorted { (first, second) -> Bool in
    return first > second
}

let reversed3: [String] = names.sorted {
    return $0 > $1
}

// 암시적 반환 표현
let reversed4: [String] = names.sorted { $0 > $1 }

// 연산자 함수
let reversed5: [String] = names.sorted(by: >)
  • 클로저는 참조 타입이다

탈출 클로저

  • 함수의 전달인자로 전달한 클로저가 함수 종료 후에 호출될 때 클로저가 함수를 탈출(Escape)한다고 표현
  • 예시 : 비동기 작업
  • @escaping 키워드가 없는 경우 매개변수로 사용되는 클로저는 기본으로 비탈출 클로저
  • 탈출 클로저 내부에서 해당 타입의 프로퍼티나 메서드, 서브스크립트 등에 접근하려면 self 키워드를 명시적으로 사용해야함 (비탈출 클로저 내부에서는 self 키워드는 선택사항)

withoutActuallyEscaping

  • 비탈출 클로저로 전달한 클로저가 탈출 클로저인 척 해야하는 경우 (실제로는 탈출하지 않는데 다른 함수에서 탈출 클로저를 요구하는 상황)
// withoutActuallyEscaping
let numbers = [2, 4, 6, 8]

let evenNumberPredicate = { (number: Int) -> Bool in
    return number % 2 == 0
}

let oddNumberPredicate = { (number: Int) -> Bool in
    return number % 2 == 1
}

func hasElements(in array: [Int], match predicate: (Int) -> Bool) -> Bool {
    return withoutActuallyEscaping(predicate) { escapablePredicate in
        return (array.lazy.filter { escapablePredicate($0)}.isEmpty == false)
    }
}

let hasEvenNumber = hasElements(in: numbers, match: evenNumberPredicate) // true
let hasOddNumber = hasElements(in: numbers, match: oddNumberPredicate)   // false

자동 클로저 (@autoclosure)

  • 함수의 전달인자로 전달하는 표현을 자동으로 변환해주는 클로저 (일반 표현의 코드를 클로저 표현의 코드로 변환)
  • 전달인자를 갖지 않음, 호출되었을 때 자신이 감싸고 있는 코드의 결괏값 반환
  • 클로저가 호출되기 전까지 클로저 내부 코드가 동작하지 않음, 연산 지연 가능
  • 비탈출 클로저임, @esacping 키워드 사용 가능
// 클로저를 이용한 연산 지연
var customersInLine: [String] = ["aaa", "bbb", "ccc", "ddd"]
print(customersInLine.count) // 4

// 클로저를 만들어두면 클로저 내부의 코드를 미리 실행(연산)하지 않고 가지고만 있다
let customerProvider: () -> String = {
    return customersInLine.removeFirst()
}

print(customersInLine.count) // 4

print("Now serving \(customerProvider())") // "Now serving aaa"
print(customersInLine.count) // 3

// ---

// 자동 클로저의 사용
func serveCustomer(_ customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())")
}

serveCustomer(customersInLine.removeFirst()) // "Now serving aaa"
// 예제 assert
public func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)

// 1
assert(false, "Error Occurred!")
assert(0 < 1, "Error Occurred!")
assert(false, (statusCode == .fileNotFound) ? "File Not Found!" : "Something going wrong!")

// 2 @autoclosure 제거
assert({ false }, { "Error Occurred!" })
assert({ 0 < 1 }, { "Error Occurred!" })
assert({ false }, {(statusCode == .fileNotFound) ? "File Not Found!" : "Something going wrong!"})
반응형