ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 클로저 (Closures)
    Swift 2020. 12. 5. 01:22

    클로저 (Closure)는 코드 블록으로, 어떤 상수나 변수의 참조를 캡쳐해 저장합니다. Swift는 이 캡쳐와 관련한 모든 메모리를 알아서 처리합니다.

    전역 함수(global functions)와 중첩 함수(nested function)은 실제 클로저의 특별한 경우입니다.

    다음은 세 가지 형태의 클로저입니다. 

    • 전역 함수 : 이름이 있고 어떤 값도 캡쳐하지 않는 클로저
    • 중첩 함수 : 이름이 있고 관련한 함수로부터 값을 캡쳐할 수 있는 클로저
    • 클로저 표현 : 경량화된 문법이 쓰여지고 관련된 문액(context)로부터 값을 캡쳐할 수 있는 이름이 없는 클로저

    클로저 표현은 문멕에서 파라미터 타입과 return 타입을 추론할 수 있고, 참조 타입입니다. 또한 후위 클로저 형식입니다.

     

     

    클로저 표현 (Closure Expressions)

    { (인자 값) -> 반환 값 in
        구현
    }

    -> 클로저 표현은 위의 형태로 정의합니다.

     

    정렬 메소드 (The Sorted Method)

    Swift의 표준 라이브러리에는 sorted(by:)라는 배열 값을 정렬하는 메소드를 제공합니다. by에서 어떤 방법으로 정렬을 수행할 것인지에 대해 클로저를 사용합니다.

    let numbers = [4, 2, 19, 30, 12, 1]

    -> 위의 배열을 정렬해보자면, 

    -> sorted(by:)메소드는 배열의 값들과 같은 타입의 값들을 두 개 받습니다. numbers는 Int형의 배열이므로, (Int, Int) -> Bool 형식의 클로저를 사용합니다.

    func ascending(_ n1: Int, _ n2: Int) -> Bool{
        return n1 < n2
    }
    
    var ascendNumber = numbers.sorted(by: ascending)

    -> 위의 코드는 numbers배열을 오름 차순으로 나열하는 코드입니다.

    -> ascendNumber를 출력해보면 [1,2,4,12,19,30]이 출력됩니다.

     

     

    클로저 표현 문법 (Closure Expression Syntax)

    위에서 설명한 코드는 아래와 같은 방식으로도 바꿀 수 있습니다.

    var ascendNumbers = numbers.sorted(by: {
        (n1: Int, n2: Int) -> Bool in
            return n1 < n2
    })

    -> 위와 같이 클로저가 인자로 들어가 있는 형태를 인라인 클로저라고 합니다. 클로저의 body는 in 키워드 다음에 시작합니다.

     

     

    문맥에서 타입 추론 (Inferring Type From Context)

    클로저에서는 인자 값의 타입과 return 타입은 생략될 수 있습니다. 

    var ascendNumbers = numbers.sorted(by: {
        (n1, n2) in
        return n1 < n2
    })

     

     

    단일 표현 클로저에서의 암시적 변환 (Implicit Returns from Single-Express Closures)

    단일 표현 클로저에서는 반환 키워드 또한 생략할 수 있습니다.

     

     

    인자 이름 축약 (Shorthand Arguments Names)

    Swift 는 인라인 클로저에 자동으로 축약 인자 이름을 제공합니다. $0, $1, $2등으로 사용할 수 있습니다. 

    축약 인자 이름을 사용하면 in 키워드와 인자를 입력받는 부분을 생략할 수 있습니다.

    ascendNumbers = numbers.sorted(by: { $0 < $1 })

     

     

    연산자 메소드 (Operator Methods)

    따로 클로저를 생성하지 않고 비교 연산자를 이용해도 됩니다.

    ascendNumbers = numbers.sorted(by: < )

     

     

     


     

    후위 클로저 (Trailing Clusures)

    만약 함수의 마지막 인자로 클로저를 넣고 그 클로저가 길다면 후위 클로저를 사용합니다.

    func someFunctionThatTakesAClosure(closure: () -> Void){
    }
    
    // ⬇️
    
    someFunctionThatTakesAClosure(closure: {
    
    })
    
    // ⬇️
    
    someFunctionThatTakesAClosure(){
    }

    -> 위의 코드를 아래의 코드와 같이 변경할 수도 있고, 세 번째처럼 후위 클로저로 표현할 수 있습니다.

     

     

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

    -> 앞의 정렬 예제를 후위 클로저로 표현하면 위와 같이 됩니다.

    -> 또한 함수의 마지막 인자가 클로저이고, 후위 클로저를 사용한다면, 괄호를 생략할 수 있습니다.

     

     

    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{ (num) -> String in
    	var num = num
        var result = ""
        repeat{
        	output = digitNames[num % 10]! + output
            num /= 10
        }while num > 0
        return output
    }

    -> numbers의 값들을 map을 이용해서 string으로 바꾸는 코드입니다. 

    -> digitNames[number % 10]! 에 느낌표가 붙은 이유는 딕셔너리의 subscript는 옵셔널이기 때문입니다. key에 대한 값은 있을 수도 있고, 없을 수도 있기 때문에 옵셔널이어야 합니다.

     

     

     


    값 캡쳐 (Capturing Values)

    클로저는 특정 문맥의 상수나 변수의 값을 캡쳐할 수 있습니다. 원본 값이 사라져도 클로져의 body안에서 그 값을 활용할 수 있습니다.

    Swift 에서 값을 캡쳐하는 방법에는 중첩 함수 (nested function)이 있습니다.

    중첩 함수는 함수 내부에서 새로운 함수가 정의된 형태를 말합니다.

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

    -> makeIncrementer함수를 보면 return 형이 ()->Int인 클로저 함수이고, 

    -> incrementer함수에서는 runningTotal과 amount가 선언되지 않아도 사용할 수 있습니다. 그 이유는 runningTotal과 amount가 캡쳐링되서 그런것입니다.

    -> 아래의 incrementByTen상수에서 makeIncrementer함수는 incrementer 클로저를 반환합니다.

    -> 만약 incrementByTen()을 여러번 실행하면 runningTotal과 amount가 캡쳐링되어서 공유되기 때문에 누적된 값을 가집니다.

     

     

    클로저는 참조 타입 (Closures Are Reference Types)

    앞의 예제에서 incrementByTen은 상수로 선언이 되어있는데 어떻게 내부 변수를 계속 증가시킬 수 있는지를 보면 함수와 클로저는 참조 타입이기 때문입니다. 함수와 클로저를 상수나 변수에 할당을 하면 해당 함수와 클로저의 참조가 할당이 됩니다. 

    그래서 같은 클로저가 할당된 두 개의 변수는 같은 클로저를 참조하고 있습니다.

    let alsoIncrementByTen = incrementByTen
    alsoIncrementByTen()

    -> 위의 코드에서 alsoIncrementByTen()는 위에서 누적된 값에서 10을 더한 값이 반환됩니다.

     

     

     

    이스케이핑 클로저 (Escaping Closures)

    클로저를 함수의 파라미터로 넣을 수 있는데, 함수가 끝나고 실행되는 클로저 , 예를 들어서 비동기로 실행되거나 completionHandler로 사용되는 클로저는 파라미터 타입 앞에 @escaping이라는 키워드를 명시해야 합니다.

    클로저는 @noescape가 기본값입니다. 함수에 클로저가 파라미터로 전달이 되고 전달된 클로저는 바로 실행이 됩니다. 이때의 클로저는 함수외부에서 접근이 불가능합니다.

    이스케이핑 클로저는 함수가 종료된 뒤에 클로저가 필요한 경우 사용합니다. 

    1. 클로저를 저장하고 함수가 종료된 뒤에 사용하고 싶을 때

    2. 디스패치 큐에서 비동기 작업을 수행하는 경우, 함수가 종료되고나서도 클로저를 메모리에 남겨두어서 비동기 작업을 이어가도록 해야할 때

    var completionHandlers: [() -> Void] = []
    func withEscapingClosure(completionHandler: @escaping() -> Void){
        completionHandlers.append(completionHandler)
    }
    
    func withoutEscapingClosure(closure: () -> Void){
        closure()
    }
    
    class SomeClass{
        var x = 10
        func doSomething(){
            withEscapingClosure{ self.x = 100 }
            withoutEscapingClosure { x = 200 }
        }
    }
    
    let instance = SomeClass()
    instance.doSomething()
    print(instance.x) // 200
    
    completionHandlers.first?()
    print(instance.x) // 100

    -> @escaping을 사용하는 클로저에는 self를 명시적으로 언급해야 합니다. 그 이유는 함수가 종료되고나서 어떤 객체를 참조하고 있는지를기억하기 위해서입니다.

    -> 파라미터로 전달된 값은 함수 내부에서만 사용할 수 있지만 이스케이핑 클로저는 함수 외부에 있는 CompletionHandlers배열에서도 접근이 가능합니다. 

    -> 출력에서, doSomething()을 실행하면 withoutEscapingClosure함수만 실행이 되었습니다. withEscapingClosure는 completionHandlers배열에 클로저를 저장만 하는 함수이므로 x의 값을 변경하지 않습니다.

    -> completionHandlers.first?()를 실행하고나서야 x의 값이 변경되었습니다.

     

     

    자동 클로저 (Autoclosures)

    클로저를 파라미터로 받을 때 @autoclosure를 붙이면 함수를 호출할 대 중괄호를 입력하지 않아도 자동으로 클로저로 변환해줍니다.

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

    -> serve함수는 파라미터로 클로저를 받지만 @autoclosure가 붙어있어서, 실제 함수를 호출할 때에는 {}를 붙이지 않아도 됩니다. 

    'Swift' 카테고리의 다른 글

    상속 (Inheritance)  (0) 2020.12.10
    메소드 (Methods)  (0) 2020.12.08
    클래스와 구조체 (Classes and Structures)  (0) 2020.11.10
    프로퍼티 (Properties)  (0) 2020.11.10
    열거형 (Enumerations)  (0) 2020.11.05

    댓글

Designed by Tistory.