[swift] Closures, 클로저에 대해서

이은수, Lee EunSoo·2024년 9월 5일
0

Swift Basic

목록 보기
6/24
post-thumbnail

들어가기에 앞서...

스위프트의 클로저는 정말 많은곳에서 사용된다( swiftUI 에서도.. )

그리고 넓은 범위에서 보면 이전에 설명한 함수도 클로저에 포함된다.

하지만 여기서 말하고자 하는 클로저는 다른 언어들에서 익명함수로 사용되는 형태를 말하고자 한다.
(일반적으로 클로저라 하면 이러한 익명함수의 클로저를 말한다.)

정의

클로저는 명명된 함수 생성없이 실행되는 코드 그룹

애플 공식문서의 클로저 페이지에서 제일 먼저 나오는 문장이다.
이름없는 함수라고 생각하면 된다.

함수와의 관계

앞서 함수도 클로저의 일종이라고 말했는데 다음을 살펴보자

함수 (Functions) 에서 소개한 전역과 중첩 함수는 클로저의 특별한 케이스 입니다. 클로저는 3가지 형태 중 하나를 취합니다:

  • 전역 함수는 이름을 가지고 어떠한 값도 캡처하지 않는 클로저입니다.
  • 중첩 함수는 이름을 가지고 둘러싼 함수로 부터 값을 캡처할 수 있는 클로저입니다.
  • 클로저 표현식은 주변 컨텍스트에서 값을 캡처할 수 있는 경량 구문으로 작성된 이름이 없는 클로저입니다.

다음과 같이 함수를 클로저로 설명 할 수 있다.

클로저의 표현

클로저의 기본적인 표현방법


{ (파라메터 이름: 파라메터 타입) -> 반환타입 in
	실행할 코드
}

함수랑 비슷한 양식이다 다른점이라곤 이름이 없고 파라메터와 반환값이 { }괄호 안으로 들어온다.

그리고 in 이후에 사용할 코드들이 들어온다 (in키워드가 코드 시작 기점)

다음으로 예시를 보자

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)

위 코드는 이름이 저장된 문자열 배열을 sorted()를 사용해서 정렬하는 예제이다
sorted()는 (String, String)->Bool 타입의 함수를 파라메터로 넘겨받아 그 함수를 기준으로 정렬해서 반환하는 메서드이다

여기선 func backward()라는 함수를 사용한다.

backward함수는 배열의 정렬을 위해서만 사용된다. 이렇게 부분적인 곳에서 함수를 사용해야하는 경우 클로저를 사용한다.

//클로저로 변환

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

backward()함수없이 sorted()함수 호출하는곳에서 클로저를 바로 만들었다, 이러한 상황에 사용하기 위해 쓰는것이 클로저이다.

클로저의 생략

개인적으로 클로저의 꽃은 생략이라고 감히 생각한다.

여러줄의 복잡한 클로저라면 많은 생략이 불가능 하지만 앞서 예시로 든 코드처럼 간단하게 return 한줄로 이루어진 형태 (인라인 클로저)의 코드의 경우 많은부분을 swift에게 추론으로 맡기고 생략이 가능하다

컨텍스트 타입 추론

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

일단 아까의 클로저 입력를 한줄로 줄일 수 있으니 한줄로 만든다(이해를 돕기위해서...)

//파라메터 타입 & 반환타입 생략

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

swift는 클로저에 들어오는 데이터의 타입과 return 문뒤에 존재하는 데이터의 타입을 가지고 추론해서 파라메터의 타입과 반환값 타입을 생략할 수 있게 도와준다.

단일 표현 클로저의 암시적 변환

말이 어려운데 쉽게 말하면 클로저의 표현이 하나인 경우 (예제는 >, 비교연산 하나만 존재) 이를 단일 표현 클로저라고 하는데

단일 표현 클로저의 경우 return을 생략할 수 있다.

//단일표현 클로저의 return 생략

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

인수 이름 생략

앞서말한 한줄짜리 클로저의 경우 인라인 클로저라고 부르는데 인라인 클로저의 경우 swift에서 인수이름을 $0, $1, $2로 바꾸는것을 지원한다.

reversedNames = names.sorted(by: { $0 > $1 } )

연산자 생략

여기서 더 생략이 가능하다!

앞서 설명했듯이 sorted() 함수는 (String, String)->Bool
타입의 클로저를 받는다

그리고 <, =, > 같은 비교연산자도 동일한 형태(타입 )이다

그래서 여기에 아까 생략된 데이터 마저도 생략하고 연산자만 입력하는것이 가능하다

"연산자가 (String, String)->Bool의 역할 수행"

//최종 형태
reversedNames = names.sorted(by: >)

다만 이 경우 비교 연산자sorted()가 필요로 하는 클로저의 타입이 동일해서 생략이 가능했던것이지 모든 경우에서 이렇게 생략이 가능하지는 않다는것을 알아 두자!

후행 클로저

var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in 
	return s1 > s2 
} )

소괄호안에 중괄호가 오다니 이 코드를 보면 조금 불편하지 않은가?

모름지기 코드에서 소괄호는 중괄호안에 가야 하거늘 좀 생소한 형태이다

애플의 swift 엔지니어들도 비슷한 생각을 했나보다

swift에서 클로저를 인수로 받는 경우에 마지막 인수가 클로저인 경우 소괄호( ) 밖에 클로저를 작성하게 해준다 이것을 후행클로저라고 한다.

var reversedNames = names.sorted(by: ) { (_ s1: String, _ s2 : String) -> Bool in
        return s1 > s2
}

그리고 후행 클로저 구문을 사용할 때 함수 호출의 일부로 첫번째 클로저 인수 라벨은 생략이 가능하다!

( by: 가 생략이 가능하다는 말이다.)

var reversedNames = names.sorted() { (_ s1: String, _ s2 : String) -> Bool in
        return s1 > s2
}

게다가 여기에 후행 클로저의 표현식이 함수와 메서드의 유일한 인수일 경우
( = 인수가 클로저 하나인 경우 )

함수를 호출할 때 함수 또는 메서드 이름 뒤에 소괄호 ( ) 를 작성하지 않아도 된다!

var reversedNames = names.sorted { (_ s1: String, _ s2 : String) -> Bool in
        return s1 > s2
}

여기서 앞서 생략했던 클로저 구문을 적용하면 다음과 같다.

var reversedNames = names.sorted { $0 > $1 }

//아쉽게도 이 정도로 줄이는건 불가능하다. 위 코드가 최대이다.
var reversedNames = names.sorted { > } // error
profile
iOS 개발자 취준생, 천 리 길도 한 걸음부터

0개의 댓글