본 내용은 '스위프트 프로그래밍' 책을 학습한 후 이를 바탕으로 작성한 글입니다.
클로저는 일정 기능을 하는 코드를 하나의 블록으로 모아놓은 것을 말한다.
{ (매개변수들) -> 반환 타입 in
실행 코드
}
let names: [String] = ["zooneon", "mike", "minsoo", "bear"]
let reversed: [String] = names.sorted(by: { (first: String, second: String) -> Bool in
return first > second
})
print(reversed) //["zooneon", "minsoo", "mike", "bear"]
let names: [String] = ["zooneon", "mike", "minsoo", "bear"]
//후행 클로저 사용
let reversed: [String] = names.sorted() { (first: String, second: String) -> Bool in
return first > second
}
//소괄호까지 생략 가능
let reserved: [String] = names.sorted { (first: String, second: String) -> Bool in
return first > second
}
func doSomething(do: (String) -> Void,
onSuccess: (Any) -> Void,
onFailure: (Error) -> Void) {
// do something...
}
//다중 후행 클로저 사용
doSomething { (something: String) in
// do closure
} onSuccess: { (result: Any) in
// success closure
} onFailure: { (error: Error) in
// failure closure
}
let names: [String] = ["zooneon", "mike", "minsoo", "bear"]
//클로저의 타입 유추
let reversed: [String] = names.sorted { (first, second) in
return first > second
}
$0
, $1
, $2
... 순서로 $
와 숫자의 조합으로 표현한다.let names: [String] = ["zooneon", "mike", "minsoo", "bear"]
//단축 인자 이름 사용
let reversed: [String] = names.sorted {
return $0 > $1
}
return
키워드도 생략할 수 있다.let names: [String] = ["zooneon", "mike", "minsoo", "bear"]
//암시적 반환 표현
let reversed: [String] = names.sorted { $0 > $1 }
클로저는 매개변수의 타입과 반환 타입이 연산자를 구현한 함수의 모양과 동일하다면 연산자만 표기하더라도 알아서 연산하고 반환한다.
→ 연산자가 일종의 함수이기 때문
let names: [String] = ["zooneon", "mike", "minsoo", "bear"]
//연산자 함수를 클로저의 역할로 사용
let reversed: [String] = names.sorted(by: > )
//중첩 함수에서의 값 획득 예시
func makeIncrementer(forIncrement amount: Int) -> (() -> Int) {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
→ incrementer()
함수는 runningTotal
과 amount
변수의 참조를 획득할 수 있다.
참조를 획득하면 runningTotal
과 amount
는 makeIncrementer
함수의 실행이 끝나도 사라지지 않는다.
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 twenty: Int = incrementByTen() //20
let thirty: Int = incrementByTen() //30
→ 각각의 incrementer
함수는 언제 호출이 되더라도 자신만의 runningTotal
변수를 갖고 카운트하게 된다.
자신만의 runningTotal
변수의 참조를 미리 획득했기 때문에 다른 함수의 영향도 받지 않는다.
let incrementByTwo: (() -> Int) = makeIncrementer(forIncrement: 2)
let sameWithIncrementByTwo: (() -> Int) = incrementByTwo
let first: Int = incrementByTwo() //2
let second: Int = sameWithIncrementByTwo() //4
→ 두 상수는 같은 클로저를 참조하기 때문에 동일한 클로저가 동작한다.
@escaping
키워드를 사용하여 클로저가 탈출하는 것을 허용한다고 명시해줄 수 있다.@escaping
키워드를 따로 명시하지 않는다면 매개변수로 사용되는 클로저는 기본으로 비탈출 클로저이다.typealias VoidVoidClosure = () -> Void
let AClosure: VoidVoidClosure = {
print("Closure A")
}
let BClosure: VoidVoidClosure = {
print("Closure B")
}
func returnOneClosure(A: @escaping VoidVoidClosure, B: @escaping VoidVoidClosure, shouldReturnAClosure: Bool) -> VoidVoidClosure {
return shouldReturnAClosure ? A : B
}
let returnedClosure: VoidVoidClosure = returnOneClosure(A: AClosure, B: BClosure, shouldReturnAClosure: true)
returnedClosure() //Closure A
var closures: [VoidVoidClosure] = []
func appendClosure(closure: @escaping VoidVoidClosure) {
closures.append(closure)
}
→ 탈출할 수 있는 조건이 명확한 경우 @escaping
키워드를 사용하지 않으면 컴파일 오류가 발생한다.
typealias VoidVoidClosure = () -> Void
func functionWithEscapingClosure(completionHandler: @escaping VoidVoidClosure) -> VoidVoidClosure {
return completionHandler
}
class SomeClass {
var x = 10
func runEscapingClosure() -> VoidVoidClosure {
return functionWithEscapingClosure { self.x = 100 }
}
}
let instance: SomeClass = SomeClass()
let returnedClosure: VoidVoidClosure = instance.runEscapingClosure()
returnedClosure()
print(instance.x) //100
→ 탈출 클로저임을 명시한 경우, 클로저 내부에서 해당 타입의 프로퍼티나 메서드 등에 접근하려면 self
키워드를 명시적으로 사용해야 한다.
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
→ lazy
컬렉션에 있는 filter
메서드의 매개변수로 비탈출 클로저를 전달하는데, lazy
컬렉션은 비동기 작업을 할 때 사용하기 때문에 filter
메서드가 요구하는 클로저는 탈출 클로저이다.
이럴 경우에 withoutActuallyEscaping 함수를 사용한다.
var customerInLine: [String] = ["zooneon", "mike", "minsoo", "jhon"]
func serveCustomer(_ customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serveCustomer(customerInLine.removeFirst()) //Now serving zooneon!
@autoclosure
속성은 @noescape
속성을 포함한다.@autoclosure
속성 뒤에 @escaping
속성을 붙여서 사용하면 된다.var customerInLine: [String] = ["zooneon", "mike", "minsoo", "jhon"]
func returnProvider(_ customerProvider: @autoclosure @escaping () -> String) -> (() -> String) {
return customerProvider
}
let customerProvider: () -> String = returnProvider(customerInLine.removeFirst())
print("Now serving \(customerProvider())!") //Now serving zooneon!
주의 ! 자동 클로저의 과도한 사용은 코드를 이해하기 어렵게 만들 수 있다.