티스토리 뷰

iOS 개발/Swift

[Swift 3] 클로저 (Closures)

beankhan 2017. 4. 14. 13:58

출처

http://kka7.tistory.com/9




클로저의 개념

스스로를 포함하는 함수 블록이며
코드 안에서 전달하고 사용할 수 있다.

클로저는 Objective-C 의 Block 과 
다른 프로그래밍 언어의 람다와 비슷하다.

클로저는 정의된 Context 로부터 모든 상수와 변수에 대한 참조를 캡쳐하고 저장한다.
Swift 는 캡쳐하는 모든 메모리를 관리해준다.

전역, 중첩 함수들은 클로저의 특별한 경우이다.



클로저 표현식 (Closure Expressions)


클로저 표현식은 문법에 집중하여
짧은 시간에 클로저를 작성하는 방법이다.

표현식은 명확성과 의도를 잃지 않고 작성 중인 클로저에 대해
최적화하는 몇가지 문법을 제공한다.

아래와 같은 예제가 있다.
일반적으로 우리가 썼던 방식이다.

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


func backward(_ s1: String, _ s2: String) -> Bool {

    return s1 > s2

}

var reversedNames = names.sorted(by: backward)




기본적인 클로저 표현식은 아래와 같다.

클로저의 본문은 " in " 키워드로 시작된다.
이 키워드는 클로저의 매개변수와 반환타입의 선언 마지막에 나타나고
클로저의 본문을 시작한다.


{ (parameters) -> return type in

    statements

}



이를 반영한 결과는 아래와 같다.

reversedNames = names.sorted(by: { (s1 : String, s2 : String) -> Bool in

    return s1 > s2

})




문맥으로부터 타입 추론

정렬 클로저는 메소드에 인자를 전달하기 때문에
Swift 는 매개변수의 타입과 반환하는 값의 타입을 추론할 수 있다.

(String, String) -> Bool 타입이 추론되기 때문에 함수 타입을 생략할 수 있다.
클로저는 중괄호로 감싸져야한다.
소괄호도 생략가능하다.


reversedNames = names.sorted(by: {

    (s1, s2) in return s1 > s2

})


reversedNames = names.sorted(by: {

    s1, s2 in return s1 > s2

})




단일 표현식 클로저로부터의 암시적인 반환

클로저의 본문은 Bool 값을 반환하는
하나의 표현식 ( s1 > s2 ) 을 포함하기 때문에 
return 키워드를 생략할 수 있다.

reversedNames = names.sorted(by: {

    (s1, s2) in s1 > s2

})




축약된 인자 이름

인라인 클로저에서 축약된 인자 이름을 자동으로 제공하며
$0, $1, $2 이름으로 클로저의 인자 값을 참조할 수 있다.

클로저 표현식에서 축약된 인자 이름을 사용하면
클로저의 정의에서 인자 목록을 생략할 수 있고
갯수와 축약된 인자 이름의 타입은 예상된 함수 타입으로 추론될 것이다.

클로저 표현식이 본문 전체를 만들기 때문에 in 을 생략할 수 있다.

reversedNames = names.sorted(by: {

    $0 > $1

})




연산자 메소드

sorted (by :...  에 전달되는 파라미터는
(String, String) -> (Bool) 로써 
크기 비교 연산자의 문자열별 구현을 정의한다.

따라서 간단하게 비교 연산자를 전달할 수 있고, 
Swift 는 사용하길 원하는 문자열별 구현을 추론한다.

reversedNames = names.sorted(by: >)





후행 클로저

함수 마지막 인자로 표현식이 긴 클로저 표현식 전달이 필요한 경우
후행 클로저로 작성할 수 있다.

후행 클로저는 함수에 인자를 전달하더라도
함수 호출의 괄호 뒤에 작성된다.

후행 클로저를 작성할 대 함수 호출의 일부분처럼 클로저에 대해 인자 레이블을 사용하지 않는다.

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



유일한 인자로 후행 클로저 표현식이 제공되면
함수를 호출할 때 함수 뒤 괄호가 생략 가능하다.

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



후행 클로저는 클로저가 한줄에 들어가지 않을만큼 충분히 길 때

매우 유용하다.


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 }

print(strings)

//["OneSix", "FiveEight", "FiveOneZero"]






값 캡쳐하기

클로저는 정의된 컨텍스트 주변의 상수와 변수를 캡쳐할 수 있다.

클로저 본문 내에서 캡쳐된 상수와 변수의 값을 수정하고 참조할 수 있다.
심지어 상수와 변수가 정의된 원래 범위에 더 이상 존재하지 않더라도 말이다.

