[SWIFT] Closure(클로저)

2020. 8. 27. 16:46SWIFT

반응형

오늘은 SWIFT 언어의 큰 특징 중 하나인 클로저에 대해 알아보려고 합니다.

 

우선 클로저를 이해하기 전에 알아야 할 것이 있습니다.

 

SWIFT 언어의 특징이 혹시 기억나시나요⁉️

 

바로 함수형 프로그래밍  이라는거 그렇다면 다들 함수형 프로그래밍에 대해 이해는 잘 되셨나요?

저도 잘 되지 않았었는데 이 패러다임을 잘 이해하기 위해 꼭 필요한 개념이 바로 클로저(Closure)라는 사실

 

그럼 클로저(Closure)을 공부하기 이전에 함수형 프로그래밍이 무엇이었는지 짧게 살펴보고 갈게요.

 

 

함수형 프로그래밍란?

기존에 있던 객체지향 프로그래밍이나 명령형 프로그래밍(C 언어)에서는 어떤 값이나 상태의 변화를 중요하게 여기지만 함수형 프로그래밍은 어떤 함수 자체를 활용해서 변하는 결과를 중요시 하는 것이 가장 큰 차이인 것 같습니다.

 

이를 활용하면 어떤 부분이 좋을까요⁉️

 

한번 객체 지향 프로그래밍이나 절차 지향 프로그래밍의 프로그램 처리 과정을 생각해볼까요? 

어떤 인자를 주고 받는 과정에서 여러 처리가 되는 동안 만약 같은 인스턴스에 접근하게 되면 어떻게 될까요⁉️

 

결과에 큰 영향을 미치겠죠.

 

그러나 함수형 프로그래밍에서는 순수하게 함수에 전달되고 그에 의해 처리 된 결과만이 중요하게 됩니다. 

즉, 어떤 순간을 사진으로 캡쳐해서 그에 의해서만 작동한다고 이해하면 될 것 같습니다.

 

그렇게 되면, 어떤 많은 처리가 필요할 때 서로에게 독립적으로 작동하기 때문에 부작용이 없어지게 되겠죠⁉️

 

함수형 프로그래밍 패러다임을 차용하면 병렬처리에서 큰 강점이 있다.

 

여기서 클로저도 이에 해당하게 될 건데, 함수형 프로그래밍에서 함수를 바로 일급 객체(first-class citizen)으로 취급한다는 것입니다.

 

 

일급 객체(first-class citizen)란?

  • 전달인자로 전달할 수 있다.
  • 동적 프로퍼티에 할당이 가능하다.
  • 변수나 데이터 구조 안에 담을 수 있다.
  • 반환 값으로 사용할 수 있다.
  • 할당할 때 사용된 이름과 관계없이 고유한 객체로 구별할 수 있다.

 

바로 이런 특징을 가진 것이 일급 객체(first-class citizen)라고 이해하시면 될 것 같습니다.

 

클로저도 일급 객체입니다.

 

함수형 프로그래밍에 대해 간단하게 알아보았고 이제 클로저에 대해 알아보도록 하겠습니다.

 

 

클로저란?

어떤 기능을 하는 코드를 하나의 블럭으로 모아놓은 것을 말합니다.

여러분들이 자주 보는 함수도 클로저의 한 형태라고 할 수 있어요.

 

클로저의 큰 특징은 클로저가 선언된 위치가 있을 것입니다. 

그 위치에서 어떤 상태를 캡쳐하고 참조를 얻을 수 있다는 것입니다.

 

이 말이 지금은 잘 이해되지 않을 것입니다. 밑의 예제에서 조금있다 더 자세히 살펴볼게요..‼️

 

그렇다면 우선 클로저의 기본 형태를 살펴볼게요.

{ (매개변수) -> 반환타입 in
    코드 블럭
}

이것이 클로저를 사용하는 기본형태로 이해해주시면 될 것 같아요.

 

이제 Apple에서 제공해주는 예시를 가지고 조금 더 자세하게 살펴볼게요.

// 이름 변수
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

// 함수 형태
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

let reversedNames = names(by: backward)

// 클로저 사용
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

 

이렇게 클로저의 기본 형태를 사용해서 Collection Type의 sorted(by:)에 값을 넘기는 작업을 해보았죠⁉️

그렇다면 조금 더 간결하게 표현할 수는 없을까요?

 

클로저는 문맥을 이용해서 좀 더 간결하게 표현이 가능해요.

  • 반환 타입 유추, 매개변수 타입 유추
  • 매개변수 생략
  • return 키워드 생략

 

