[Swift] 클로저 closure

Bibi·2022년 1월 20일

[Swift] 클로저 closure

https://babbab2.tistory.com/81?category=828998

클로저는 람다 계산식lambda calculus 의 구현체이다.

  • 이름 없는 함수로 작성 가능
  • 선언한 스코프에서 변수를 캡처해 저장하고 닫힘

클로저란?

스위프트에서 클로저란, 내부 함수와 내부 함수에 영향을 미치는 주변 요소를 모두 포함한 객체이다.

  • 클로저는 클로저 내에서 사용하는 변수/상수의 값을 미리 저장해 두고(캡처) 사용한다.

클로저와 함수

클로저는 사실 이름이 있는 함수와 이름이 없는 함수 모두를 포함하는 개념이다.

  • 이름이 있는 함수(Named Closure)
    • func 로 선언된 이름이 있는 함수들
  • 이름이 없는 함수(Unnamed Closure), 익명함수
    • 이름을 붙이지 않고 사용하는 함수들

그러나 통상 "클로저"라고 하면 익명함수를 의미한다.

1급 객체

클로저는 함수이고, 스위프트에서 함수는 1급 객체이다.

따라서 클로저도...

  • 변수/상수에 대입 가능
  • 리턴값으로 사용 가능
  • 파라미터로 사용 가능

클로저는 참조 타입

클로저와 함수는 모두 참조 타입이다.

  • 메모리의 힙 영역에서 관리된다.
  • 함수나 클로저를 상수/변수에 할당할 때, 그 값이 아닌 참조를 할당하는 것이다.

클로저와 값 획득 - 캡쳐 Capture

클로저는 클로저 내에서 사용하는 변수/상수의 값을 획득해 두고(캡쳐) 사용한다.

  • 값 획득을 통해, 클로저는 주변에 정의된 상수/변수가 없어지더라도 해당 상수/변수의 값을 클로저 내부에서 참조 및 수정할 수 있다.
  • 비동기 작업에 사용되는 클로저가 제대로 작동하도록 한다.

클로저의 캡쳐 방식 - Reference Capture

❗️클로저는 값을 캡처할 때, 그 값이 값 타입/참조 타입인 것과 관계없이 캡쳐하는 값들을 참조한다. - "참조 캡처 Reference Capture"

  • 값 타입도 참조를 캡쳐하고, 참조 타입도 참조를 캡쳐한다.

만약 값 타입을 값으로 캡쳐하고 싶다면 캡쳐 리스트 Capture List를 활용해야 한다 - 참고


클로저 표현식

{ (Parameters) -> ReturnType in
	// 실행 구문
}
  • 클로저 헤드 Closure Head : (Parameters) -> ReturnType
  • 클로저 바디 Closure Body : 실행 구문
  • in : 클로저 헤드와 클로저 바디를 구분짓는 키워드

❗️클로저에서는 Argument Label을 사용하지 않는다. 오직 Parameter Name 만 사용한다.

클로저 실행하기

클로저는 호출 구문인 ()를 붙여 실행한다.

호출 방법은 크게 두 가지가 있다

  • 클로저가 대입된 상수/변수로 호출하기
let closure = { () -> String in
     return "hello world!"
}

closure()
  • 클로저를 직접 실행하기 - 일회용
    • 클로저 전체를 소괄호() 로 감싸고, 마지막에 호출 구문인 ()을 붙인다.
({ () -> () in
  	print("hello world!")
})()

클로저의 경량 문법

트레일링 클로저 (trailing Closure) = 후행 클로저

  • "함수의 마지막 파라미터가 클로저일 때", 클로저의 Argument Label을 생략하고 함수 뒤에 붙여 작성한다.

    • func doSomething(closure: () -> ()) {
         print("do something!")
      }
      
      // doSomething 호출 - Inline Closure
      doSomething(closure: { () -> () in
          print("Hello")
      })
      
      // doSomething 호출 - Trailing Closure
      doSomething() { () -> () in
          print("Hello")  
      }
  • "함수의 파라미터가 클로저 하나인 경우", 호출구문 ()도 생략 가능하다.

    • doSomething { () -> () in
         print("Hello")
      }
  • 함수의 파라미터가 여러 개일 때는 호출구문()을 마음대로 생략해서는 안된다(다른 파라미터를 넘겨 주어야 하므로)

    • func fetchData(success: () -> (), fail: () -> ()) {
         // do something
      }
      
      // fetchData 호출 - Inline Closure
      fetchData(success: { () -> () in
          print("success")
      }, fail: { () -> () in
          print("fail")
      })
      
      // fetchData 호출 - Trailing Closure
      fetchData(success: { () -> () in
         print("success")
      }) { () -> () in
         print("fail")  
      } // 마지막 파라미터의 클로저는 함수 뒤에 붙여 작성

클로저 표현 간소화

아래 예제를 간소화해 표현해 본다.

func doSomething(closure: (Int, Int) -> Int) {
   closure(1, 2)
}

doSomething(closure: { (a: Int, b: Int) -> Int in
     return a + b
})
  • 파라미터 타입과 리턴 타입 생략하기

    • doSomething(closure: { (a, b) in
          return a + b  
      })
  • 단축 인자 이름(Shorthand Argument Names) 사용하기

    • Parameter Name 대신 단축인자를 사용 가능하다.

    • doSomething(closure: {
          return $0 + $1
      })
  • return 생략하기

    • 암시적 반환 표현 : 클로저 내부에 return문 한 줄만 남은 경우, return 표현도 생략 가능

    • doSomething(closure: {
         $0 + $1
      })
  • 트레일링 클로저

    • 마지막 파라미터인 클로저를 함수 선언 바깥에 사용

    • Argument Label 생략 가능

      • doSomething() {
            $0 + $1
        }
    • 클로저 하나만 파라미터로 전달하는 경우, 호출구문의 소괄호 생략 가능

      • doSomething {
            $0 + $1
        }
  • 연산자 함수

    • 스위프트의 연산자 역시 일종의 함수이다. 따라서 사용할 클로저의 시그니처가 연산자 함수의 시그니처와 같다면 연산자만 표기해 사용할 수 있다.

    • let arr1 = [1,2,3]
      arr1.reduce(0) { $0 + $1 } // 6
      arr1.reduce(0, +) // 6

0개의 댓글