타이머를 만들다가 (action: () -> Void)
라는 코드를 작성하게 되었는데 저게 뭐지 ? 싶었다. 알아보니 (action: () -> Void)
에서 Void
는 반환 값이 없다는 것을 의미한다고 하는데,
쉽게 말하면 " 아무 값도 돌려주지 않는 함수 (클로저)" 라는 뜻이라고 한다.
그런데.. 아무값도 돌려주지 않는 함수라는 뜻이라고 하지만 프린트 출력이 됐다면 값이 나온거나 마찬가지 아닌가?? 싶더라.
하지만 저 위에 예시에서 출력된 결과와 반환값을 혼동하지 않도록 하는 것이 중요하다고 한다.
반환값 (return value)
함수나 클로저가 작업을 마치고 호출한 쪽으로 돌려주는 값.
이 반환값은 나중에 계산된 결과를 다시 사용할 때 필요하게 되는데, 반환값이 있는 함수는 return
키워드를 사용해서 값을 반환하고 클로저도 마찬가지로 값을 반환할 수 있는 것이다.
출력 (print statement)
print()
함수는 값을 콘솔에 출력하는 것인데 출력은 단순히 값을 화면에 보여주는 것일 뿐, 실제로 호출한 함수나 클로저로 돌려주는 값은 아니다.
func test(action: () -> Void) {
action()
}
test {
print("그냥 출력할거야")
}
이 예시를 다시 보자면 여기서 action()
은 () -> Void
타입의 클로저인데, 이 클로저는 반환값이 없다. 이 말은 어떤 값을 반환하지 않고 실행을 마친다는 의미인 것이다.
print("그냥 출력할거야")
는 콘솔에 메세지를 출력하는 함수일 뿐이고 반환값이 아니라는 점을 이해하는게 중요하다. print()
함수 자체는 화면에 메세지를 보여주는 역할을 하지만 그 자체는 값을 돌려주지 않기에 이 클로저는 여전히 Void
타입으로 아무값도 반환하지 않는 클로저로 정의되는 것이다.
일단 내가 잘못 이해하고 있었던 부분은 좀 풀리게 되었다.
이제 본격적으로 클로저의 대해 알아보자.
클로저를 알아야 하는 이유는 크게 세 가지 정도로 요약할 수 있다. 코드 간결화
, 비동기 작업 처리
, 함수형 프로그래밍 패러다임
때문.
함수..프로..패러다임 ? 무슨 말이지.. 하나하나 같이 살펴보도록 하자.
클로저는 함수를 짧고 간결하게 작성할 수 있게 해줄 수 있다. 특히 Swift에서는 클로저를 함수의 매개변수로 전달할 수 있어 코드의 중복을 줄이고 간단한 작업을 더 직관적으로 처리할 수 있다.
여기서는 map
함수에 클로저를 전달하여 배열의 각 요소를 두배로 만든다. 짚고 넘어가자면 map은 배열이나 컬렉션의 각 요소에 대해 동일한 작업을 수행하고 그 결과를 새로운 배열로 변환하는 함수다. 쉽게 말하면 배열의 각 요소를 변환하는데 사용되는데,
예시처럼 map
을 사용하는 이유는 배열의 모든 요소에 동일한 연산을 작용하고 그 결과를 새로운 배열에 저장하기 위해이다. 예로 들자면 주어진 배열에 각 숫자마다 2를 곱하고 싶을 때 map을 사용하면 각 배열에 있는 모든 숫자에 2를 곱한 값을 자동으로 계산해 새로운 배열로 만들어준다.
아무튼 이러한 클로저는 매개변수와 반환 타입을 생략하고 $0
이라는 축약된 형태로 첫 번째 매개 변수를 사용하는데 이렇게 하면 위에 코드처럼 간결해지고 가독성을 높일 수 있다.
비동기 작업(asynchronous task)
은 작업이 즉시 완료되지 않고 시간이 걸리는 작업을 처리할 때 주로 사용되는데, 대표적으로 네트워크 요청, 파일 다운로드, 타이머 등이 비동기 작업에 해당한다.
그렇게 여러 비동기 작업에서 작업이 완료된 후에 무엇을 할지 정의하는 코드를 전달하는 경우가 많은데, 바로 여기서 클로저가 유용하게 쓰이는 것이다. 클로저는 나중에 실행할 코드를 캡슐화해서 전달할 수 있기 때문이다. 예를 들어 네트워크 요청이 완료된 후 데이터를 처리하는 코드가 필요하다면 클로저를 사용해 이를 처리할 수 있다.
함수형 프로그래밍 패러다임은 함수를 중심으로 문제를 해결하는 프로그래밍 방식이라고 한다. 이 패러다임에서는 프로그램을 상태 변경이나 명령어 실행보다 수학적 함수의 개념에 기반해서 설계하고 구현을 하는데, 프로그램의 구성요소를 순수함수로 구성하여 문제를 해결하는 방식을 말하는 것이라고 한다.
* 순수함수란 ?
함수형 프로그래밍에서 함수는 동일한 입력이 주어졌을 때 항상 동일한 출력을 반환해야 한다. 즉, 외부 상태나 전역 변수를 참조하거나 수정하지 않는다. 이러한 함수를 순수 함수라고 한다.
위 코드에서 map
과 filter
는 고차 함수다. numbers
배열은 변경되지 않고 새로운 배열이 생성된 것이고 이러한 방식이 함수형 프로그래밍의 전형적인 예시라고 볼 수 있다.
함수형 프로그래밍은 함수 중심으로 설계하고, 순수 함수, 불변성, 고차 함수 등의 개념을 통해 명령형 프로그래밍과 구별된다. 이 패러다임은 코드의 가독성, 유지보수성, 테스트 용이성을 높이고, 특히 병렬 처리를 할 때 큰 이점을 제공한다.
클로저는 함수와 마찬가지로 입력 매개변수를 받을 수 있다. 클로저를 선언할 때 입력 매개변수를 명시해주고 클로저 호출 시 그 값을 전달할 수 있다는 것인데 예시를 보자.
위 코드에서 클로저는 multiplyClosure
이다. 클로저는 코드 블록으로 특정 작업을 수행하며 입력값을 받아 출력값을 반환한다. 클로저는 함수와 유사하게 동작하지만, 함수 이름 없이 익명으로 정의할 수 있다.
음? 함수 이름 없이 익명으로 정의할 수 있다는게 무슨 말이지? 이미 multiplyClosure
라는 이름이 있는데.... 라고 생각했다.
multiplyClosure
는 이름이 있지만, 클로저 자체는 여전히 "익명 함수"
로 정의된다는 의미라고 한다.
함수는 이름이 필수적으로 붙는 구조다. 예를 들면 아래 코드를 보자.
func multiply(a: Int, b: Int) -> Int {
return a * b
}
여기서 multiply
라는 이름의 함수가 명확하게 정의되어 있는걸 볼 수 있을것이다.
클로저는 코드 블록을 이름 없이 정의할 수 있는 구조라서 클로저를 변수에 할당해 이름을 붙일 수 있지만, 그 클로저 블록 자체는 이름이 없다. 클로저가 다른 변수나 상수에 담길 때 비로소 그 변수 이름으로 접근할 수 있는 것이다.
let multiplyClosure = { (a: Int, b: Int) -> Int in
return a * b
}
위 예시를 보면 multiplyClosure
는 상수의 이름일 뿐이고, 그 안에 담긴 코드 블록은 익명 함수다. 클로저의 본문 부분에는 함수 이름이 없다.
다시 말하면 multiplyClosure
라는 이름은 변수의 이름이다. 클로저 자체는 이름 없는 익명 함수이고, 그 함수는 변수를 통해 접근하고 호출할 수 있다.
따라서 클로저는 코드 블록 자체가 이름 없이 정의된다는 점에서 "익명 함수
"로 간주된다는 것이다.
클로저는 함수처럼 반환값을 가질 수 있다. 크로저 내부에서 계산한 값을 return
키워드를 통해 반환하거나 간결한 표현으로 반환할 수 있다는 것이다.
구조적으로 봤을 때 클로저는 입력 매개변수와 반환 타입을 명시하고, in
키워드 뒤에 실행할 코드를 적었다. 이 클로저는 String
타입의 매개변수 name
을 받아 환영 메시지를 반환해주는데, return
키워드를 통해 반환 값을 알려주고 있다.
반환값
은 보통 간단한 계산이나 변환을 수행한 결과이기 때문에 그 자체로도 이해하기가 좀 더 쉽긴 하다.
클로저는 함수의 매개변수로 전달될 수 있는데, 이것이 클로저의 주요 활용 방식 중 하나이기도 하다. 클로저를 함수의 인자로 넘겨 해당 함수가 클로저를 실행하도록 만들수가 있다.
여기서 performOperation
함수는 두 정수를 매개변수로 받고 세 번째 매개변수로 클로저를 받는다. 이 클로저는 두 정수를 받아서 연산을 수행하는 코드 블록이다. 클로저는 함수 호출 시 인자로 전달되며, 함수 내부에서 실행된다.
이 예시는 클로저를 함수의 매개변수로 넘기는 것을 보여준다.
마지막으로 다양한 길이로 줄여보는 예제를 통해서 이해해보는 걸로 마무리를 해보자.
긴 경우에서는 모든 매개변수와 반환형을 명시적으로 작성한 클로저를 보여줬다.
중간 경우는 매개변수의 타입을 생략하고, 변환형만 명시한 클로저로 줄여봤다.
짧을 경우는 매개변수의 타입과 반환형을 모두 생략하고 $0, $1 등 단축표현을 사용한 클로저로 간결하게 표현했다.
오늘은 클로저가 왜 중요한지를 이해하고자 공부를 해봤는데 주요 내용을 요약하자면,
공부하면서 어려웠던 점은 클로저의 여러유형과 이들간의 차이를 이해하는 것이 시간이 많이 걸렸던 듯 하다.
그리고 비동기 작업 처리와 관련된 부분도 복잡하게 느끼기도 했다.
그래도 공부하면서 클로저가 비동기 작업을 처리할 때 아주 유용하다는 것과 함수형 프로그래밍 패러다임에서도 중요한 역할을 한다는 점을 알게됐고, 클로저를 활용하면 코드의 재사용성과 간결성까지 높일 수 있다는 걸 배웠다.
클로저를 매개변수로 받아 다양한 작업을 수행하는 함수도 만들어보고 비동기 네트워크 요청을 처리하는 코드도 작성해보고 싶고.. 잘 사용한다면 코드를 작성할 때 유연하게 대처하는 능력을 기를수도 있을테니, 배우면서 어려운 부분이 많았지만 공부하면서 해봤던 실습예제들을 통해 자연스럽게 익혀보고 싶다.
클로줠... 쉽지 않군요