Swift 5 : 클로저과 클로저 표현식 (Closure and Closure Expression)

버들비·2020년 7월 15일
0

Swift

목록 보기
5/10

revised on 200815


보통은 다음과 같은 것을 클로저(Closure)라고 한다

클로저는 함수나 클로저 표현식과 같은 독립적인 코드 블록이, 코드 블록 주변에 있는 변수들과 결합된 것을 말한다.
다르게 표현하면, 클로저는 함수가 선언될 당시의 환경을 기억했다가 나중에 호출되었을때 원래의 환경에 따라 수행되는 함수이다.
예제 코드는 다음과 같다.

func functionA() -> () -> Int {
	var counter = 0
    
    func functionB() -> Int {
    	return counter + 10
    }
    return functionB
}

functionA는 ()를 받아서 "()를 받아 Int를 반환하는 함수"를 반환한다.
functionB 내부에서 counter 라는 변수가 쓰였는데, counter 변수는 functionB 라는 독립적인 코드 블록 밖에서 선언된 변수이다.
functionB는 내부 영역밖에 선언된 counter 에 의존하는 클로저이고, functionA 는 클로저를 반환하고 있다.
Swift 는 클로저를 지원하는 프로그래밍 언어로써, 위의 예제코드는 이상없이 작동한다.

Closure in Swift

Capturing Value

다음은 클로저를 반환하는 함수 예제이다.
incrementer 라는 함수를 반환하는 함수 makeIncrementer.

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

let incrementBySeven = makeIncrementer(forIncrement: 7)

incrementBySeven() // 7
incrementBySeven() // 14

var itIsIncrementByTenToo = incrementByTen
itIsAlsoIncrementByTen() // 30

incrementByTen()을 실행할때마다 숫자가 10씩 증가한다. incrementByTen을 다른 변수(itIsAlsoIncrementByTen)에 할당한 뒤 itIsAlsoIncrementByTen()를 실행하면, 10에서 시작하는게 아니라 30이 나온다.
클로저는 레퍼런스 타입임을 알 수 있다.


Escaping Closure


스위프트에서 클로저는 다음과 같은 세가지 형태가 있다.

  • 이름을 갖고 있고, 어떤 값도 캡쳐하지 않는 Global Function(전역 함수).
  • 이름을 갖고 있고, 자신을 감싸고 있는 함수에서 값을 캡쳐해 가질 수 있는 Nested Function.
  • 이름이 없고, 자신을 둘러싼 환경의 값을 캡쳐해 가질 수 있는 Closure Expression.

간혹 외부 값을 캡쳐하지 않은 클로저 표현식(ex: 중괄호 {} 안에 적힌 inline function 들)을 그냥 클로저라고 표현하는 경우가 많은데, 보는 사람 입장에서 혼동이 온다..


클로저 표현식(Closure expression)

독립적인 코드 블록이다.
예를 들어, 다음은 클로저 표현식을 선언하고 그것을 sayHello 라는 이름의 상수에 할당하는 코드이다.

let sayHello = { print("Hello!") }

클로저 표현식은 매개변수를 받아 결과값을 반환하도록 구성할 수도 있다. 두개의 Int 를 받아 하나의 Int 를 반환하는 클로저 표현식 코드는 다음과 같다. 일반함수에서 반환타입 뒤에 {} 로 시작하는 것과 다르게, in 키워드로 시작한다는 차이점이 있다.

let multiply = {(_ input1: Int, _ input2: Int) -> Int in
	return input1 * input2
}

함수와 큰 차이가 없다. 함수와 클로저 표현식의 차이점은, 함수는 이름이 있고 클로저 표현식은 이름이 없다.

함수는 이름이 있는 클로저 표현식일 뿐이다.

스위프트의 클로저 표현식에는 다음과 같은 특징이 있다.

  • 파라미터와 리턴값 타입 추론
  • 한줄짜리 표현에서 return 문구 생략 가능
  • Argument 이름에 대해 축약어 사용가능
  • Trailing Closures: 함수의 마지막 인자가 클로저 표현식인 경우, 소괄호 () 뒤에 중괄호 {} 로 빼내서 쓸 수 있다.

파라미터와 리턴값 타입 추론

스위프트는 아주 깐깐하게 타입체크를 하는 언어라서, 정석대로라면 다음과 같이 인풋과 아웃풋의 타입을 적어줘야 한다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

클로저 표현식의 경우,인풋 파라미터와 아웃풋 리턴에 대해 타입을 명시하지 않아도 스위프트 컴파일러가 어느정도 알아서 추론해준다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: { (s1, s2) in
    return s1 > s2
})
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

한줄짜리 표현에서 return 문구 생략 가능

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: { (s1, s2) in
   s1 > s2
})
// ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

Argument 이름에 대해 축약어 사용가능

스위프트는 클로저 표현식에 대한 축약어를 제공한다. 축약어는 첫번째 인자가 $0, 두번째가 $1, 세번째가 $2... 이다. 축약어를 사용할 경우 클로저 표현식 중괄호 {} 안에서 파라미터도 생략해 적을 수 있다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted(by: { $0 > $1 } )

코드 길이가 대폭 줄어들었지만, 초보자 입장에서 축약어가 사용된 코드는 알아보기가 힘들다.

Trailing Closures: 함수의 마지막 인자가 클로저 표현식인 경우, 소괄호 () 뒤에 중괄호 {} 로 빼내서 쓸 수 있다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted() { $0 > $1 }

sorted 라는 함수의 마지막 인자로 클로저 표현식이 들어갔는데, 이 경우 소괄호 바깥으로 빼서 위와 같이 적을 수 있다. 소괄호는 생략해도 된다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var reversedNames = names.sorted { $0 > $1 }

SwiftUI 에서 사용되는 HStack, VStack 등이 Trailing Closure 방식으로 많이 쓰인다.

HStack {
	Text("Hello,")
    Text("World!")
}

클로저 표현식은 비동기 메서드 호출에 대한 완료 핸들러를 선언할 때 종종 사용된다. iOS 어플리케이션을 개발할 때 어떤 작업을 백그라운드에서 작업하게 해서 어플리케이션이 다른 작업을 계속 할 수 있도록 해야 하는 경우가 종종 생긴다. 이런 경우에는 보통 시스템이 어플리케이션에게 작업이 완료된 것을 알리고 메서드를 호출할 때 선언했던 완료 핸들러를 호출하여 결과를 반환한다.
완료 핸들러에 대한 코드는 주로 클로저 표현식을 이용해 작성한다.

eventstore.reqyestAccess(to: .reminder, completion: {(granted: Bool, error: Error?) -> Void in
	if !granted {
    	print(error!.localizedDescription)
    }
})

reference

0개의 댓글