티스토리 뷰

Swift/야곰의 스위프트

13장 클로저

할루루 2018. 2. 12. 10:52

[시작]


- Swift에서 함수형 프로그래밍 패러다임을 이해하기 위해 반드시 알아야 하는 것

- 클로저, 제네릭, 프로토콜, 모나드 등이 결합하여 Swift는 더욱 강력한 언어가 되었음


- 클로저 : 일정 기능을 하는 코드를 하나의 블록으로 모아놓은 것

 : 함수는 클로저의 한 형태

- 클로저는 변수나 상수가 선언된 위치에서 참조를 획득하고 저장할 수 있음

- 클로저의 세 가지 형태

 : 이름이 있고, 어떠한 값도 획득하지 않는 전역함수의 형태

 : 이름이 있고, 다른 함수 내부의 값을 획득할 수 있는 중첩함수의 형태

 : 이름이 없고, 주변 문맥에 따라 값을 획득할 수 있는 축약 문법으로 작성된 형태

- 클로저의 다양한 표현

 : 매개변수와 반환값의 타입을 문맥을 통해 유추할 수 있으므로 생략 가능

 : 단 한 줄의 표현만 들어가 있다면 이를 반환값으로 취급

 : 축약된 전달인자 이름 사용 가능

 : 후행 클로저 문법 사용 가능



[기본 클로저]


1
2
3
4
{ (매개변수들) -> 반환 타입 in
    실행 코드
}
 
cs

위는 일반적인 클로저의 형태


