[Swift] 클로저 알아보기

·2024년 7월 2일
0

Swift 문법

목록 보기
9/16

클로저

Group code that executes together, without creating a named function.
이름 없이 생성되어 함께 실행되는 코드 블럭
일정 기능을 하는 코드를 하나의 블럭으로 모아놓은 그룹


Swift는 명확함과 의도를 잃지 않고 보여줌과 동시에
짧은 형태로 클로저를 작성하기 위해서 방법을 정해 뒀음!

정말 많이 쓰는 sorted(by:) 메서드를 통해 해당 내용들을 이해해 보자.


let a = [5, 3, 1, 4, 2]

a 배열을 오름차순으로 정렬할 때 sorted(by:) 메서드를 사용하려고 한다.


이때 sorted(by:) 메서드를 살펴 보면,

func sorted(by areInIncreasingOrder: (Self.Element, Self.Element) throws -> Bool) rethrows -> [Self.Element]

areInIncreasingOrder - 첫 번째 파라미터가 두 번째 파라미터보다 먼저 정렬되어야 한다면 true를 반환하는 구문


sorted(by:) 는 인자로 (Self.Element, Self.Element) throws -> Bool
즉, 이 예제에서 sorted(by:)(Int, Int) -> Bool 형태의 파라미터를 필요로 한다.


일단 (Int, Int) -> Bool 는 함수 형태이니, 함수를 따로 만들어 파라미터로 넣어줄 수 있을 것이다.

func ascending(_ i1: Int, _ i2: Int) -> Bool {
//    if i1 < i2 {
//        return true
//    } else {
//        return false
//    }
    return i1 < i2
}

let sortedA = a.sorted(by: ascending)
print(sortedA) // [1, 2, 3, 4, 5]

직접 써 보면,
return i1 < i2 하나를 위해 함수를 따로 만들고, 파라미터로 넣어주는 작업은 배보다 배꼽이 큰 느낌이다.

이 예제는 정렬 클로저를 인라인으로 작성했을 때 훨씬 효율적일 수 있다.

이때 인라인 클로저는 함수로 따로 정의된 형태가 아니라, 파라미터로 들어가는 클로저를 의미하게 된다.


클로저 표현식