클로저의 가장 간단한 형태는 값을 캡쳐할 수 있는 중첩된 함수이며
다른 함수의 본문에 작성한다.

func makeIncrementer(forIncrement amount: Int) -> () -> Int {

    var runningTotal = 0

    

    func incrementer() -> Int {

        runningTotal += amount

        return runningTotal

    }

    

    return incrementer

}



let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen()

incrementByTen()

incrementByTen()


let incrementBySeven = makeIncrementer(forIncrement: 7)

incrementBySeven()  // 7


incrementByTen()    // 40





클로저는 참조타입이다 (reference type)

incrementByTen 은 상수이지만, 
클로저는 runningTotal 증가 가능한 변수를 캡쳐한 상수를 참조한다.

함수나 클로저를 상수나 변수에 할당할 때
실제로 상수와 변수는 함수나 클로저의 참조로 설정된다.

이는 두 개의 다른 상수나 변수에 클로저를 할당하는 경우

상수나 변수 둘 다 같은 클로저를 참조한다는 것을 의미한다.


let anotherIncrementByTen = incrementByTen

anotherIncrementByTen()     //50





클로저 벗어나기

함수를 선언할 때 매개변수로 클로저를 가지며
클로저가 벗어나는 것을 허용한다는 것을 나타내기 위해
매개변수 타입 앞에 @escaping 을 작성한다.

즉시 실행을 막고 추후에 실행할 수 있도록 막는 것을 뜻한다.


var completionHandlers: [() -> Void] = []


func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {

    completionHandlers.append(completionHandler)

}


func someFunctionWithNonescapingClosure(closure: () -> Void){

    closure()

}


class SomeClass {

    var x = 10

    func doSomething() {

        someFunctionWithEscapingClosure {

            self.x = 100

        }

        someFunctionWithNonescapingClosure {

            x = 200

        }

    }

}


let instance = SomeClass()


instance.doSomething()

print(instance.x) // Prints "200"


completionHandlers.first?()

print(instance.x) // Prints "100"



클로저를 @escaping 표시하는 것은 

클로저가 self 을 명시적으로 참조하고 있다는 것을 의미한다.


위 예제에서는 someFunctionWithEscapingClosure(_:) 에 전달하여

명시적으로 self 를 참조할 필요가 있다는 의미이며, 클로저가 벗어난다.


클로저 내부에서 self 를 참조하고 있다면 

해당 클로저를 파라미터로 받는 함수는 @escaping type 으로 선언해줘야한다.


반대로, someFunctionWithNonescapingClosure(_:) 는 

암시적으로 self 를 참조할 수 있다는 의미이며 클로저가 벗어나지 못한다.





자동 클로저


함수 호출은 자동클로저지만 함수의 종류에 대해 일반적인 구현은 아니다.

자동 클로저 코드 내부는 클로저가 호출될 때까지 
실행되지 않기 때문에 지연되어 구해진다.

지연되어 구해지는 것은 코드가 언제 구해지는지 제어할 수 있기 대문에
부작용이 있거나 계산이 오래 걸리는 때 유용하다.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]


print(customersInLine.count)

// Prints "5" let


let customerProvider = {

    customersInLine.remove(at: 0)

}


print(customersInLine.count)

// Prints "5"


print("Now serving \(customerProvider())!")

// Prints "Now serving Chris!"


print(customersInLine.count) // Prints "4"


배열의 요소는 클로저가 실제 호출될 때까지 제거하지 않는다. (remove)


클로저가 호출되지 않으면 클로저 내부의 표현식은 처리되지 않으며

배열의 요소가 제거되지 않는다.




// 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!"


여기서 serve 함수는 customers 의 이름을 반환하는 명시적인 클로저를 가진다.

아래 예제의 경우 @autoclosure 속성을 가진 자동클로저를 가진다.


comtomerProvider 매개변수의 타입이 @autoclosure 속성으로 되어있기 때문에

그 인자는 자동으로 클로저로 변환된다.

즉, 중괄호를 생략할 수 있다.


// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] 

func serve(customer customerProvider: @autoclosure () -> String) {

    print("Now serving \(customerProvider())!")

}


serve(customer: customersInLine.remove(at: 0))

// Prints "Now serving Alex!"



자동 클로저를 남용하면 코드를 이해하기 어렵게 만들 수 있다.

context 와 함수 이름은 명확하게 만들고 처리가 지연되는 것을 확인해야한다.













공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함