
: 명명된 함수 생성 없이 실행되는 코드 블럭
: 코드에서 주변에 전달과 사용할 수 있는 자체 포함된 기능 블럭
(다른 프로그래밍 언어의 클로저, 익명함수, 람다, 블럭과 비슷)
정의된 컨텍스트 안에서
모든 상수 / 변수에 대한 참조를 1) 캡처할 수 있고 2) 저장할 수 있다.
전역 함수 : 이름을 가지고(o) 어떠한 값도 캡처하지 않는(x)
중첩 함수 : 이름을 가지고(o) 둘러싼 함수로부터 값을 캡처할 수 있는(o)
클로저 표현식 : 이름이 없고(x) 주변 컨텍스트에서 값을 캡처할 수 있는(o)
클로저 표현식은 깔끔한 구문을 장려하는 최적화를 통해 깔끔하고 명확한 스타일을 가지고 있다.
컨텍스트에서 파라미터와 반환값 타입 유추
단일 표현식 클로저의 암시적 반환
약식 인수 이름
후행 클로저 구문
: 간단하고 집중적인 구문으로 "이름 없는 함수"를 작성하는 방법 -> 함수의 기능을 한 줄로 정의 !!
sorted(by: ) 메서드 : 배열을 정렬할 때 사용하는 메서드
1) 전통적인 방법 (일반 함수)
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
//backward 함수 인수로 전달
var reversedNames = names.sorted(by: backward)
// reversedNames는 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
2) 클로저 표현식을 사용한 방법 (backward 함수의 클로저 표현 버전)
in 키워드로 클로저의 본문을 시작한다.
-> 클로저의 파라미터와 리턴 타입 정의가 끝남 & 클로저의 본문이 시작함
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
클로저를 인라인 표현식으로 전단할 때 항상 파라미터 타입과 반환 타입을 유추할 수 있다.
ex) 위 예제의 경우 문자열 배열에 대해서 호출했기때문에 "string 이겠구나!🤓"하고 유추 가능
즉, [->, 소괄호] 를 생략하여 한줄로도 작성할 수 있다.
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
return 키워드로 생략하여 단일 표현식으로 암시적으로 값을 반환할 수 있다!
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
swift는 인라인 클로저의 [$0, $1, $2] 등 클로저의 인수값으로 참조하는데 사용할 수 있는 자동적으로 짧은 인수 이름을 제공한다.
또한 클로저 표현식이 본문으로 전체가 구성되기 때문에
in 키워드를 생략할 수도 있다.
reversedNames = names.sorted(by: { $0 > $1 } )
$0 와 $1 은 클로저의 첫번째와 두번째 String 인수를 참조한다.
$1 가장 높은 숫자이므로 클로저는 2개의 인수가 있다고 이해한다.
Swift의 String 타입은 보다 큰 연산자 (>)의 문자열 별 구현을 String 타입의 파라미터 2개가 있는 메서드로 정의하고 Bool 타입의 값을 반환한다.
= sorted(by:) 메서드와 정확하게 일치!!
아래와 같이 짧게 쓸 수 있다.
reversedNames = names.sorted(by: >)
: 마지막 인수로 함수에 클로저 표현식을 전달해야하고, 클로저 표현식이 긴 경우 사용
1. 일반 클로저 전달 방식
func someFunction(closure: () -> Void) {
// 함수 본문
}
someFunction(closure: {
print("Hello, world!")
})func someFunction(closure: () -> Void) {
// 함수 본문
}
someFunction(closure: {
print("Hello, world!")
})
2. 후행 클로저 방식
후행 클로저는 함수의 인수이지만 함수 호출의 소괄호 다음에 작성한다.
someFunction() {
print("Hello, world!")
}
후행 클로저는 클로저가 길어서 한줄로 인라인으로 작성이 불가능할 때 유용하다.
ex)
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 배열을 사용하여 후행 클로저로 map(_:) 메서드로 클로저 표현식을 전달하여 String 값의 배열을 생성할 수 있다.
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"]
클로저 표현식은 호출될 때마다 output 이라는 문자열을 만든다.
나머지 연산자 (number % 10)를 이용하여 number 의 마지막 숫자를 계산하고 digitNames 딕셔너리에 적절한 숫자 문자열을 찾는다.
: 첫번재 후행 클로저의 인수 라벨을 생략하고 남은 후행 클로저의 라벨은 표기
Ex) 사진 갤러리에서 사진 하나를 불러오는 함수
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
사진 하나를 불러오기 위해 2개의 클로저를 제공
//1. 사진 다운로드 완료 후에 사진을 보여주기 위한 완료 처리
loadPicture(from: someServer) { picture in
someView.currentPicture = picture
}
//2. 오류를 표시하는 오류 처리기
onFailure: {
print("Couldn't download the next picture.")
}
loadPicture(from:completion:onFailure:) 함수는 네트워크 작업을 백그라운드로 전달하고 네트워크 작업이 완료되면 두 완료 처리기 중 하나를 호출한다.
두 상황을 모두 처리하는 하나의 클로저를 사용하는 대신 성공적인 다운로드 후 사용자 인터페이스를 업데이트 하는 코드에서 네트워크 오류를 처리하는 코드를 명확하게 분리할 수 있다.
클로저는 둘러싸인 컨텍스트에서 상수와 변수의 값을 참조하고 수정할 수 있다.
바깥 함수의 어떠한 인수도 캡처할 수 있고 바깥 함수 내에 정의된 상수와 변수를 캡처할 수도 있다.
아래 코드는 incrementer 라는 중첩 함수가 포함된 makeIncrementer 라는 함수의 예제 !
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
중첩된 incrementer() 함수는 둘러싸인 컨텍스트에 runningTotal 과 amount 인 2개의 값을 캡처
캡처한 후에 incrementer 는 호출될 때마다 amount 로 runningTotal 을 증가시키는 클로저로 makeIncrementer 에 의해 반환
makeIncrementer 호출이 종료될 때 runningTotal 과 amount 가 사라지지 않고 다음에 incrementer 함수가 호출될 때 runningTotal 을 사용
동작을 어떻게 하는지 봐보면..! 😙
1. runningTotal 변수에 10 을 더하는 증가 함수를 참조하도록 incrementByTen 이라는 상수를 설정해보자.
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// 1o
incrementByTen()
// 20
incrementByTen()
// 30
2. 두번째 증가를 생성해보자.
(새로운 분리된 runningTotal 변수에 참조 저장)
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 7
3. 기존 증가 incrementByTen 을 다시 호출해보자.
incrementByTen()
// 40
그것의 runningTotal 변수는 이어서 증가되고 incrementBySeven 으로 캡처된 변수는 영향을 주지 않는다.
클로저는 참조타입이기때문에 위와 같이 캡처한 특정 변수를 계속 증가시킬 수 있는 것이다.
참조타입인 것을 더 확실하게 확인해보자면..!
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 50
incrementByTen()
// 60
위 예제에서 alsoIncrementByTen 호출은 incrementByTen 호출과 같음을 보여준다.
2개 모두 같은 클로저를 참조하기 때문에 둘 다 증가하고 같은 러닝 합계를 반환한다.
함수가 반환된 후 호출되는 클로저를 '함수를 탈출하다'라고 말한다.
클로저가 함수 안에서 실행되면 괜찮지만, 함수 밖으로 "탈출"해서 나중에 실행된다면 문제가 생김! -> 파라미터의 타입 전에 @escaping을 작성해서 이 클로저는 나중에 쓸거다는 의미를 나타내야한다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler) // 함수 밖으로 탈출!
}
someFunctionWithEscapingClosure(_:) 함수는 인수로 클로저를 가지고 있고 함수 바깥에 선언된 배열에 추가한다.
함수의 파라미터에 @escaping 을 표시하지 않으면 컴파일 시 에러가 발생 !
인수를 클로저처럼 감싸서 실행 시점을 지연시켜주는 문법이다.
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 마치 그냥 표현식처럼 보이지만, 내부적으로 클로저로 감싸짐!
// Prints "Now serving Ewa!"
클로저를 나중에 실행하고 싶고, 자동으로 감싸고 싶을 때 둘 다 붙이면 된다 👍
func collectCustomerProviders(_ customer: @autoclosure @escaping () -> String) {
customerProviders.append(customer)
}