관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift 공식 문서] 7. Closure (클로저) 본문

swift 공식 문서

[iOS - swift 공식 문서] 7. Closure (클로저)

jake-kim 2021. 6. 24. 00:59

Closure

  • Closure(폐쇄): 클로저는 코드에서 전달사용할 수 있는 2가지의 자체 기능을 가진 블록
  • 정의된 컨텍스트에서 모든 상수 및 변수에 대한 참조를 캡쳐하고 저장 가능
    • 캡쳐: 자신의 블록 외부에 있는 값을 참조하는 것
// ex) 캡쳐: incrementer() 함수 블록에서 runningTotal과 amount인수를 참조

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

Closure는 실행 순서 제어가능

  • 클로저는 호출할 때까지 내부 코드가 실행되지 않기 때문에 적절한 타이밍에 사용 가능
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
customersInLine.count // "5"

let customerProvider = { customersInLine.remove(at: 0) }
customersInLine.count // "5"

customerProvider()
customersInLine.count // "4"

예시) 정렬 클로저

  • sroted(by:): 함수 클로저를 전달하여 정렬 규칙 정의

  • 함수 클로저 전달
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

let arr = ["a", "x", "d"]
let sortedArr = arr.sorted(by: backward)
  • Closure Expression (클로저 표현식): 함수를 따로 정의하지 않고 블록을 바로 전달
let sortedArr = arr.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})
  • Closure Expression - 타입 추론
let sortedArr = arr.sorted(by: { s1, s2 in return s1 > s2 } )
  • Closure Expression - retrun 생략
let sortedArr = arr.sorted(by: { s1, s2 in s1 > s2 } )
  • Closure Expression - argument name 접근: $0, $1, $2...
let sortedArr = arr.sorted(by: { $0 > $1 } )

Trailing Closure

func someFunctionThatTakesAClosure(closure: () -> Void) {
}

someFunctionThatTakesAClosure(closure: {
})

someFunctionThatTakesAClosure() {
}
  • 함수가 여러 클로저를 사용하는 경우: 첫 번째 trailing 클로저에 대한 argument label을 생략하고 나머지 trailing 클로저에 argument label 작성
// closure로 사용할 수 있는 parameter를 가진 함수 정의
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let picture = download("photo.jpg", from: server) {
        completion(picture)
    } else {
        onFailure()
    }
}

// trailing closure로 사용: 첫 argument label만 생략
loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print("Couldn't download the next picture.")
}	

closure는 reference type

  • 함수와 클로저는 reference type이기 때문에, incrementByTen() 호출마다 값이 누적되며 증가
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

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

@escaping closure

  • escaping closure: 외부에서 closure를 참조하는 것
    • cf) capture: 내부에서 외부를 참조하는 것

- ex) escaping closure

  • 주로 비동기 작업을 시작하는 많은 함수는 completionHandler로 클로저 인수를 사용
  • 함수는 작업을 시작한 후에 반환되지만 작업이 완료될 때까지 클로저가 호출되지 않으므로, 클로저를 나중에 호출하기 위하여 escaping closure를 사용
// @escaping을 선언하지 않은 경우 컴파일 에러 - 외부에서 closure를 참조하고 있기 때문

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}
  • 캡쳐 시 주의
    • escaping closure는 self 명시적 선언 필요
      • self가 필요한 이유: escaping은 해당 클로저가 외부로부터 참조 당했을 때, 캡쳐도 되는 상태면 reference cycle이 생길 수 있으므로 명시적 표현
    • none escaping closure는 self 암시적 사용
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

func someFunctionWithEscapingClosure(closure: @escaping () -> Void) {
    closure()
}

class SomeClass {
    var x = 10

    func doSomething() {
        someFunctionWithNonescapingClosure {
            x = 200 // 캡쳐
        }

        someFunctionWithEscapingClosure { [weak self] in
            self?.x = 100 // 캡쳐
        }
    }
}

@autoclosure

  • 인수를 받지 않으며 호출 될 때 내부 wrapping된 식을 반환하여 명시적 closure대신 일반 표현식을 작성하여 함수의 매개변수 주위에 블록을 생략하여 사용
  • @autoclosure 키워드
    • 인수가 자동으로 클로저로 변환되므로 괄호 '{}' 생략 가능
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

* 참고

https://docs.swift.org/swift-book/LanguageGuide/Closures.html

Comments