본문 바로가기
공부/스위프트

[스위프트] 클로저(Closures)

by 초코팅촉 2023. 7. 10.
728x90

스위프트에서 클로저라는 기능은 일회용 함수를 작성할 수 있는 구문이다.
작성방식은 워낙에 다양하지만 일회용으로 함수를 작성하다 보니 함수명이 필요 없어 익명함수라고 부르기도 한다.

클로저는 이미 다른 언어들에도 존재하는 기능인데, 다른 언어에서는 다음과 같이 부른다.
자바스크립트 - 익명함수
자바 - 람다함수
파이썬 - 람다함수
아마 대충 어떤 내용이 나올지 예상이 될 것이다.

 

원래 클로저 자체는 세 가지를 의미한다.

• 전역함수
• 중첩함수
• 클로저 표현식
여기서 살펴볼 내용은 클로저 표현식에 중점을 둘 것이다.

 

클로저의 표현식

우선 클로저는 기본적으로 함수이기 때문에 함수의 기본적인 형태를 따르나
일회용이기 때문에 많은 부분이 생략된다. 차차 알아가 보자.

//기본적인 형식
{ (매개변수) -> 반환형 in
    실행 구문
}

//인자값, 반환값 없을때
{ () -> () in
    실행 구문
}

우선 func 키워드가 생략됐고 중괄호 안에 처음에 내용이 전부 들어간다.
특이한 점은 함수 작성 시엔 반환값이 없어도 정상적으로 작성 가능하지만
클로저는 반환값이 없어도 가독성을 위해 반드시 빈 소괄호나 Void를 작성해야 한다.

클로저는 여러 가지 방법으로 실행할 수 있다.

//기본적인 실행방식
let abc = { (num: Int) -> () in
    print(num+1)
}

abc(2)    // 3 출력

//한번 더 간소화
({ () -> () in
    print("실행됨.")
})()    // 실행됨. 출력

첫 번째를 보면 매개변수명을 써도 함수 내부에서만 사용하는데
이는 간소화를 해도 똑같이 적용된다.

 

후행 클로저(트레일링 클로저; Trailing Closure)

후행(트레일링) 클로저는 함수의 마지막 인자값이 클로저일 때, 인자값 형식으로 작성하지 않고
함수 뒤에 꼬리처럼 붙이는 것을 의미한다.
일단 예시를 보자.

var arr = [4, 2, 5, 1, 3]

//아래처럼 클로저가 있다고 할 때
arr.sorted(by: {(s1, s2) in
return s1 > s2})

⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎

arr.sorted() { (s1, s2) in
return s1 > s2
}

⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎⬇︎

arr.sorted(by: >)

클로저를 꼬리로 따로 붙이고 함수의 괄호를 생략까지도 할 수 있다.

 

탈출 클로져(Escaping Closures)

우선 예시를 하나 보자.

func callback(fn: () -> ()) {
    fn()
}

callback {
    print("Closure 실행됨.")
}

위의 예시는 callback함수를 후행 클로저를 이용해 간단하게 만들어

print함수를 callback 함수 내부에서 클로저로 사용한 것이다.

하지만 이를 아래처럼 바꾸면 주석처럼 오류가 발생한다.

func callback(fn: () -> ()) {
    let abc = fn    // Non-escaping parameter'fn' may only be called
    abc()
}

callback {
    print("Closure 실행됨.")
}

fn이 non-escaping 파라미터라 직접 호출하는 것만 가능하다는 뜻으로

상수에 대입도 할 수 없는 것이다. 따라서 아래와 같이 @escaping 속성을 타입 앞에 붙인다.

func callback(fn: @escaping () -> ()) {
    let abc = fn
    abc()
}

callback {
    print("Closure 실행됨.")    // 정상적으로 "Closure 실행됨." 출력된다.
}

탈출 클로저는 주로 비동기로 작동해야 할 부분들에 사용된다.

 

자동 클로저(Autoclosures)

자동 클로저는 인자값으로 전달된 구문들을 클로저로 래핑(Wrapping)한다.

이 말인 즉, 인자값으로 전달된 구문을 알아서 클로저로 만들어 준다는 것이다.

실제로는 지연된 동작방식이 필요할 때 사용한다.

선언 방식은 위의 탈출 클로저처럼 타입 앞에 @autoclosure를 붙여주면 된다.

우선 다음의 예시를 보자.

// 빈 배열 정의
var arr = [String]()

func addVars(fn: @autoclosure () -> Void) {

	// 배열 요소를 3개로 만들어 초기화
    arr = Array(repeating:"", count: 3)
    
    // 인자값으로 전달된 클로저 실행
    fn()
}

arr.insert("KR", at: 1)	// 오류 발생

addVars(fn: arr.insert("KR", at: 1))	// 정상 실행

// 출처 - 꼼꼼한 재은씨의 스위프트:문법편

위의 예시를 보면 알겠지만 arr를 분명히 초기화한 것 같지만 13번째 줄에서 오류가 발생한다.

이유는 간단하다. 함수가 작성되고 실행되지 않았기 때문에 안의 구문은 아직 실행이 되지 않았고

15번째 줄에서 실제로 함수가 호출되어 실행될 때 초기화 또한 하기 때문에

그제야 정상적으로 실행되는 걸 볼 수 있다.

 

 

스위프트 문법 - 클로저, Closure, swift