{ 매개변수 -> 반환 타입 in
//실행 코드
}
sorted(by:) 메서드로 클로저를 이해해보자.
public func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Elemet]
func backward(first: String, second: String) -> Bool {
return first > second
}
// sorted(by: backward)
sorted(by: { first: String , second: String -> Bool in
return first > second
}
sorted { first: String , second: String -> Bool in
return first > second
}
doSomething { something: String in
// do it
} onSuccess : { result: Any? in
// when success
} onFailure : { error: Error in
// when fail
}
sorted { first , second in
return first > second
}
sorted {
return $0 > $1
}
sorted { $0 > $1 }
sorted(by: >)
클로저는 자신이 (정의된 위치의 주변 문맥을 통해) 상수나 변수를 Capture(획득)할 수 있다.
→ 그래서 클로저는 (주변에 정의한) [상수나 변수가 더 이상 존재하지 않더라도] 해당 상수나 변수의 값을 자신 내부에 참조하거나 수정할 수 있다.
이 기능이 왜 필요할까?
클로저는 비동기 작업에 많이 사용된다. 클로저를 통해서 비동기 콜백(Async Call-back)을 작성하는 경우, 현재 상태를 미리 획득해두지 않으면, [실제로 클로저의 기능을 실행하는 순간]에는 (주변의 상수나 변수)가 이미 메모리에 존재하지 않는 경우가 발생한다. 그래서 콜백시에 사용될 수 있도록 미리 참조를 획득해놓는 것이다. (미리 참조를 획득했다면, 서로 영향을 주고 받지 않는 deep copy가 이뤄졌다는 말이다)
이 주변이라는 말은 지역변수, 상수 뿐 아니라 멤버변수, 상수도 가능하다. 그렇기 때문에 정확하게 맥락, 즉 어디 소속인지, 누구를 참조해서 캡처를 하려는지 명확하게 표현을 해줘야 한다. 그래서 항상 클래스 안에서 변수나 상수를 참조하려고 할 때 self를 쓰라고 나온다.
이런 참조 덕분에 캡처된 존재들은 RC가 올라간다. 메모리에서 해제되지 않고 그 클로저가 끝날 때까지 계속 유지하고 있는다. 이 때문에 비동기 작업시 메모리릭이 발생할 수 있다. self를 참조하고 있는 다른 객체, 클로저 등등에서 강한 순환 참조가 발생할 수 있기 때문이다.
클로저는 참조 타입이다. 즉 함수나 클로저를 상수나 변수에 할당할 때마다 사실은 상수나 변수에 함수나 클로저의 참조를 설정(swallow copy)하는 것일 뿐이다. 매번 클로저를 복사(deep copy)하는게 아니다. 따라서 해당 클로저를 다른 상수에 할당해준다면 이 두 상수가 같은 클로저를 가리킨다는 의미다.
클로저를 클래스의 인스턴스 프로퍼티로 할당하면, 클로저는 클래스 인스턴스(self) 혹은 클래스 인스턴스의 멤버(self.property)를 획득할 수 있지만 강한 순환 참조로 엮이게 된다. 클래스는 클로저를 강한 참조로 소유하고 있고 또 클로저 역시 클래스를 강함 참조로 소유하고 있기 때문에 메모리에서 해제가 되지 않는 것이다. 메모리릭! [weak self], [unowned self]를 클로저 안에서 자주 쓰게 되는 이유이다.
함수의 전달인자로 전달한 클로저가 함수 종료 후에 호출될 때 클로저가 함수를 탈출한다고 표현한다. 주로 비동기식 작업을 실행하는 함수들은 클로저를 completionHandler를 전달인자로 사용.
func hasElements(in array: [Int], match predicate: (Int)->Bool) -> Bool {
return array.lazy.filter { predicate($0) }.isEmpry == false
}
이 경우 lazy 때문에 filter의 연산이 arr에 실제로 접근할 때까지 미뤄진다. 즉 match라는 클로저로 들어온 인자가 함수 종료 후에 호출되야 하는건 아니지만 filter라는 함수에서 요구를 하기 때문에 어쩔 수 없이 나가야 하는 상황이다. 이 경우에 withoutActuallyEscaping(_:do:)를 사용한다.
func hasElements(in array: [Int], match predicate: (Int)->Bool) -> Bool {
return withoutActuallyEscaping(predinate, do: { escapablePredicate in
return array.lazy.filter { escapablePredicate($0) }.isEmpry == false
})
}
이렇게 withoutActuallyEscaping(_:do:)를 사용해서 비탈출 클러저를 탈출 클로저로 사용할 수 있다.
함수의 전달인자로 전달하는 표현(String, Int 등) 자동으로 변환해주는 클로저이다. 연산의 지연(클로저의 기본적인 특징인 실행해야 연산을 실행하는), 문법적 편의를 위한 것인데, 자주 사용하면 좋지 않고 사용하더라도 자동 클로저임을 명시해야 한다.
func serveCustomer(_ customerProvider: @autoclosure () -> String) {
print("\(customerProvider())"
}
serveCostomer(customerLine.removeFirst()) // "Jepline"