본문 바로가기
공부/[iOS&Swift]

[Swift] 스위프트의 클로저(Closure)에 대하여!

by 인생은아름다워 2022. 1. 11.

✏️ 클로저에 대하여(Closure)

매우 중요한 개념의 등장인듯 하다. 함수형 프로그래밍의 시작, 클로저

일전에 아무것도 모른 채로 iOS앱 개발을 해본 적이 있다. 이때 모르는 문법들이 상당히 많이 등장했는데(C, python의 문법 정도만 알던 나에겐 매우 고차원적인 문법들이 많았다.) 그것들의 대다수는 클로저를 이해해야 함께 이해가 가능한 것들이 많았다.

또한, 스위프트란 함수형 프로그래밍 패러다임을 지향한다고 하는데, 이곳 저곳에서 찾아본 결과 이 개념 또한, 클로저를 이해하는것이 시작일 것이라고 판단된다.

클로저의 기본적인 개념과 축약 문법들 등이 자연스럽게 하는것이 오늘 공부의 목적이다!

✏️ Closure란?

공식문서에서는 클로저를 Closures are self-contained blocks of functionality that can be passed around and used in your code.라고 정의한다.

어딘가로 던져지거나 코드에서 사용될 수 있는 기능단위의 블록 정도로 정의하면 될 듯 하다.

그리고, Closure는 어떤 상수나 변수가 선언된 곳에서 그 참조를 획득(capture)하고, 저장(store)할 수 있다고 한다. 이러한 행위를 상수나 변수의 클로징(closing)이라 하며, 이것이 Closure(클로저)의 어원인 것 같다!

정리하자면, 클로저는 코드를 묶어놓은 블록이며, 변수와 상수를 클로징할 수 있는 것이다!

✏️ Capture(획득)이란?

위의 클로저의 어원에서 capture(획득)이라는 표현이 거슬린다. 무슨의미일까?(저장은 그래도 알겠는데...)

공식문서에서는 Capturing values라는 이름으로 해당 기능을 기술한다.

🤔 음..

Closure는 어떤 상수나 변수를 획득(capture)할 수 있으며, 그 내부에서(그 클로저 내부에서) 그 상수나 변수가 선언되어있는 곳이 아니라 하더라도 그 상수나 변수의 값을 인용하거나(refer to), 수정할(modify)수 있다.

대충 이해가 갈 것 같기도 하지만, 예시를 통해 확실하게 보는게 좋을듯 하다.

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

공식문서에 나와있는 예시이다. 위 코드를 보면, incrementer() 라는 함수 내의 runningTotal, amount 이 두 변수는 함수 내에서 어디에서도 정의해준 적이 없다! 뭔가 느낌이 오는 것 같기도 하다!

 

🧐 조금 더 자세히 봐볼까?

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}

이게 가능한 함수인가? 라는 물음에 대한 답은 당연히 No 이다. 클로저를 이용하면 이런 행위가 가능하다는 것이다. 그러니까, 위에서 설명한 클로징(closing)이라는 행위의 대표적인 예시가 바로 위의 nested function이다!

 

✔️ 위에 정의한 클로징에대한 설명을 다시 보면 뭔가 알겠다는것을 느낄 것이다!

여기서 클로징이라는 개념이 어떤것을 더 해줄 수 있는지 보자. 신기하다.

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

let incrementByTen: (() -> Int) = makeIncrementer(forIncrement: 10)
let incrementByOne: (() -> Int) = makeIncrementer(forIncrement: 1)

incrementByTen() // return 10
incrementByTen() // return 20
incrementByTen() // return 30

incrementByOne() // return 1
incrementByOne() // return 2

언제 어떻게 써먹을 수 있을진 모르지만, 진짜 신기하다. 위의 코드에서 makeIncrementer(forIncrement:) 함수가 실행되고 사라지더라도

incrementByTen과, incrementByOne 이라는 상수에는 incrementer라는 클로저 형태가 할당되어있으며, 자연스럽게 각각 amount를 10, 1로 각각 가지는... 그런 클로저가 되어있는 것이다.

이렇게 runningTotal과 amount라는 변수를 capture & store 하는것을 확인한다는것만으로도 매우 신기하는 생각이 든다.

✏️ Closure 알아보기

스위프트에서 클로저의 기본 표현은 아래와 같다.

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

공식문서에서는 컬렉션타입인 Array의 메써드 sorted(by: )를 통해 설명한다.

