# 함수형 프로그래밍 2 - 클로저 [스위프트] 🦩

클로저
맵, 필터, 리듀스
모나드

import Swift

let add: (Int, Int) -> Int
add = {(a:Int, b:Int) -> Int in 
    return a + b
}

let substract: (Int, Int) -> Int
substract = {(a:Int, b:Int) -> Int in
    return a-b
} 

let divide: (Int, Int) -> Int
divide = {(a:Int, b:Int) -> Int in
    return a/b
}

// 함수 안에서 전달받은 method클로저를 호출한다
func calculate(a:Int, b:Int, method: (Int, Int)-> Int) -> Int {
    return method(a, b)
}

var calculated: Int

calculated = calculate(a: 50, b: 10, method: add)
print(calculated)   // 60
calculated = calculate(a: 50, b: 10, method: substract)
print(calculated)   // 40
calculated = calculate(a: 50, b: 10, method: divide)
print(calculated)   // 5


calculated = calculate(a: 50, b: 10, method: {(left: Int, right:Int) -> Int in
    return left * right
})
print(calculated)   // 500

// 후행클로저
calculated = calculate(a: 50, b: 10) { (left: Int, right:Int) -> Int in
    return left * right
}
print(calculated)

// 반환타입은 위에 명시되어 있기 때문에 생략 가능
calculated = calculate(a: 50, b: 10, method: {(left: Int, right:Int) in
    return left * right
})
print(calculated)   // 500

// 단축 인자 이름
calculated = calculate(a: 50, b: 10, method: {
    return $0 * $1
})
print(calculated)   // 500

// 단축 인자 이름 + 후행 클로저
calculated = calculate(a: 50, b: 10) {
    return $0 * $1
}
print(calculated)   // 500

// 암시적 반환
calculated = calculate(a: 50, b: 10) {
    $0 * $1
}
print(calculated)   // 500

13. 클로저 Closure

참조Reference를 획득Capture할 수 있어서
변수나 상수의 클로징(잠금)이라고 한다.

  • 이름이 있거나 없거나
  • 값을 획득하거나 하지 않거나

13.1 기본 클로저

{ (매개변수들) -> 반환타입 in
	실행코드
}
let names: [String] = ["wizplan", "eric", "yagom", "jenny"]
let reversed: [String] = names.sorted(by: { (first: String, second: String) -> Bool in
    return first > second
})
print(reversed)     // ["yagom", "wizplan", "jenny", "eric"]

13.2 후행 클로저

Trailing closure을 사용하려면 completion 함수가 가장 뒤에 나와야 한다

  • 클로저의 타입을 정의해주고 클로저 함수를 따로 써준 후 함수를 호출하는 경우
// 1.url 구글을 받으면 completion함수 {}를 실행시킨다
// 2.completion에 string을 넣어준다
//2.
func getData(url: String, completion: ((String)-> Void)) {
    completion("foo")
}

//1.
getData(url: "www.google.com") { string in
    print(string)   //foo
}
  • 클로저 타입을 선언하고 실행할 함수도 함께 쓴 경우
// Bool타입의 파라미터를 받는 클로저 타입의 변수 completion
// ((Bool) -> Void) = { 실행함수 }
let completion: ((Bool) -> Void) = { value in
    print(value)
}

completion(true)    // true
completion(false)   // false

APICaller에서 활용해보기

import Foundation

class APICaller {
    static let shared = APICaller()
    
    // 1.
    func performRequest(with url: URL, completion: @escaping ((Result<Data, Error>)-> Void)) {
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                return
            }
            completion(.success(data))	//비동기로 실행되는 클로저
        }
        task.resume()
    }
}
  1. Escaping closure captures non-escaping parameter 'completion'
    task의 completion handler에서 performRequest의 completion handler를 사용하기 때문에 escaping 해주어야 한다

    task가 완료되면 data, response, error를 사용하는 completion handler 함수가 정의된다
    이를 performRequest가 completion handler를 사용해 Result타입의 매개변수(성공 또는 실패)를 넣어 함수를 정의한다
    여기에서는 성공하면 data를 받아서 Void를 리턴해! 라는 뜻으로 사용되었다


import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let url = URL(string: "") else {
            return
        }
        
        APICaller.shared.performRequest(with: url) { result in
            switch result {
            case .success(let data):
                print(data)
            case .failure(let error):
                print(error)
            }
        }
    }
}

13.3 클로저 표현 간소화

13.3.1 문맥을 이용한 타입 유추

let reversed: [String] = names.sorted() { (first, second) in
    return first > second
}

