티스토리 뷰
애플 문서를 보고 정리해본 내용입니다.
정의 및 특징
- 클로저는 코드 안에서 전달되어 사용할 수 있는 독립적인 기능을 가진 일급 객체입니다.
- 일급 객체는 변수/상수로 선언하거나 전달이 가능하며, 반환값으로도 사용될 수 있는 객체입니다.
- 클로저는 참조 타입입니다.
표현 방식
아래는 일반적인 표현 방식의 형태입니다.
{ (parameters) -> returnType in
// do something..
}
아래는 숫자의 기호(양수/음수)를 변경 하는 클로저를 선언하여 사용하는 예제입니다.
let operation = { (operand: Double) -> Double in
return -operand
}
operation(4.0) // -4.0
위와 같이 선언한 클로저를 축약해서 표현할 수도 있습니다.
// 리턴 타입 생략
// 타입 추론을 통해 파라미터의 타입과 리턴되는 값의 타입을 제거할 수 있습니다.
operation = { (operand) in return -operand }
// return 키워드 생략
// 어떤 값을 리턴할지 알고 있기 때문에 return 명령어를 제거할 수 있습니다.
operation = { (operand) in -operand }
// 파라미터 이름 생략
// 파라미터 이름을 따로 명시하지 않을 경우 순서대로 $0, $1, $2 등으로 축약해서 사용할 수 있습니다.
// 때문에 operand는 $0으로 변환해주면서 in이 필요 없어지게 됩니다.
operation = { -$0 }
순차적으로 클로저를 축약해 보았습니다.
하지만 문법을 간결하게 한다고 해서 무조건 좋은 것은 아니기 때문에 상황에 따라서 읽기 편하게 사용하면 될 것 같습니다.
Trailing Closure(후위 클로저)
함수의 마지막 파라미터로 클로저를 전달할 경우 표현할 수 있는 방식입니다.
클로저를 사용하는 map 함수를 예제로 알아보겠습니다.
let numbers = [1, 2, 3, 4, 5]
// map 함수의 파라미터로 클로저가 전달됩니다.
let negativeNumbers = numbers.map({ -$0 }) // [-1, -2, -3, -4, -5]
// 함수의 마지막 파라미터로 클로저가 전달되면 괄호 밖으로 빼서 표현할 수 있습니다.
let floatNumbers = numbers.map() { $0 * 1.0 } // [1.0, 2.0, 3.0, 4.0, 5.0]
// 클로저가 유일한 파라미터라면 괄호를 제거해도 됩니다.
let stringNumbers = numbers.map { String($0) } // ["1", "2", "3", "4", "5"]
Capturing
클로저는 그것이 정의된 지역 외부에 있는 상수 및 변수를 캡처할 수 있습니다. 캡처한 상수 및 변수는 나중에 없어지더라도 클로저 내에서 값을 참조하고 있기 때문에 접근하여 수정할 수 있습니다.
Swift에서 값을 캡처 하는 가장 단순한 형태는 중첩 함수(nested function) 입니다.
중첩 함수는 외부 함수의 인자와 외부 함수에서 정의한 상수 및 변수를 캡처할 수 있습니다.
아래 예제를 보겠습니다.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
makeIncrementer 함수는 내부에 incrementer 함수를 반환하는 형태의 중첩 함수입니다.
incrementer 함수만 따로 보면,
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
어디에도 runningTotal, amount가 선언되지 않았습니다.
runningTotal은 함수 외부에서 선언된 변수이고, amount는 외부 함수에서 인자로 받은 값입니다.
incrementer 함수에서 해당 값을 캡처해서 사용하고 있다는 것입니다.
아래는 함수의 호출 예제입니다.
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // returns a value of 10
incrementByTen() // returns a value of 20
incrementByTen() // returns a value of 30
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven() // returns a value of 7
클래스 인스턴스의 속성(property)에 클로저를 할당하고 클로저가 인스턴스 및 인스턴스의 멤버를 참조하면, 캡처링이 발생하면서 클로저와 인스턴스 사이에 강한 순환 참조가 생성됩니다.
이런 경우, 인스턴스가 없어져도 강한 참조가 해제되지 않기 때문에 메모리 사이클이 발생합니다.
이 문제를 해결하기 위해 캡처 리스트(capture list)를 사용해야 합니다.
escaping closure
클로저를 함수의 파라미터로 전달될 때, 전달된 클로저가 함수가 끝나고 함수 외부에서 실행될 수 있습니다.
예를 들면 비동기 작업을 하는 함수에서 완료 핸들러로 클로저 파라미터를 사용합니다. 이때 클로저는 작업이 완료되고 함수 외부에서 호출하게 되는데, 이 경우에는 클로저 앞에 @escaping 키워드를 명시해야 컴파일 오류가 발생하지 않습니다.
일반적으로 클로저는 내부에서 사용되는 변수를 암시적으로 캡처하게 됩니다. 하지만 escaping 클로저의 경우 사용되는 변수를 명시적으로 표현해야 합니다.
만약 self를 캡처하려면 self를 사용할 때 직접 명시해주거나, 캡처 리스트(capture list)에 포함하여 캡처한다는 의도를 명확하게 보여줘야 합니다.
아래는 escaping closure에서 self를 명시적으로 표현하는 예제입니다.
var completionHandlers = [() -> Void]()
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
// self를 명시적으로 표현해야 에러가 발생하지 않음
someFunctionWithEscapingClosure { self.x = 100 }
// self를 암시적으로 사용할 수 있어 self를 명시하지 않아도 됨
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
아래는 캡처 리스트에 self를 추가하여 사용하는 예제입니다.
class SomeOtherClass {
var x = 10
func doSomething() {
// capture list에 self 추가하여 캡처
someFunctionWithEscapingClosure { [self] in x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
AutoClosure
AutoClosure는 파라미터 값이 없으며 특정 표현을 감싸서 다른 함수의 파라미터로 사용할 수 있는 클로저입니다.
AutoClosure를 사용하면 클로저가 호출될 때까지 코드가 실행되지 실행되지 않습니다. 이런 코드 지연의 특성을 이용하면 실행 시점을 제어할 수 있기 때문에 부작용을 줄이고, 복잡한 연산이 필요할 때 유용합니다.
아래는 코드가 지연되어 사용되는 예제입니다.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count) // 아직 클로저가 호출되지 않았기 때문에 값이 변하지 않았음
// Prints "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count) // 클로저 호출 후 값이 변함
// Prints "4"
AutoClosure를 함수의 파라미터로 전달하는 예제입니다.
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"
serve 함수는 String을 반환하는 클로저를 파라미터로 받습니다.
보통 클로저를 전달할 때, {}(중괄호)를 붙여야 하지만 @autoclosure 키워드를 명시해주면 {}(중괄호) 없이 클로저를 전달할 수 있습니다.
아래는 @autoclosure 키워드를 사용한 예제입니다.
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"
Reference
https://docs.swift.org/swift-book/LanguageGuide/Closures.html#
https://jusung.gitbook.io/the-swift-language-guide/language-guide/07-closures
'Swift' 카테고리의 다른 글
[Swift] Coordinator Pattern (0) | 2021.01.31 |
---|---|
[Swift] xib 파일로 View 관리하기 (0) | 2020.09.28 |
[swift] xib를 이용한 alertView 만들기 (0) | 2020.07.19 |
[swift] UIButton에 클로저를 추가해 보았습니다. (0) | 2020.07.14 |
[swift] AssociatedObject (0) | 2020.07.11 |
- Total
- Today
- Yesterday
- AVKit
- TDD
- Cleancode
- UIControl
- IOS
- permission error
- BaseViewController
- UIButton
- Design Pattern
- http live streaming
- Coordinator
- AVFoundation
- Realm
- HLS
- AssociatedObject
- NIB
- pagingView
- carousel
- Video
- RECORDING
- xib
- m3u8
- customAlertView
- UIBarButtonItem
- database
- CollectionView
- testing
- Closure
- Swift
- ssh
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |