Closures 간단 정리
2023. 2. 6. 03:16ㆍSwift
반응형
- 해당 글은 공식문서의 내용을 간단하게 정리한 것이며 번역 오류가 있을 수 있습니다
참고문서
Closures
- 글로벌 함수는 이름이 있으면서 어떠한 값도 캡쳐하지않는 클로저이다
- 중첩함수는 이름이 있으면서 가장 가까운 함수의 값을 캡쳐할 수 있는 클로저이다
- 클로저 구문은 구문에 있는 값을 캡쳐 가능하며, 가벼운 구문(lightweight syntax)으로 작성 된 이름없는 클로저이다
클로저 표현법
{ (parameters) -> return type in
statement
}
- 위는 기본적인 클로저의 표현법이다
- 위 파라미터는 in-out파라미터이지만, 기본값을 가지지는 않는다
let numbers = [1, 2, 4, 3, 7, 5, 2]
let sortedNumbers = numbers.sorted(by: { (s1: Int, s2: Int) -> Bool in
return s1 > s2
})
- 위의 sorted는 애플 기본 라이브러리에서 제공하는 정렬메소드로 클로저를 전달 받아 해당 클로저의 내용에 따라 정렬해주는 메소드이다
- 이를 통해 s1이 s2보다 클 경우 true를 리턴하면서 정렬을 해줄텐데 이럴 경우
numbers
안의 요소들이 바뀌기 때문에 해당 s1, s2파라미터들이 위에서 말한 in-out파라미터인것을 알 수 있다
- Swift의 컴파일러는 타입을 추정하기 때문에 Int타입을 써주지 않고 더욱 간단하게 클로저를 작성할 수 있다
numbers.sorted(by: { s1, s2 in return s1 > s2 })
- Single-Express Closure의 암시적 반환
- 리턴 타입이 명확한 시점에서는 return타입을 생략해도 어떠한 모호성이 없다
numbers.sorted(by: { s1, s2 in s1 > s2 })
- 클로저의 파라미터들은 축약하여 사용할 수 있는 이름을 제공한다
$0
,$1
등 을 사용할 경우 클로저 첫부분 in과 앞의 파라미터 정의 부분을 생략할 수 있다.number.sorted(by: { s1 > s2 })
- 놀랍게도 여기서는 연산자를 사용하여 리턴값을 반환하므로 단순하게 연산자만을 이용하여 클로저 선언도 가능하다
numbers.sorted(by: >)
Trailing Closures(후행 클로저)
- 위 sorted의 인자에서 마지막 인자를 받을 경우에도 다양한 형태의 포현 방식을 쓸 수 있다
numbers.sorted() { $0 > $1 }
numbers.sorted { $0 > $1 }
- 이와 같이 후행 클로저를 쓸 경우
()
를 생략할 수 있다
Capturing Values
- 맨 위에 서술했듯이 클로저는 값을 캡처할 수 있다
- 값을 캡쳐한다는 의미는 특정 문맥에서 상수나 변수 값이 사라진다 하더라도 클로저 안에서 사용할 수 있는 것을 뜻한다
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer }
- 위 함수는
makeIncrementer
함수 내에서incremented
이라는 중첩함수가 있는 형태이다 makeIncrementer
라는 함수의 반환값은() -> Int
형태를 가진 클로저를 반환하며 코드내에서 확인해보면incrementer
을 반환하는 모습을 볼 수 있다
increment
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
- 중첩함수인
increment
는runningTotal
과amount
를 더하는 모습을 보여주는데 함수 내에는 이 두가지 변수가 선언도 되어있지않고, 파라미터로 받는 형태도 아니지만 해당 함수는 동작을 한다 - 이는
makeIncrementer
내에서runningTotal
변수와amount
인자가 캡쳐되었기 때문이다
let incrementByOne = makeIncrementer(forIncrement: 1)
print(incrementByOne()) // 1
print(incrementByOne()) // 2
print(incrementByOne()) // 3
print(incrementByOne()) // 4
- 위 함수를 실행 할 시
runningTotal
과amount
가 캡쳐되어 이전의 내용이 저장되어 출력되는 모습을 확인할 수 있다
let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen()) // 10
print(incrementByOne()) // 5
print(incrementByTen()) // 20
- 새로운 클로저를 만들 경우에는 서로 다른
runningTotal
과amount
를 가지고 있으므로 서로 다른 값이 나오게 된다 - 이는 클로저가 클래스와 같은 참조타입이므로 다음과 같이 동작할 수 있다는 것을 나타낸다
Closures are reference types
- 위에도 서술했듯이 클로저는 참조타입이다
- 그렇기 때문에
incrementByOne
과incrementByTen
은 서로 다른 클로저이며, 힙에 할당된 각각의 클로저 주소를 가지고 있다 - 또한 힙에 할당된 두 클로저는 값 캡쳐와 같이 힙에 할당되어 있기 때문에 지속적으로 업데이트 되며, 사용할 수 있다.
Escaping Closures
- 클로저를 함수의 파라미터로 넣을 수 있다는 것은 위의
sorted
혹은 자주 사용하는map
,filter
와 같은 메소드를 통해서도 알 수 있다. - 다만 이런 클로저가 함수가 종료된 이후에 실행된다면 해당 클로저는 함수 파라미터 앞에
@escaping
어노테이션을 붙여줘야한다
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
completionHandlers[0]()
print(instance.x)
- 위 함수를 보면 escaping을 사용하는 함수의 경우 함수가 끝난 이후 클로저가 실행되며, escaping을 사용하지 않는 함수는 클로저를 함수 내부에서 사용이후 종료가 된다
SomeClass
에서의 프로퍼티x
를 이스케이핑 클로저에서 사용하기 위해서는self
키워드를 붙여줘야한다self
키워드를 붙이지 않을 경우 Reference to property ‘x’ in closure requires explicit use of ‘self’ to make capture semantics explicit와 같은 오류가 난다- 이스케이핑 클로저에서 캡처를 사용하기 위해서는 명시적으로 self를 붙여야 한다는 에러이다
Autoclosures(자동 클로저)
- 자동 클로저는 파라미터가 없으며 특정 구문을 통해 다른 함수에 전달인자로 사용 가능한 클로저이다
var customerInLine = ["KDB", "Rodri", "Ake", "Sliva", "Jack"]
let customerProvider = { customerInLine.remove(at: 0) }
print(customerInLine.count) // 5
print(customerInLine[0]) // KDB
- 위 구문은 클로저를 지났기 때문에
customerProvider
에 “KDB”가 들어있고,customerProvider
의 경우 개수가 4가 되어야 할 것 같지만 클로저는 실행되지 않는다 - 이렇게 자동 클로저는 실행하기 전까지 실행되지 않는다
func printFirstCustomer(customers: () -> String) {
print(customers())
}
printFirstCustomer(customers: customerProvider) // KDB
- 위와 같이 클로저를 전달 할 수도 있지만
@autoclosure
을 사용하여 밑 코드와 같이 사용도 가능하다
func printFirstCustomer(customers: @autoclosure () -> String) {
print(customers())
}
printFirstCustomer(customers: customerInLine.remove(at: 0)) // KDB
반응형
'Swift' 카테고리의 다른 글
Swift 컴파일, 컴파일러 (0) | 2023.02.14 |
---|---|
ARC-간단정리 (0) | 2023.02.07 |
정규표현식(Swift) (0) | 2023.01.29 |
KVC(Key-value coding), KVO(Key-value observing) (0) | 2023.01.22 |
Opaque Types(불투명 타입) 간단하게 훑어보기 (0) | 2023.01.21 |