13.3.2 단축 인자 이름

let reversed: [String] = names.sorted() {
    return $0 > $1
}

13.3.3 암시적 반환 표현 (한 줄 일 때)

let reversed: [String] = names.sorted() { $0 > $1 }

13.3.4 연산자 함수(>)

>가 함수 이름

public func > <T: Comparable>(lhs: T, rhs: T) -> Bool
let reversed: [String] = names.sorted(by: >)

13.4 값 획득 Variable Capture

13.5 클로저는 참조 타입

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

let incrementByTwo: (() -> Int) = makeIncrementer(forIncrement: 2)
let sameWithIncrementByTwo: (() -> Int) = incrementByTwo

let first: Int = incrementByTwo()           //2
let second: Int = sameWithIncrementByTwo()  //4

13.6 탈출 클로저 @escaping 💥

https://docs.swift.org/swift-book/LanguageGuide/Closures.html

non-escaping closure

func runClosure(closure: () -> Void) {
    closure()
}
  1. 클로저가 runClosure() 함수의 closure 인자로 전달됨
  2. 함수 안에서 closure() 가 실행됨
  3. runClosure() 함수가 값을 반환하고 종료됨
    이렇게 클로저가 함수가 종료되기 전에 실행되기 때문에 closure는 Non-Escaping 클로저 입니다.

escaping closure

class ViewModel {
    var completionhandler: (() -> Void)? = nil
    
    func fetchData(completion: @escaping () -> Void) {
        completionhandler = completion
    }
}
  1. 클로저가 fetchData() 함수의 completion 인자로 전달됨
  2. 클로저 completioncompletionhandler 변수에 저장됨
  3. fetchData() 함수가 값을 반환하고 종료됨
  4. 클로저 completion은 아직 실행되지 않음
    completion은 함수의 실행이 종료되기 전에 실행되지 않기 때문에 escaping 클로저, 다시말해 함수 밖(escaping)에서 실행되는 클로저 입니다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

위 함수에서 인자로 전달된 completionHandler는 someFunctionWithEscapingClosure 함수가 끝나고 나중에 처리 됩니다. 만약 함수가 끝나고 실행되는 클로저에 @escaping 키워드를 붙이지 않으면 컴파일시 오류가 발생합니다.

이스케이핑 클로저 (Escaping Closures): 클로저를 함수의 파라미터로 넣을 수 있는데, 파라미터 타입 앞에 @escaping이라는 키워드를 명시해야 합니다.

    1. 함수 밖(함수가 끝나고)에서 실행되는 클로저
    1. 비동기로 실행되는 클로저

@escaping 를 사용하는 클로저에서는 self를 명시적으로 언급해야 합니다.

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()    // 함수 안에서 끝나는 클로저
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 합니다.
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

import UIKit

// task 내부에서 외부의 completion: ((Bool) -> Void) 함수를 사용하기 위해 @escaping을 붙여준다
func getData(completion: @escaping ((Bool) -> Void)) {
    let task = URLSession.shared.dataTask(with: URL(string: "")!) { data, response, error in
        guard data != nil else {
            completion(false)
            return
        }
        completion(true)
    }
    task.resume()
}


final class APICaller {
    var isReady = false
    
    func warmup() {
        isReady = true
        
        // array에 값이 있으면 안에 있는 모든 completion을 실행시키고 삭제한다
        if !completionHandlers.isEmpty {
            completionHandlers.forEach { $0() }
            completionHandlers.removeAll()
        }
    }
    
    var completionHandlers = [(() -> Void)]()
    
    func doSomething(completion: @escaping (() -> Void)) {
        guard isReady else {
            completionHandlers.append {     //1. 1. 외부의 completionHandlers를 사용하기 때문에 @escaping이 필요하다
                completion()
            }
            return
        }
        completion()
    }
}

클로저를 활용해 버튼 생성해보기

버튼 생성할 때 클로저를 활용하면 속성을 한눈에 보기 쉬워진다

import UIKit

class ViewController: UIViewController {
    
//    let button = UIButton()
    
    // 파라미터는 없지만 버튼을 리턴하는 함수
    let button: UIButton = {
        let button = UIButton()
        button.backgroundColor = .red
        button.setTitleColor(.white, for: .normal)
        button.setTitle("static configure", for: .normal)
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //configureButton()
    }
    
//    private func configureButton() {
//        button.backgroundColor = .gray
//        button.setTitle("Continue", for: .normal)
//    }
    
    @objc func didTapButton() {
        
    }
}

좋은 웹페이지 즐겨찾기