함수나 메서드가 하나 이상의 argument를 가질 때 이름과 선언없이 함수같은 역할을 하는 짧게 쓸 수 있다.
swift library가 제공하는 sorted(by:) 메소드는 사용자가 정의한 closure sorting을 기준으로 배열을 정렬해준다.
array.sorted(by:)를 실행하게 된다면 자체적으로 배열이 수정되는 것이 아니라 사용하려면 할당을 새로 해줘야 한다.
let names = ["Chirs","Alex","Ewa","Barry","Daniella"]
sorted(by:) 메서드는 배열의 내용을 같은 타입의 두 인자를 closure가 받아들이고, 오름차순인지 내림차순인지 결정하는 Bool 값을 받는다.
func backward(_ s1: String, _s2:String) -> Bool {
return s1>s2
}
var reversedNames = names.sorted(by:backward)
s1이 s2보다 크다면, backward(::)가 true를 반환하면서, 이것은 s1이 s2보다 앞에 나타나야함을 의미한다. String에서 크다는 의미는 사전순서로 나중에 나타난다는 의미이다.
예를 들어서 "B"는 "A" 보다 크고, "Tom"은 "Tim"보다 더 크다는 것이다.
하지만 a single-expression function(a>b)는 비효율적이기 때문에 closure을 사용하면 된다.
{(paramters) -> return type in
statements
}
closure 표현에서 parameters는 in-out으로 사용할 수 있지만, 그렇게 되면 default value는 사용 불가.
Variadic parameters도 사용가능하며 Tuple도 매개변수나 반환값에 사용이 가능하다.
위의 backward(::)의 closure버전을 보여주겠다.
reveredNames = names.sorted(by : {(s1:String,s2:String) -> Bool in
return s1 > s2
})
더 간단하게도 가능하다.
reveredNames = names.sorted(by: {(s1:String,s2:String) -> Bool in return s1>s2})
context로 부터 타입을 유추할 수 있어서 type을 쓰지 않아도 된다.
reversedNames = names.sorted(by:{s1,s2 in return s1 > s2})
names의 element는 String Type이므로 s1과 s2 또한 String type임을 알 수 있고 , s1>s2 의 Return type은 true or false인 Bool 타입임을 유추할 수 있다.
return 또한 생략가능하다.
reveredNames = names.sorted(by : s1, s2 in s1>s2)}
swift는 argument (s1,s2)를 간단하게 사용하는 방법을 제공한다.
인수 이름은 순서에 따라 $0,$1,$2와 같이 사용 가능.
즉 $0,$1과 같이 간편하게 사용할 수 있고, swift가 이를 자동으로 유추한다.
reveredNames = names.sorted(by:{$0>$1})
in 키워드 또한 생략을 할 수 있다. $0과 $1은 s1과 s2과 같은 의미이다.
더 짧게도 축약이 가능하다. Swift의 String 비교 연산자(<,>)를 사용하여 비교 가능.
reveredNames = name.sorted(by:>)
reveredNames = name.sorted(by: { (s1:String,s2:String) -> Bool in
return s1>s2
})
함수의 마지막 인수로 Closure을 사용할 때 너무 긴 경우 후행 closure로 사용이 가능하다
후행클로져는 함수 호출의 () 뒤 {}에 작성하면 된다.
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
// Here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}
위의 sorted를 구현한 것을 후행 closure로 나타낼 수 있다.
reveredNames = names.sorted() { $0 > $1}
만약 클로저 표현식이 함수,메서드의 유일한 매개변수일 때, 작성시 ()를 생략할 수 있다.
후행클로저는 길이가 길 때 사용하기 좋다.
예를 들어 Array 타입의 map(_:) 메서드는 하나의 클로저 표현식을 매개변수로 받는 메서드이다.
클로저는 Array의 항목을 한 번씩 호출하고 해당 항목에 대항 값들을 매핑값에 반환한다.
map에 전달되는 클로저는 매핑의 특성과 반환될 값들을 저장한다.
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
numbers Array를 digitNames를 사용하여 String type으로 변환할 때
let strings = numbers.map { (number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
위 코드처럼 Array의 모든 element에 대해 closure을 수행한다.
매개변수 (number)는 swift가 자동으로 타입을 유추하고, digitNames[number%10]!은 Dictionary의 value는 Optional이므로 강제 추출을 했다.
후행 closure을 여러개 사용하고 싶다면, 첫번째 후행 closure의 인수는 생략하고, 그 후의 클로저들에는 인수 레이블을 지정하면 됩니다.
func loadPicture(from server : Server, completion:(Picture) -> Void,onFailure:() -> Void) {
if let picture = download("photo.jpg",from : server) {
complection(picture)
} else {
onFailure()
}
}
첫번째 closure은 다운로드 성공 후 사진을 보여주는 completion handler이다.
두번째 closure은 유저에게 에러를 보여주는 부분이다.
loadPicture(from: someServer) { pictrue in
someView.currentPicture = picture
} onFailure : {
print("Couldn't download the next picture")
}
loadPicture함수를 호출하게 되면 completion,onFailure 클로저가 사용.
첫번째 completion부분에는 인수를 생략해도 가능해서 completion이 생략되었지만 두번째 closure는 인수를 생략하면 안되기때문에 onFailure이 쓰였다.
클로저는 상수나 변수를 캡쳐할 수 있다. 클로저가 주변 코드에서 상수와 변수를 캡쳐하고 그 뒤에 상수와 변수가 접근하게 되더라도 클로저는 해당 상수 및 변수의 값을 참조하고 수정할 수 있다.
swift에서 가장 쉽게 값을 캡처하는 방법은 중첩함수를 사용하는것이다.
func makeIncrementer(forIncrement amount:Int) -> () -> Int {
var unningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
incrementer()의 외부함수 makeIncrementer의 매개변수인 amount와 함수 내에서 정의된 runningTotal 변수를 캡쳐하여 사용. 실제로 incrementer함수에는 이러한 변수나 정의가 사용되어 있지 않지만 캡쳐하여 사용할 수 있다.
makeIncrementer의 변환 타입이 ()->Int인데 반환하는 값이 incrementer이므로 결국에는 incrementer의 반환 타입인 Int을 반환하게 된다.
let incrementByTem = makeIncrementer(forIncrement : 10)
incrementByTen()
incrementByTen()
incrementByTen()
increment 함수 호출마다 runningTotal의 값이 10씩 증가하고 이 값이 함수 makeincrementer이 호출된 이후에도 값이 사라지지 않고 계속 유지된다.
원서를 보고 작성했으나 이해가 되지않아서 나중에 추후에 더 디테일하게 작성하겠다.
위의 코드에서 incrementByTen은 상수이지만, 계속해서 그 값이 변하는 것을 볼 수 있다.
함수나 클로저를 상수나 변수에 할당하게 되면 복사되는 것이 아닌 메모리 주소 참조를 하게 되기 때문에 가능하다.
let alsoIncrementByTen = incrementByTen
alsoIncerementByTen()
//returns a value of 40
incrementByTEn()
//returns a value of 50
클로저는 함수에 인수로써 클로저가 전달될때 클로저는 함수를 Escape한다고 말할 수 있다. 하지만 함수가 return한 후에 호출된다.
클로저를 매개 변수중 하나로 클로저를 사용할 때 매개변수 타입 앞에 @eacaping을 작성하여 클로저가 Escape 될 수 있음을 나타낼 수 있다. 클로저가 escape할 수 있는 한가지 방법은 함수 외부에 정의된 변수에 클로저를 저장하고, 이를 매개변수로 이용하는 것이다.
예를 들어 비동기작업을 시작하는 많은 함수는 완료 핸들러로 클로저를 사용한다. 함수 작업을 시작한 후 반환되지만 작업이 완료될 때 까지 클로저가 호출되지 않는다. 클로저는 Escape해야 나중에 호출할 수 있다.
var completionHandlers = [() -> Void]()
function someFunctionWithEscapingClosure(completionHanlder : @escaping() -> Void{
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_ :) 함수는 매개변수로 클로저를 받고 완료된 작업을 외부에선 Array로 선언하는 함수이다. 만약 @escaping을 작성하지 않는다면 컴파일 에러가 발생한다.
self를 참조하는 Escaping 클로저는 더 신중하게 사용되어야 한다.
Escape 클로저에 self를 캡쳐하면 실수로 강력한 참조를 만들 수 있다. 강력 참조를 만들게되면 Swift의 AFC로 인해 메모리 낭비가 발생할 수 있다.
var completionHandler = [() -> Void]()
func someFunctionWithEscapingClosure(completionHanlder: @escaping() -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionNonescapingClosure(closure:() -> Void){
closure
}
class SomeClass {
var x = 10
func doSometing() {
someFunctionWithEscapingClosure{self.x = 100}
someFunctionWithNonescapingClosure{x = 200}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandler.first?()
print(instance.x)
// Prins "100"
someFunctionWithEscapingClosure함수에 전달된 클로저는 명시적으로 self를 나타냈는데, 반대로 밑의 함수는 excape 클로저가 아니기때문에 암시적으로 self를 참조할 수 있다.
class SomeOtherClass {
var x = 10
func doSometing() {
someFunctionWithEscapingClosure{ [self] in x = 100}
someFunctionWithNonescapingClosure{ x = 200}
}
}
위의 코드는 클로저의 캡쳐 목록에 포함해서 self를 캡쳐하고 암시적으로 self를 참조하는 방식이다.
self가 구조체,열거형 인스턴스인 경우 언제든지 self를 참조할 수 있다. 하지만 Excape 클로저는 이러한 가변적인 것에는 캡쳐할 수 없다.
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNonescapingClosure { x = 200 } // Ok
someFunctionWithEscapingClosure { x = 100 } // Error
}
}
excape 클로저를 사용하지 않고 구조체의 인스턴스를 참조할 수 있다.
이 부분 또한 이해하기 너무 어려워서 좀 더 공부를 해야될 것 같다.
함수에 인수로 전달되는 표현식을 자동으로 감싸기 위해 생성되는 클로저이다.
매개변수를 갖지 않으며 호출될 때 그 안에 있는 표현식의 값을 반환한다. 편의를 위해 AutoClosure를 사용할 때는 명시적인 클로저 대신 정규 표현식을 작성해서 함수의 매개변수를 감싸는 괄호를 생략할 수 있다.
Autoclosure를 수행하는 함수를 호출하는 것은 일반적이지만 함수를 구현하는 것은 일반적이진 않다.
예를 들어 assert(condition:message:file:line:) 함수는 condition,message 매개변수를 위해 오토 클로저를 사용합니다. condition 매개변수는 디버그 빌드에서만 사용되고, message 매개변수는 condition이 false인 경우에만 사용
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’
customProvider에서 클로저 내부코드로 at:0의 Element를 제거하지만 실제로는 갯수가 그대로이다.
클로저가 실제로 호출되지 않는 이상 수행되지 않는것이다. 실제로 호출하게 뒤면 그 이후 개수가 한개 줄었음을 알 수 있다.
func serve(customer customerProvider: @autoclosure() -> String {
print("Now Serving \(customerProvider())!")
}
serve함수는 매개변수를 명시적으로 받고, Array의 항목을 하나 제거하면 제거된 값을 print로 출력하는 함수이다.
func serv(customer customerProvider : @autoclosure() -> String {
print("Now serving \(customerProvider())!")
}
serve(customer:customersInLine.remove(at : 0))
//prints "Now serving Ewa!"
serve에 @autoclosure 키워드를 사용해 autoclosure을 사용한 것.
이렇게 하면 클로저를 호출할 때 까지 String타입을 사용하는 것처럼 함수 호출.
@autoclosure를 붙이게 되면 인수가 자동으로 클로저를 변환한다.
autoclosure과 escape 클로저를 함께 사용하고 싶다면 둘다 사용할 수 있다
// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"
위와 같이 collectCustomerProviders의 매개변수 customerProvider로 전달된 클로저를 호출하는 대신 collectCustomerProviders 함수는 클로저를 customerProviders Array에 추가합니다. Array는 collectCustomerProviders 밖에서 선언되었기 때문에 함수가 반환된 후 Array의 클로저가 실행될 수 있습니다. 즉 매개변수 customerProvider의 값이 함수의 범위를 벗어날 수 있어야 하는 것입니다.
처음 공부하고 무작정 해석하면서 쓰기만 해서 이해를 30%도 못한 것 같다.
계속 공부해서 보완해나가도록 하겠다.