[Swift] Closure

Lena·2020년 12월 1일

Closure

Closure는 실행 가능한 코드 블럭이다. 함수의 인자로 closure가 전달될 수 있다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// 함수로 정의
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

// 인라인 클로저로 작성
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

Closure Expression Syntax

names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
names.sorted(by: { s1, s2 in return s1 > s2 } ) // Inferring Type From Context
names.sorted(by: { s1, s2 in s1 > s2 } ) // Implicit Returns from Single-Expression Closures
names.sorted(by: { $0 > $1 } ) // Shorthand Argument Names

Trailing Closures

Closure가 함수의 마지막 인자로 전달될 경우, () 뒤에 argument label 을 생략하고 괄호를 열어서 closure를 작성할 수 있다.

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

closure가 함수의 마지막이자 유일한 인자라면, ()도 생략할 수 있다.

names.sorted { $0 > $1 }

스위프트의 collection에서 기본적으로 제공하는 map, filter, reduce 함수는 closure를 유일한 인자로 받는다.

let numbers = [0, 1, 2, 3, 4]

var doubledNumbers = [Int]()
var strings = [String]()

// for-in
for number in numbers {
    doubledNumbers.append(number * 2)
    strings.append("\(number)")
}

// map
doubledNumbers = numbers.map { $0 * 2 } // trailing closure
strings = numbers.map { "\($0)" }

// filter
let numbers = [0, 1, 2, 3, 4, 5]
let evens = numbers.filter { $0 % 2 == 0 } // [0, 2, 4]

Capturing Values

A closure can capture constants and variables from the surrounding context in which it is defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.

Closure는 주변의 상수나 변수를 capture할 수 있고, 클로저 내에서 그 값을 참조하거나 변경할 수도 있다. capturing values의 가장 간단한 형태는 nested function에서 볼 수 있다(func 안의 func).

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

incrementer()은 amount, runningTotal를 capture하고, 그 값을 function body 내에서 사용한다. capture한 reference는 incrementer()가 종료되더라도 유지되기 때문에 다시 incrementer()가 호출되면 runningTotal 사용 가능하다.

let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen() // return 10
incrementByTen() // return 20

또한, clousure는 reference type이다.

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // return 30

Escaping Closures

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns

클로저가 함수의 인자로 전달되지만 함수가 리턴된 이후 실행 되는 경우 escaping closure이라고 한다. 즉, 함수의 인자로 전달되나 나중에 실행되어야 하는 경우 escaping closure를 사용한다고 할 수 있다.

var completionHandlers = [() -> Void]() // 나중에 실행될 completionHandler 클로저

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler) // 클로저가 함수를 탈출
}

// https://firebase.google.com/docs/storage/ios/download-files?hl=ko
// 파이어베이스 스토리지로부터 다운로드 url 생성할 때, download(completion: (URL?, Error?) -> Void)을 호출할 수 있다.

starsRef.downloadURL { url, error in
    if let error = error {
      // Handle any errors
    } else {
      // Get the download URL for 'images/stars.jpg'
    }
}
    
// download url
func downloadSong(for path: String, completionHandler: @escaping (Result<URL, Error>) -> Void) {
    // storage 경로
    let ref = storage.child(path)
      
    // 경로에 있는 파일 다운로드할 url 얻기
    ref.downloadURL(completion: { url, error in
        guard let url = url, error == nil else {
            // 실패
            completionHandler(.failure(Error.FailToFetch))
            return
        }
        // 성공
        completionHandler(.success(url))
    })
}

// caller
StorageManager.shared.downloadSong(for: "songs/distant moon.mp3") 
{ [weak self] result in
	switch result {
	case .success(let url):
	    self?.songs.append(데이터)
	case .failure(let error):
	    print(error)
	}
})

escaping closure에서 self를 참조할 때는 self 키워드를 명시적으로 작성하거나 capture list에 포함해야 한다.

someFunctionWithEscapingClosure { [self] in x = 10 }

하지만 strong reference cycle을 만들기 쉬우므로 weak selfunowned self을 명시하는 것이 좋다.

struct, enum과 같은 value type의 인스턴스는 escaping closure가 capture(refer to) 할 수 없다.

References

https://docs.swift.org/swift-book/LanguageGuide/Closures.html
https://oaksong.github.io/2018/01/20/higher-order-functions/

Read More

What’s the difference between a function and a method?

0개의 댓글