sorted(by:) 메써드의 정의를 보면 다음과 같다. func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element]

매개변수로 Element타입 두 개를 받고, Bool타입 을 반환하는 클로져, 반환타입으로는 Element타입의 Array

let myStringArray = ["JM", "jm", "MJ", "mj"]

let inputClosureForSort = { String, String -> Bool in
                          return }

let inputClosureForSort = { (first: String, second: String) -> Bool in
    return first > second
}

myStringArray.sorted(by: inputClosureForSort) //어떤식으로든 정렬되어 나온 새로운 Array

myStringArray.sorted(by: { (first: String, second: String) -> Bool in
    return first > second
})
//위의 표현과 정확히 같은 표현

위와 같이 클로져를 정의해주고, 직접 넣어주면 된다는것인데... 사용이 너~무 복잡하다! 그래서 우리는 클로져 사용시 축약하는 문법들을 가지고 있다!

✏️ Trailing Closure(후행 클로저)

스위프트에서 함수의 전달인자 중, 마지막에 클로저가 위치하는 경우 ==마지막에 위치하는 클로저는 함수(또는 메서드)의 소괄호를 닫고난 이후에 작성해줘도 된다.==

//마지막 전달인자이기 때문에 소괄호 닫은 이후 클로저를 전달
myStringArray.sorted(){ (first: String, second: String) -> Bool in
  return first > second
}

//전달인자가 클로저 하나만 있을 경우 이렇게 소괄호 자체를 생략할 수도 있다.
myStringArray.sorted { (first: String, second: String) -> Bool in
    return first > second
}

위와 같은 형태의 축약은 정말 정말 많이 보여질 예정이기에 숙지해야겠다!

✏️ (Inferring Type From Context) 문맥을 통한 타입 유추

함수(또는 메서드)호출 시 전달해줘야 하는 클로저의 형태는, 함수에서 지정한 타입과 일치해야한다. 그 말은, 컴파일러는 이미 어떤 클로저의 타입이 와야할지 안다는 것이다. 그 말은, 전달하는 클로저의 타입을 생략해도, 컴파일러는 알아서 잘 타입을 유추한다는 것이다.

여기서 생략하는 타입이란, 클로저의 매개변수, 그리고 반환 타입을 말한다!

myStringArray.sorted { (first, second) in
    return first > second
}

//sorted라는 메서드의 전달인자로는 어떤 클로저가 보내졌고,
//그 클로저는 first, second라는 두 개의 매개변수를 갖고있으며
//first>second라는 값을 반환하는 클로저이다

in은 빠지지 않았다.

✏️ (Shortand Argument Names) 단축 인자 이름

전달하는 클로저의 매개변수의 이름도 굳이 필요없다고 보는 것이다. 그래서, 매개변수의 이름도 생략할 수 있다.

myStringArray.sorted {
    return $0 > $1
}

//sorted라는 메서드의 전달인자는 하나의 클로저인데
//그 클로저의 매개변수는 두 개이며($0, $1을 보고 유추)
//첫 번째 매개변수 > 두 번째 배개변수 의 부울값을 반환하는 클로저이다.

✏️ Implicit Returns from Single-Expression Closures (암시적 반환 표현)

이제는 return 도 보기 싫다는 것이다. 스위프트에서 함수(또는 메서드)의 전달인자로 클로저가 전달될 때 그 ==클로저 내부의 실행문이 딱 한줄이며, 반환값을 가진다면, 암시적으로 return키워드를 생략하고 그 실행문 자체를 반환값으로 사용할 수 있다.==

myStringArray.sorted {
    $0 > $1
}

// sorted 메서드는 클로저 하나를 전달인자로 받으며
// 그 클로저의 매개변수는 두개이고, 반환값은 (첫번째 매개변수 > 두 번째 매개변수)이다.

너..무 간단해졌다.

오늘은 클로저의 문법을 이정도로 정리해볼까 한다. 더 많은 사용법, 문법이 있지만 지금 나에게는 이정도를 자연스럽게 해석해내는 것이 먼저일 것 같다. Escapingclosure나 Autoclosure는 나중에...

🍎 결론

진짜 많이 쓰인다는 것을 알아서 한 줄 한 줄 적어봤다. 아직 익숙하지 않다.

그래도 익숙해져야한다.

참고했던 글

Swift-LanguageGuide/Closure

댓글