Swift를 공부하는 과정 중 많은 고비가 있습니다.
그중 하나는 단연 클로저라 할 수 있죠.
클로저가 뭐냐고요?
함수입니다.
그럼
.
.
.
.
.
...사실 이 정도 설명으로 끝날 주제였다면 시작도 하지 않았겠죠.
설명할 게 좀 됩니다.
그러니 질문을 하나 던져봅시다.
절대 설명하기 귀찮아서가 아니라,
모르는 것을 아는 것이 배움의 시작 이어서입니다.
(누군가 했던 말 같은데 누군지는 기억이 안 나네요 아시는 분은 댓글부탁드려요)
그럼,
가장 큰 이유는 $0, $1 가 갑툭튀하고,
기존에 쓰지 않던 문법들이 등장해서...라고 저는 생각합니다.
마치 숫자만 있던 초등학교 시간의 아름다운 수학시간이 지나고,
중, 고등학교에서 점점 문자와 기호만 그득한
수학(숫자 안 나옴)을 보는 것과 같은 느낌이랄까요?
그니까,
func deepThought() {
print("42")
}
이런 걸 보다가 갑자기,
이런 걸 봐버리니 정신이나가버릴것같은것이죠.
하지만 두려워 마십시오.
Swift 뉴비인 저도 저렇게 어렵게 보이게 쓰는 게 가능하니
여러분도 조만간 어렵잖게 저렇게 어렵게 보이게 쓸 수 있습니다.
Swift를 공부하며 클로저까지 왔을 시점이면,
타입 선언(Type Annotation)과 타입 추론(Type Inference),
그리고 함수 파라미터에 대해서는 알고 계시리라 간주하고 설명하겠습니다.
*모르시는 분들을 위한 설명은 추후에 따로 올려보겠습니다.
먼저 함수는 클로저입니다.
정확히는 우리들이 헷갈려하는 클로저는 "익명 클로저"라는 이름이 없는 함수이죠.
이름 있는 클로저인 함수는 잘 써보셨을 겁니다.
아니라고요...? 그거는... 예... 추후 함수에 대해 글을 써보겠습니다...!
여튼 지금 이 글에서 말하려는 헷갈리는 클로저는 익명 클로저입니다.
아래 코드에서 함수와 익명 클로저를 함께 보시죠:
5번 줄과 7번 줄에서 볼 수 있는 나는상수다에 할당된 { } <- 요 녀석이 클로저입니다.
위에 있는 "나는함수다" 함수와 비슷한 기능을 하죠.
함수와 익명 클로저의 공통점이 좀 보이시나요?
함수는 함수명 옆에 리턴 타입과 파라미터들이 적혀있는 반면,
익명인 클로저는 { 중괄호 } 안에 리턴 타입과 파라미터들을 작성하게 됩니다. 실행문과는 in 키워드로 분리가 되고요!
그러니까 정리하자면 이렇습니다:
파라미터가 있는 다른 예시로도 한 번 볼까요?
둘이 똑같은 기능을 하는 것을 볼 수 있습니다.
모양은 다르지만요.
그래서 결론은?
{ }가 클로저다.
이게 내 결론이다.
근데 여기서 의문을 가지실 수 있습니다.
맞습니다.
클로저가 헷갈리는 이유가 여기서 발생하죠.
풀 버전에서 생략되는 게 많습니다.
마치 마블 영화 요약 설명을 *튜브에서 찾아보면,
뭔가 (중략) 되는 부분이 많은 것처럼요.
다른 예시로 설명해 보겠습니다:
이번엔 클로저와 상수를 같이 적어보았습니다.
같이 적힌 상수 선언과 클로저의 공통점이 보이시나요?
공통점이 잘 안 보이는군요. 표시를 해보겠습니다.
좀 감이 오시나요? 오지 않아도 괜찮습니다.
제가 친절하게 설명해 드립니다.
위의 사진에서 표시된 부분은 타입 선언입니다.
각각의 영역이 알려주는 내용을 문자로 풀어서 써보겠습니다:
is42는 - "Int를 받아 Bool을 리턴하는 클로저"가 할당된 상수이다
fourtyTwo는 - "Int 타입"의 상수이다
Swift Playground에서 변수 좀 할당해서 놀아보신 분들이라면,
어렵잖게 이해가 가능하실 겁니다.
근데 아시다시피, Swift에는 타입 추론이라는 기능이 있습니다.
주어진 값에 따라 데이터 타입을 Swift가 추론할 수 있는 기능이죠.
그래서 변수 또는 상수를 생성할 때 타입 선언을 생략하는 게 가능합니다.
(단, 주어지는 값에서 추론이 가능해야 합니다.)
어? 근데 보면, is42는 뒤에 (num: Int) -> Bool이라고 함수 인풋값과 리턴값이 명시되어 있네요?
그리고 fourtyTwo는 정수 42가 할당되어 있구요~!
Swift가 타입을 추론할 수 있으니,
저기 위에 표시한 영역을 썰어서 날려버려도 된다는 말이죠.
금방 다듬어서 갖고 올 테니 기다려주셔요.
(서걱서걱)
여기 나왔습니다:
클로저 구문 내에서 선언된 타입도 생략이 가능합니다.
왜냐?
뒤에 있는 return num == 42에서 타입 유추가 가능하기 때문이죠.
네? 무슨 소리냐고요?
return num == 42는 짧아 보이지만 자그마치 3개의 형식이 합쳐진 콤비네이션,
Int값 42, Boolean 연산자 ==, 그리고 Int 인풋값 num.
Swift는 여기서 이것들을 보고는,
"음! Int값인 42와 Comparable**(==)하려면,
또 다른 Int인 num을 받아야겠고,
비교한 결과는 Bool이니, 리턴 타입은 Bool이겠구만!"
이라고 생각*하며 센스 있게 받아들이기 때문이죠.
* 실제로 스위프트가 이런 말투로 생각하거나 작동하는지는 모릅니다. 글쓴놈의 상상일 뿐입니다.
** 여기서 Comparable은 Swift의 프로토콜 중 Comparable을 일컫는 것입니다. 관련해선 나중에 더 자세히 써보도록 하겠습니다.
네 여튼 Swift가 알아서 타입 유추를 할 수 있으니 생략 가능하다는 말입니다.
더 썰어오도록 하겠습니다
(서걱서걱)
만약 클로저가 반환 값을 갖는 클로저이고,
클로저 내부의 실행문이 단 한 줄이라면,
암시적으로 그 실행문을 반환 값으로 사용할 수 있습니다.
스위프트 프로그래밍 3판에서 발췌
예시와 같이 실행문이 한 줄이고, 그게 리턴값이면
return을 날려버려도 된다는 소리입니다.
(서걱)
바로 짧은 인수 이름(Shorthand Argument Name)이라는 것을 사용한다면 말이죠.
거 왜 맨날 map, filter, reduce, sorted 같은 메서드 사용하다 보면 항상 보이는 거 있잖아요.
0달라 1달라 2달라 3달라...
가 아니고...
$0, $1, $2... 이거요...
이 짧은 인수 이름을 사용하면,
클로저의 머리와 몸통을 분리하고 있던 목뼈 개념의 in 키워드도 생략이 가능합니다.
왜?라고 물어보시면... Swift가 그렇게 태어났어요...
(BGM: Born This Way - Lady Gaga)
그야말로 몸뚱이만 남기고 다 없애버린 것이지요.
관공 어찌하여 목만 오셨소... 가 아니라,
가 되겠네요.
이렇게 말이죠.
축약하는 과정을 정리해 보자면 아래와 같습니다:
// 기본 문법
let is42: (Int) -> Bool = { (num: Int) -> Bool in
return num == 42
}
// 상수의 타입 선언을 생략하기 (타입 추론)
let is42 = { (num: Int) -> Bool in
return num == 42
}
// 클로저의 타입 선언 생략하기 (입력, 반환되는 값에서 추론)
let is42 = { num in
return num == 42
}
// return문 생략
let is42 = { num in
num == 42
}
// Shorthand Argument를 사용하여 in 구문 생략하기
let is42 = { $0 == 42 }
어느 정도... 이해가 되셨나요?
다음 시간에는 자주 쓰이는 메서드들로 예시를 작성해 보도록 하겠습니다.
그럼
이 포스트는 아래 자료에 기반하여 작성되었습니다:
Swift 한국어 - 클로저 (Closures) [링크]
스위프트 프로그래밍 3판 - 야곰
모야 글 왜케 잘 써요,,, 재밌어서 훅훅 읽히네 클로저 저도 어려워서 맨날 검색하는데ㅜ 잘 보고 갑니둥