각각의 경우를 살펴볼게요

 

✔️ 반환 타입 유추, 매개변수 타입 유추

// 매개변수의 타입, 반환 타입을 생략한 것을 알 수 있죠?
let reversedNames = names.sorted(by: { (s1, s2) in
    return s1 > s2
})

 

✔️ 매개변수 생략

// $0, $1, $2, $3, .... 등을 이용해 매개변수를 선언할 필요도 없습니다.
// 이렇게 되면 실행 코드와 매개변수 구분을 위해 사용했던 in을 사용할 필요도 없습니다.
let reversedNames = names.sorted(by: {
    return $0 > $1
})

 

✔️return 키워드 생략

// return 키워드 조차 문맥을 통해 마지막 줄이 return으로 유추할 수 있습니다.
let reversedNames = names.sorted(by: { $0 > $1 })

이렇듯 클로저는 표현을 여러형식으로 간략화 시킬 수 있어요..‼️

하지만 아직 더 다르게 표현이 가능하다는거 마지막 하나 더 다루어 볼게요..

 

클로저가 함수의 마지막 매개변수로 전달할 경우 다음과 같이 표현이 가능해요.

이름은 후행클로저로 알고 다루면 됩니다.

 

 

✔️후행 클로저

// (by: ) 소괄호가 사라진 거 확인가능하시죠?!
let reversedNames = names.sorted { $0 > $1 }

이렇듯 마지막 매개변수로 클로저가 전달될 경우 소괄호를 생략하고 마지막에 클로저의 형태로 작성이 가능합니다.

더 가독성이 높아졌죠⁉️


지금까지는 클로저의 기본형태와 표현방법에 대해 알아봤어요.

아까 클로저를 배우기 시작할 때, '현재의 상태를 캡쳐'한다는 말 기억하시나요⁉️

 

여기에 대해 예시를 통해 더 자세히 살펴볼게요.

 

✔️현재의 상태를 캡쳐

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

이렇게 makeIncrementer(forIncrement:)라는 ()->Int 클로저를 반환하는 함수가 있습니다.

지속적으로 증가할 양을 매개변수로 넣어서 보내주면 현재의 runningTotal 값을 캡쳐해서 +amount 해주는 클로저를 반환하는 함수입니다.

 

이를 간단히 어떻게 활용하는지 보고 캡쳐한다는 의미가 어떻게 전달되는지 확인해볼게요.

let incrementByTwo: (() -> Int) = makeIncrementer(forceIncrement: 2)

let first = incrementByTwo()	// 2
let second = incrementByTwo()	// 4
let third = incrementByTwo()	// 6

처음 (() -> Int)의 참조를 생성할 때, runningTotal의 값을 캡쳐해서 생성되었습니다.

 

그렇다면 클로저를 호출할 때마다 2씩 증가되는거 확인이 가능하시죠⁉️

 

이를 여러 개의 클로저를 생성하면 어떻게 될까요?

// (() -> Int)의 레퍼런스 3개 생성
let incrementByTwo = makeIncrementer(forceIncrement: 2)
let incrementByTwo2 = makeIncrementer(forceIncrement: 2)
let incrementByTen = makeIncrementer(forceIncrement: 10)

// 각각의 레퍼런스에 대해 실행
let first = incrementByTwo()		// 2
let second = incrementByTwo()		// 4
let third = incrementByTwo()		// 6

let first2 = incrementByTwo2()		// 2
let second2 = incrementByTwo2()		// 4
let third2 = incrementByTwo2()		// 6

let first3 = incrementByTen()		// 10
let second3 = incrementByTen()		// 20
let third3 = incrementByTen()		// 30

 

처음 3개의 (() -> Int)을 생성하고 각각에 대해 3번씩 실행하고 실행결과를 확인한 값입니다.

 

예상한 값과 같은가요⁉️

 

이렇게 나오는 것이 바로 클로저의 참조를 생성할 때, 값을 캡쳐하기 때문에 다음과 같은 결과가 나오는 것이랍니다.

즉, 클로저가 생성될 때 runningTotal의 값은 0이기 때문에 모두 이를 기준으로 실행됩니다.

 

이제 값을 캡쳐한다는 의미가 감이 오시죠.

 

 

 

이렇듯 클로저를 활용하면 좀 더 함수형 프로그래밍 패러다임을 차용한 SWIFT 언어의 특징을 좀 더 살려서 코딩할 수 있을 것 같습니다.

 

다음은 클로저를 좀 더 활용하는 방법을 포스팅해보도록 할게요~~

반응형