1
2
3
4
5
6
let numbers: [Int= [1,7,2,3,4,8,5]
let sortedNumbers: [Int= numbers.sorted { (val1: Int, val2: Int-> Bool in
    return val1 < val2
}
print(sortedNumbers)
 
cs


위는 Swift 표준 라이브러리에 있는 sorted(by:) 함수를 클로저를 활용하여 구현한 코드

sortedNumbers에는 numbers 배열이 오름차순으로 정렬된 결과가 들어감



[후행 클로저]


- 함수나 메서드의 마지막 전달인자로 위치하는 클로저는 함수나 메서드의 소괄호를 닫은 후 작성해도 됨

- 단 하나의 클로저만 전달인자로 전달하는 경우에는 소괄호를 생략할 수 있음



[클로저 표현 간소화]


- 메서드의 전달인자로 전달하는 클로저는 메서드가 요구하는 형태로 전달해야 함

- 즉 전달인자로 전달할 클로저는 이미 적합한 타입을 준수하고 있다고 유추할 수 있음

- 그러므로 전달인자로 전달하는 클로저를 구현할 때는 매개변수와 반환값의 타입을 명시하지 않아도 됨

1
2
3
4
5
let numbers: [Int= [1,7,2,3,4,8,5]
let sortedNumbers: [Int= numbers.sorted { (val1, val2) in
    return val1 < val2
}
print(sortedNumbers)
cs


위는 매개변수와 반환값의 타입을 생략한 코드 (문맥을 이용한 타입 유추)


- $와 숫자의 조합으로 단축 인자 이름을 표현할 수 있음 ($0, $1, $2, ...)

- 단축 인자 이름을 사용하면 키워드 in을 사용할 필요도 없음

1
2
3
4
5
6
let numbers: [Int= [1,7,2,3,4,8,5]
let sortedNumbers: [Int= numbers.sorted {
    return $0 < $1
}
print(sortedNumbers)
 
cs


위는 단축 인자 이름을 사용하여 매개변수 이름과 키워드 in을 생략함 (단축 인자 이름)


- 클로저가 반환값을 갖고 클로저 내부의 실행문이 단 한 줄이라면, return 키워드를 생략하고 해당 실행문을 반환값으로 사용 가능

1
2
3
4
5
6
let numbers: [Int= [1,7,2,3,4,8,5]
let sortedNumbers: [Int= numbers.sorted {
    $0 < $1
}
print(sortedNumbers)
 
cs


위는 return 키워드를 생략함 (암시적 반환 표현)


- 비교 연산자는 두 개의 피연산자를 통해 Bool 타입을 반환함

 : 연산자는 일종의 함수 -> 클로저

- 연산자 자체가 함수의 이름이므로 이를 클로저를 요구하는 전달인자로 전달 가능

1
2
3
4
let numbers: [Int= [1,7,2,3,4,8,5]
let sortedNumbers: [Int= numbers.sorted(by: <)
print(sortedNumbers)
 
cs




[값 획득]



1
2
3
4
5
6
7
8
9
10
11
12
13
func makeIncrementer(forIncrement amount: Int-> ()->Int{
    var runningTotal = 0
    func incrementer() -> Int{
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}
let incrementByTwo = makeIncrementer(forIncrement: 2)
let first = incrementByTwo()    //2
let second = incrementByTwo()   //4
let thrid = incrementByTwo()    //6
 
cs


makeIncrementer 함수는 함수 객체를 반환함

runningTotalamount는 초기화되지 않고 누적됨

incrementer 클로저runningTotalamount의 참조를 획득했기 때문



[클로저는 참조 타입]


- 함수나 클로저를 상수나 변수에 할당하는 것은 사실 상수나 변수에 함수나 클로저의 참조를 설정하는 것

 : 값이 아닌 참조를 할당

- 클로저의 참조를 다른 상수에 할당한다면 이는 각각의 상수가 모두 같은 클로저를 가리키는 것



[탈출 클로저]


- 함수의 전달인자로 전달한 클로저가 함수 종료 후에 호출될 때 클로저가 함수를 탈출한다고 표현

- 매개변수 이름 콜론 뒤에 @escaping 키워드를 붙여 클로저가 탈출하는 것을 허용한다고 명시해줄 수 있음

- 비동기 작업으로 함수가 종료된 후 작업이 끝나고 호출할 필요가 있는 클로저를 사용해야 할 때 활용 가능


- 위의 sorted(by:) 같은 경우는 클로저가 함수 작업에 사용되므로 비탈출 클로저를 전달인자로 전달함


- 클로저가 함수를 탈출할 수 있는 경우

 : 함수 외부에 정의된 변수나 상수에 저장되어 함수가 종료된 후 사용될 때

 : 함수의 전달인자로 전달 받은 클로저를 반환할 때

1
2
3
4
5
6
7
8
9
10
let firstClosure: () -> Void = { print("Closure A") }
let secondClosure: () -> Void = { print("Closure B") }
func returnOneClosure(first: @escaping () -> Void, second: @escaping () -> Void, shouldReturnFirstClosure: Bool-> () -> Void {
    //전달인자로 넘어온 클로저를 반환함
    return shouldReturnFirstClosure ? first : second
}
//함수에서 반환한 클로저가 함수 외부의 상수에 저장됨
let returnedClosure: () -> Void = returnOneClosure(first: firstClosure, second: secondClosure, shouldReturnFirstClosure: true)
returnedClosure()
 
cs


위에서 함수가 전달인자로 넘어온 클로저를 반환하므로 탈출 가능함


- 탈출 클로저 내부에서 해당 타입의 프로퍼티나 메서드 등에 접근하려면 self 키워드를 명시적으로 사용해야 함


- 비탈출 클로저로 전달한 클로저가 탈출 클로저인 척 해야 하는 경우

 : hasElements(in:match:) 함수는 비탈출 클로저를 전달받음, 전달받은 클로저는 탈출 클로저를 요구하는 메서드의 매개변수로 들어감

 : 이러한 경우에 비탈출 클로저를 탈출 클로저인 것처럼 만들어 주어야 함

- withoutActuallyEscaping(_:do:) : 첫 번째 매개변수에 비탈출 클로저, 두 번째 매개변수에 실제 작업을 실행할 탈출 클로저 전달



[자동 클로저]


- 함수의 전달인자로 전달하는 표현을 자동으로 변환해주는 클로저

- 자동 클로저는 전달인자를 갖지 않음

- 호출되기 전까지 클로저 내부의 코드가 동작하지 않음 -> 지연


- 자동 클로저를 사용하면 기존 방법처럼 클로저를 전달인자로 넘겨줄 수 없음

- 전달인자를 갖지 않으므로 반환 타입의 값이 자동 클로저의 매개변수로 전달되면 이를 클로저로 바꾸어 줌

1
2
3
4
5
6
var numbers: [Int= [6,1,2,4]
func serveCustomer(_ customerProvider: @autoclosure () -> Int) {
    print("Now \(customerProvider())")
}
serveCustomer(numbers.removeFirst())
 
cs

1
2
3
4
5
6
var numbers: [Int= [6,1,2,4]
func serveCustomer(_ customerProvider: () -> Int) {
    print("Now \(customerProvider())")
}
serveCustomer { numbers.removeFirst() }
 
cs


위의 코드들은 같은 동작을 함

함수의 전달인자가 자동 클로저로 선언된 것은 전달인자로 클로저가 아닌 반환 타입의 값을 전달해줌


- 자동 클로저의 과도한 사용은 코드를 이해하기 어렵게 만들 수 있음


- 자동 클로저를 탈출하는 클로저로 사용하고 싶다면 @autoclosure @escaping 키워드를 사용



[마무리]


- 클로저는 매우 강력하지만 생략할 수 있는 부분이 많아 가독성이 떨어질 수 있음

- 협업 시 클로저 표현 방법을 정해 놓고 하는 것이 좋겠다

'Swift > 야곰의 스위프트' 카테고리의 다른 글

15장 맵, 필터, 리듀스  (0) 2018.02.12
14장 옵셔널 체이닝과 빠른 종료  (0) 2018.02.12
12장 접근제어  (0) 2018.02.05
11장 인스턴스 생성 및 소멸  (0) 2018.02.05
10장 프로퍼티와 메서드  (0) 2018.02.03
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/02   »
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
글 보관함