{ (<#parameters#>) -> <#return type#> in
   <#statements#>
}

일반적으로는 다음과 같은 형태이다.

  • in 키워드는 클로저의 파라미터와 반환 타입의 정의가 끝났을 때 적고, 클로저의 본문이 시작됨을 나타낸다.

let sortedA = a.sorted(by: { (i1: Int, i2: Int) -> Bool in
    return i1 < i2
})
print(sortedA) // [1, 2, 3, 4, 5]

위의 ascending(::) 함수를 클로저로 표현하면 위와 같다.

이와 같이 클로저에 대한 파라미터명, 파라미터 타입, 반환 타입을 모두 적어줘도 되지만,
더 간결하게 표현할 수 있는 다양한 방식이 존재한다.


Context로 타입 추론이 가능

sorted(by:) 메서드처럼 클로저가 파라미터로 전달되는 경우,
Swift는 파라미터 타입과 반환 타입을 유추할 수 있다.

let a = [5, 3, 1, 4, 2]

우리는 a 배열에서 sorted(by:) 를 호출하므로, 클로저는 (Int, Int) -> Bool 형태임을 유추할 수 있는 것이다. 따라서 해당 정보를 적어주지 않아도 됨.


let sortedA = a.sorted(by: { i1, i2 in return i1 < i2 })
print(sortedA) // [1, 2, 3, 4, 5]

단일 표현 클로저의 return 생략

return i1 < i2 와 같은 단일 표현 클로저는 return 을 생략할 수 있다.
단일 표현 클로저는 본문이 return i1 < i2 만 필요할 때와 같은 경우를 의미한다.
다른 코드가 들어가야 한다면 return은 생략할 수 없다.

let sortedA = a.sorted(by: { i1, i2 in i1 < i2 })
print(sortedA) // [1, 2, 3, 4, 5]

축약된 파라미터 이름 제공

클로저의 파라미터 명을 차례대로 $0 , $1 , $2 … 으로 제공함.
파라미터 명, 파라미터 타입과 반환 타입을 명시해 주지 않으므로 in 키워드도 생략 가능

let sortedA = a.sorted(by: { $0 < $1 })
print(sortedA) // [1, 2, 3, 4, 5]

따라서 파라미터 이름을 명시해 줄 필요 없고 제공되는 축약된 이름을 사용할 수 있음.


연산자 메서드

< / > 등과 같은 비교 연산자를 사용하여 축약할 수 있음.

let sortedA = a.sorted(by: <)
print(sortedA) // [1, 2, 3, 4, 5]

이런 식으로 클로저를 간결하게 표현할 수 있는 방법이 다양하지만,
개인적으로는 파라미터 명을 명시해 주는 게 이해하기 편한 경우도 있어서 적절하게 사용하면 좋을 것 같다.


후행 클로저(Trailing Closure)

함수의 마지막 파라미터로 클로저를 작성할 때,
클로저 표현식이 길 때는 후행 클로저를 사용하면 유용하다.


  1. 후행 클로저는 함수의 소괄호 다음에 작성한다.
    후행 클로저로 작성 시에는 함수의 파라미터 이름을 적어주지 않아도 된다.
let sortedA = a.sorted() { $0 < $1 }
print(sortedA) // [1, 2, 3, 4, 5]

  1. 후행 클로저로 표현된 클로저가 함수의 유일한 파라미터일 경우, 소괄호도 생략이 가능하다.
let sortedA = a.sorted { $0 < $1 }
print(sortedA) // [1, 2, 3, 4, 5]

  1. 함수는 여러 후행 클로저를 파라미터로 포함할 수 있다.

나는 요 부분이 많이 헷갈렸는데,
후행 클로저는 마지막 파라미터로 오는 클로저를 의미하는 게 아니라, 하나의 표현 방법이라는 점을 이해하면 된다.


여러 후행 클로저를 파라미터로 포함하고 싶을 때는
첫 번째 후행 클로저의 파라미터 이름은 생략하고, 남은 후행 클로저들의 파라미터 이름은 남겨둔다.

2번과 같이 앞에 명시해 줄 파라미터가 없으므로 소괄호도 생략할 수 있다.

func test(first: (Int, Int) -> Int, 
          second: (Int, Int) -> Int,
          third: (Int, Int) -> Int) {
    // ...
}

이렇게 작성할 수 있도록 Xcode에서 자동으로 바꿔주기도 한다.


escaping closures

escaping 클로저는 함수에 인수로 클로저가 전달될 경우,
함수의 실행이 종료된 후에 호출된다.

escaping 클로저임을 나타내기 위해 @escaping 키워드를 붙여 준다.

자세한 포스팅
https://velog.io/@minnnidev/Swift-escaping-closure


auto closures

auto closure은 함수에 전달되는 인자를 래핑하여 자동으로 클로저가 생성되게끔 해 준다.
auto closure을 통해 클로저의 중괄호를 생략한 일반 표현식을 적을 수 있음!

단, 클로저가 파라미터를 가지지 않을 경우에만 가능.
auto closure에 escaping 속성을 주고 싶다면, 둘다 사용할 수 있음.

사용법

func test(_ closure: () -> Void) {
    closure()
}

test({ print("안뇽1") })
test {
    print("안뇽2")
}

// 안뇽1
// 안뇽2

원래 clousre 파라미터는 후행 클로저로test 메서드 호출 시에 위와 같이 2가지로 표현될 수 있다.


해당 예제에 @autoClosure 을 적용해 보자.

func test(_ closure: @autoclosure () -> Void) {
    closure()
}

test(print("wow 😇")) // wow 😇

이런 식으로 중괄호를 생략하고 일반 표현식으로 표현할 수 있다.
훨씬 더 간결해진 느낌이다.


사실 이때까지 auto closure을 직접 구현해 본 경험은 거의 없을 텐데,
auto closure을 가지는 함수를 호출하는 것은 일반적이지만,
우리가 직접 구현하는 것은 일반적이지 않다고 한다.


사용 예시

document에 있는 예시를 들고 왔다.
assert(::file:line:)`

조건을 체크하여 false일 경우 message를 출력한다.

func assert(
    _ condition: @autoclosure () -> Bool,
    _ message: @autoclosure () -> String = String(),
    file: StaticString = #file,
    line: UInt = #line
)

condition - 체크할 조건
message - 조건이 false일 경우 해당 메세지 출력
file - 체크에 실패할 경우 message 와 함께 출력할 file 이름
line - 체크에 실패할 경우 message 와 함께 출력할 line number

message , file , line 모두 기본값이 존재

assert(3 < 5, "3은 5보다 작은데요?")

@autoclosure로 선언된 클로저 인자들(condition , message 의 중괄호를 생략하여 일반 표현식으로 간결하게 표현할 수 있다.



Reference

https://bbiguduk.gitbook.io/swift/language-guide-1/closures

0개의 댓글

관련 채용 정보