일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- AJAX
- JavaScript
- Xib
- FileOwner
- Xcode
- 개발기록
- 스프링
- spring
- 파이썬서버
- iOS앱배포
- Python
- 웹
- 계산기앱만들기
- 딩동말씀
- iOS배포
- customclass
- 스위프트
- 앱버전구하기
- 계산기앱
- ios
- subscript
- Swift
- MainScheduler
- jQuery
- 자바스크립트
- 앱배포
- iOS계산기
- FLASK
- DispatchGroup
- 맥
- Today
- Total
개발하는 뚝딱이
[swift] 클로저 본문
책 <스위프트 프로그래밍 3판 ;야곰 지음> 을 정리한 글입니다.
0. 클로저
- 클로저는 변수나 상수가 선언된 위치에서 참조(Reference)를 획득(Capture)하고 저장
- 이를 변수나 상수의 클로징이라고 하며 클로저는 여기서 착안된 이름
- 함수도 클로저의 형태 중 하나
- 클로저의 세 가지 형태
- 이름이 있으면서 어떤 값도 획득하지 않는 전역함수의 형태
- 이름이 있으면서 다른 함수 내부의 값을 획득할 수 있는 중첩된 함수의 형태
- 이름이 없고 주변 문맥에 따라 값을 획득할 수 있는 축약 문법으로 작성한 형태
1. 기본 클로저
- 클로저의 기본 형태
{ (매개변수들) -> 반환타입 in
실행 코드
}
// sorted(by:) 메서드에 클로저 전달
let name: [String] = ["james", "sam", "david", "su"]
let reversed: [String] = name.sorted(by: { (first: String, second: String) -> Bool in
return first > second
})
2. 후행 클로저
- 함수나 메서드의 마지막 전달인자로 위치하는 클로저는 함수느 메서드의 소괄호를 닫은 후 작성해도 됨
- 클로저가 조금 길어지거나 가독성이 떨어질 때 후행 클로저 기능을 사용하면 좋음
- 단, 후행 클로저는 맨 마지막 전달인자로 전달되는 클로저에만 해당되므로 클로저 여러 개를 전달할 때는 맨 마지막 클로저만 후행 클로저를 사용할 수 있음
let reversed: [String] = name.sorted() { (first: String, second: String) -> Bool in
return first > second
}
let reversed2: [String] = name.sorted { (first: String, second: String) -> Bool in
return first > second
}
3. 클로저 표현 간소화
3.1 문맥을 이용한 타입 유추
- 메서드의 전달인자로 전달하는 클로저는 메서드에서 요구하는 형태로 전달해야 함
- 매개변수의 타입이나 개수, 반환 타입 등이 같아야 전달인자로서 전달 가능
- 매개변수의 타입이나 반환 타입을 굳이 표현해주지 않고 생략 가능
let reversed: [String] = name.sorted { (first, second) in
return first > second
}
3.2 단축 인자 이름
- 첫 번째 전달인자부터 $0, $1, $2, $3, ... 순서로 $와 숫자의 조합으로 표현
- 매개변수와 반환 타입을 실행코드와 구분하기 위해 in 사용했으나 필요 없어짐
let reversed: [String] = name.sorted {
return $0 > $1
}
3.3 암시적 반환 표현
- return 키워드도 생략 가능
- 클로저가 반환 값을 갖고 클로저 내부의 실행문이 한 줄이면, 실행문을 반환값으로 사용 가능
let reversed: [String] = name.sorted { $0 > $1 }
3.4 연산자 함수
- '>'가 자체가 함수의 이름
// > 연산자 정의
public func ><T:Comparable>(lhs: T, rhs: T) -> Bool
// 연산자 함수를 클로저의 역할로 사용
let reversed: [String] = names.sorted(by: >)
4. 값 획득
- 클로저는 자신이 정의된 위치의 주변 문맥을 통해 상수나 변수를 획득 (Capture)
- 값 획득을 통해 클로저는 주변에 정의한 상수나 변수가 더 이상 존재하지 않더라도 해당 상수나 변수의 값을 자신 내부에서 참조하거나 수정할 수 있음
- 따라서, 비동기 작업에 많이 사용됨
- 클로저를 통해 비동기 콜백을 작성하는 경우, 현재 상태를 미리 획득해두지 않으면 실제로 클로저의 기능을 실행하는 순간에는 주변의 상수나 변수가 이미 메모리에서 존재하지 않는 경우가 발생
- 즉, 자신을 포함하는 함수의 지역변수나 지역상수를 획득할 수 있음
func makeIncrementer(forIncrement amount: Int) -> (() -> Int) {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTwo: (() -> Int) = makeIncrementer(forIncrement: 2)
let incrementByTwo2: (() -> Int) = makeIncrementer(forIncrement: 2)
let incrementByTen: (() -> Int) = makeIncrementer(forIncrement: 10)
let first: Int = incrementByTwo() // 2
let second: Int = incrementByTwo() // 4
let third: Int = incrementByTwo() // 6
let first2: Int = incrementByTwo2() // 2
let second2: Int = incrementByTwo2()// 4
let third2: Int = incrementByTwo2() // 6
let ten: Int = incrementByTen() // 10
let twnety: Int = incrementByTen() // 20
let rhid: Int = incrementByTen() // 30
- makeIncrementer 함수의 실행이 끝나도 runningTotal과 amount는 사라지지 않음
- 각각 자신만의 runningTotal의 참조를 미리 획득하여, 다른 함수의 영향을 전혀 받지 않음
- 그러나 클래스 인스턴스 프로퍼티로서의 클로저를 할당하면, 클러스와 인스턴스 사이에 강한참조 순환문제가 발생 (이후에 다룸)
5. 클로저는 참조 타입
- 함수나 클로저를 상수나 변수에 할당하는 것은 함수나 클로저의 참조를 설정하는 것
- incrementByTwo라는 상수에 클로저를 할당하는 것은 클로저의 내용물, 즉 값을 할당하는 것이 아니라 해당 클로저의 참조를 할당하는 것
- 그래서 클로저의 참조를 다른 상수에게 할당해준다면 이 두 상수가 모두 같은 클로저를 가리킨다는 뜻
6. 탈출 클로저
- 함수의 파라메터로 전달한 클로저가 함수 종료 후에 호출될 때 클로저가 함수를 탈출한다고 표현
- 클로저를 매개변수로 갖는 함수를 선언할 때 매개변수 이름의 콜론(:) 뒤에 @escaping 키워드를 사용하여 클로저가 탈출하는 것을 허용한다고 명시해줄 수 있음
- @escaping 키워드를 명시하지 않으면 매개변수로 사용되는 클로저는 기본으로 비탈출 클로저
- 함수로 전달된 클로저가 함수의 동작이 끝난 후 사용할 필요가 없을 때 비탈출 클로저를 사용
// 탈출 클로저를 파라메터로 갖는 함수
var completionHandlers: [() -> Void] = []
func someFunctionwithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
- 탈출 클로저의 경우, 클로저 내부에서 해당 타입의 프로퍼티나 메서드, 서브스크립트 등에 접근하려면 self 키워드를 사용해야 함
typealias VoidVoidClosure = () -> Void
func functionWithNoescapeClosure(closure: VoidVoidClosure) {
closure()
}
func functionWithEscapingClosure(completionHandler: @escaping VoidVoidClosure) -> VoidVoidClosure {
return completionHandler
}
class SomeClass {
var x = 10
func runNoescapeClosure() {
// 비탈출 클로저에서 self 키워드 사용은 선택사항
functionWithNoescapeClosure { x = 200 }
}
func runEscapingClosure() -> VoidVoidClosure {
// 탈출 클로저에서는 명시적으로 self를 사용해야 함
return functionWithEscapingClosure { self.x = 100 }
}
}
let instance: SomeClass = SomeClass()
instance.runNoescapeClosure()
print(instance.x) // 200
let returnedClosure: VoidVoidClosure = instance.runEscapingClosure()
returnedClosure()
print(instance.x) // 100
6.1 withoutAcutallyEscaping
- 실제로는 탈출하지 않는데 다른 함수에서 탈출 클로저인 척 해야 하는 경우
func hasElements(in array: [Int], match predicate: (Int) -> Bool) -> Bool {
return (array.lazy.filter {predicate($0)}.isEmpty == false) // 오류 발생
}
- hashElements(in:match:) 함수는 match라는 매개변수로 검사를 실행할 클로저를 받아들임
- hashElements(in:match:) 함수는 @escaping 키워드가 없으므로 비탈출 클로저를 전달받음
- 내부에서 배열의 lazy 컬렉션에 있는 filter 메서드의 매개변수로 비탈출 클로저를 전달, lazy 컬렉션은 비동기 작업을 사용하기 때문에 filter메서드가 요구하는 클로저는 탈출 클로저
- withoutActuallyEscaping(_:do:)함수의 첫번째 전달인자로 탈출 클로저인 척해야 하는 클로저가 전달됨
- do 전달인자는 이 비탈출 클로저를 또 매개변수로 전달받아 실제로 작업을 실행할 탈출 클로저를 전달
let numbers: [Int] = [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, do: { escapablePredicate in
return (array.lazy.filter {escapablePredicate($0)}.isEmpty == false )
})
}
let hasEvenNumber = hasElements(in: numbers, match: evenNumberPredicate)
let hasOddNumber = hasElements(in: numbers, match: oddNumberPredicate)
print(hasEvenNumber) // true
print(hasOddNumber) // false
7. 자동 클로저
- 함수의 전달인자로 전달하는 표현을 자동으로 변환해주는 클로저를 자동 클로저라고 함
- 자동 클로저는 전달인자를 갖지 않음
- 호출되었을 때 자신이 감싸고 있는 코드의 결괏값을 반환
- 함수로 전달하는 클로저를 (소괄호와 중괄호를 겹쳐서 써야 하는) 어려운 클로저 문법을 사용하지 않고도 클로저로 사용할 수 있도록 문법적 편의를 제공
- 자동클로저는 클로저가 호출되기 전까지 클로저 내부의 코드가 동작하지 않음, 따라서 연산을 지연시킬 수 있음
// 클로저를 이용한 연산 지연
var customersInLine: [String] = ["Eric", "Jin", "Taylor", "Sophie"]
print(customersInLine.count) // 4
// 클로저를 만들어두면 클로저 내부의 코드를 미리 실행하지 않고 가지고만 있음
let customerProvider: () -> String = {
return customersInLine.removeFirst()
}
print(customersInLine.count) // 4
// 실제로 실행
print("Now serving \(customerProvider())!") // "Now serving Eric!"
print(customersInLine.count) // 3
// 함수의 전달인자로 전달하는 클로저
var customersInline: [String] = ["Eric", "Jin", "Taylor", "Sophie"]
func serveCustomer(_ customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer({ customersInline.removeFirst() }) // "Now serving Eric"
// 자동 클로저의 사용
var customersInLine: [String] = ["Eric", "Jin", "Taylor", "Sophie"]
func serveCustomer(_ customerProvider: @autoclosure () -> String){
print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeFirst()) // "Now serving Eric!"
// 자동 클로저의 탈출
var customersInLine: [String] = ["Eric", "Jin", "Taylor", "Sophie"]
func returnProvider(_ customerProvider: @autoclosure @escaping () -> String) -> (() -> String) {
return customerProvider
}
let customerProvider: () -> String = returnProvider(customersInLine.removeFirst())
print("Now serving \(customerProvider())!") // "Now serving Eric!"
'swift' 카테고리의 다른 글
[swift] 맵, 필터, 리듀스 (0) | 2020.05.13 |
---|---|
[swift] 옵셔널 체이닝과 빠른 종료 (0) | 2020.05.08 |
[swift] 접근제어 (0) | 2020.05.02 |
[swift] 인스턴스 생성 및 소멸 (0) | 2020.05.01 |
[swift] 옵셔널 (0) | 2020.04.28 |