티스토리 뷰
[시작]
- 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 함수는 함수 객체를 반환함
runningTotal과 amount는 초기화되지 않고 누적됨
incrementer 클로저가 runningTotal과 amount의 참조를 획득했기 때문
[클로저는 참조 타입]
- 함수나 클로저를 상수나 변수에 할당하는 것은 사실 상수나 변수에 함수나 클로저의 참조를 설정하는 것
: 값이 아닌 참조를 할당
- 클로저의 참조를 다른 상수에 할당한다면 이는 각각의 상수가 모두 같은 클로저를 가리키는 것
[탈출 클로저]
- 함수의 전달인자로 전달한 클로저가 함수 종료 후에 호출될 때 클로저가 함수를 탈출한다고 표현
- 매개변수 이름 콜론 뒤에 @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 |