[Swift5] Closures 2

Junyoung Park·2022년 3월 3일
0

Swift5 Docs

목록 보기
15/37
post-thumbnail
  • 다음은 Swift 5.6 Doc의 Closures 공부 내용을 정리했음을 밝힙니다.

Closures

트레일링 클로저

클로저가 함수의 파이널 아규먼트로 사용되거나 너무 길다면 트레일링 클로저(trailing closure)를 사용할 수 있다.

트레일링 클로저는 함수 호출 시 괄호 다음에 작성한다. 이때 함수 호출의 일부로 첫 번째 클로저에 대한 아규먼트 라벨을 쓰지 않아도 된다. 함수 호출은 여러 개의 트레일링 클로저를 포함할 수 있다.

func func_closure(closure: () -> Void){
	// function body
    // function without trailing closure
}

func func_closure2(closure: {
	// closure body
})

func func_trailing(){
	// trailing closure body
}

트레일링 클로저가 메소드 괄호 바깥에 사용되는 경우는 다음과 같다.

reversedNames = names.sorted() { $0 > $1 }

이때 클로저 표현이 유일한 아규먼트고 이 표현을 트레일링 클로저로 쓸 때에는 괄호를 쓰지 않아도 된다.

reversedNames = 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]

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
}

map 메소드는 배열에 저장된 아이템 하나마다 클로저 표현을 호출한다.
타입 추론을 통해서 이후 매핑된 값이 저장될 배열이 어떤 타입인지도 알 수 있다.

위에서 digitNames에 담긴 옵셔널 값을 강제 언래핑하는 것도 체크하자.

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let picture = download("photo.jpg", from: server) {
        completion(picture)
    } else {
        onFailure()
    }
}

이 함수는 클로저가 두 개다. 첫 번째 클로저는 completion 핸들러로 다운로드가 성공적인 경우, 두 번째 클로저는 onFailure 핸들러로 에러가 발생했을 때를 다룬다.

loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print("Couldn't download the next picture.")
}

loadPicture를 호출했을 때 다운로드한 사진을 someView.currentPicture로 할당하고, 실패할 경우 에러 핸들러에 전달한 프린트 문을 출력한다. 즉 함수 내 선언과 별개로 원하는 값을 출력할 수 있다는 게 특징이다.

값 캡처하기

클로저는 클로저가 정의된 콘텐스트에서 상수 및 변수를 캡처할 수 있다. 그리고 클로저 바디에서 정의된 기존 범위가 존재하지 않더라도 그 값을 참조 및 변경할 수 있다.

값을 캡처하는 가장 간단한 클로저는 중첩 함수다. 중첩 함수는 자신을 둘러싼 함수의 아규먼트, 변수, 상수를 모두 캡처할 수 있다.

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

makeIncrementer의 리턴 타입은 () -> Int, 즉 함수를 리턴하는데, 파라미터가 없고 정수 값을 리턴하는 함수다. 또한 이 함수는 forIncrement이라는 아규먼트 라벨을 가진 amoun라는 정수를 파라미터로 받는다.

makeIncrementerincrementer를 바깥에서 둘러싸고 있는데, 이때 incrementer 중첩 함수가 makeIncrementer의 아규먼트 amount와 변수 runningTotal을 모두 캡처하고 있다.

이때 중첩 함수 incrementer()가 파라미터 없이 선언되지만 자신을 둘러싼 makeIncrementer 값을 참조할 수 있다. 또한 makeIncrementer 호출이 끝나도 runningTotal이나 amount가 사라지지 않고 남아 있다는 데 주의.

let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7

incrementByTen()
// returns a value of 40

amount가 10으로 고정된 incrementByTen은 호출될 때마다 amount를 더한 10, 20, 30...을 리턴하고 있다. 파라미터를 다르게 준 incrementBySeven이 만들어진 이후에도 incrementByTen이 그대로 유지되고 있다.

profile
JUST DO IT

0개의 댓글