swift & iOS/swift

[swift] 클로저 closure (feat. 함수)

whale3 2022. 1. 16. 18:56

스위프트의 함수는 '1급 객체'이다. 1급 객체의 특징은 다음과 같다.

- 변수에 저장할 수 있음

- argument로 전달할 수 있음

- 리턴값으로 사용할 수 있음

 

제목은 클로저라고 써놓고는 왜 함수를 얘기하냐면 함수도 결국 클로저였기 때문이다. 스위프트 공식 문서에 아래처럼 나와있다. 

출처: https://docs.swift.org/swift-book/LanguageGuide/Closures.html

 

Global and nested functions, as introduced in Functions, are actually special cases of closures. Closures take one of three forms:
Global functions are closures that have a name and don’t capture any values. Nested functions are closures that have a name and can capture values from their enclosing function. Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.

 

스위프트 공식 문서에서 functions 챕터에 나왔던 글로벌 함수(global functions)와 중첩 함수(nested functions)는 클로저의 특별한 예시인 것이다. (왜 special 이지...?) 그래서 '클로저'는 아래의 3가지 경우의 하나인 것이다. 

 

1. 글로벌 함수: 이름이 있고 캡쳐링 하지 않는 클로저

2. 중첩 함수: 이름이 있고 자기 자신을 감싸는 함수의 값들을 캡쳐링하는 클로저

3. 클로저 표현식(closure expression): 이름 없으며(=익명) 좀 더 간결하게 작성된 클로저. 자기 자신 바깥 환경의 값을 캡쳐링 함

 

스위프트 공식문서에서 functions 챕터는 1, 2를 다루고 closures 챕터에서는 3번을 다룬다. 

 

parameter로 하나 이상의 함수를 받는 메소드, 함수를 다룰 때는 '좀 더 간단한 형태의 함수 같은 것'을 사용하는 것이 더 편하다고 공식문서에서는 말한다. closure expression이 대체 얼마나 간단하게 작성할 수 있길래 이렇게 소개해놓은 것인가 살펴보면...

 

공식 문서에 나와있는 예를 보면

// 출처: https://docs.swift.org/swift-book/LanguageGuide/Closures.html

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

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward) 
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
// names를 알파벳 역순으로 정렬하여 새로운 배열을 반환되어 reversedNames에 할당됨

 

흔히 사용하는 함수 형식으로 작성할 수 있다. 하지만 결국 필요한 부분은 s1 > s2 인데 이것을 쓰려고 필요없는 func, 함수 이름, 줄바꿈(??)을 사용하게 된다. 이 부분을 closure expression 으로 바꿔서 작성하면 좀 더 간단해진다. 

 

먼저 closure expression은 아래처럼 생겼다. 

{ (parameters) -> return type in
    statements
}

이제 func backward 부분을 closure expression으로 바꿔보면 아래처럼 작성할 수 있다. 

/*
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
*/

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

그리고 이렇게 in 다음으로 나오는 부분이 짧은 경우는 한 줄로 쭉 작성할 수 있다. 이렇게 작성하는 표현법을 inline closure expression 이라고 공식 문서에 나와있다. 

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

'sorted()' 메소드를 [String] 타입의 names 라는 변수가 호출하기 때문에 sorted 메소드에 argument로 전달하는 closure 는 당연히 2개의 String을 argument로 받고 Bool 타입의 값을 리턴하는 closure여야 한다.... 라는 사실을 스위프트는 미리 짐작하여 알 수 있기 때문에 위에서 argument 와 리턴 값의 타입도 생략할 수 있다. 그래서 또 줄여서 작성하면,

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

이렇게 된다. 꼭 sorted 메소드가 아니어도 스위프트는 언제나 메소드나 함수에 argument으로 전달하는 closure expression의 argument와 리턴값의 타입을 짐작할 수 있기 때문에 타입들을 생략하여 위처럼 작성할 수 있다고 한다. (하지만 경우에 따라 타입들을 확실하게 작성하는 것을 선호하면 타입을 적어도 상관없다)

 

여기서 더 줄여 return 키워드를 생략하면,

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

또 한 번 줄여서 argument들을 $0, $1, $2... 이렇게 할 수도 있다.

// var reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })
// 첫번째 argument s1은 $0, 두번째 argument s2는 $1 ...
var reversedNames = names.sorted(by: { $0 > $1 })

마지막으로 여기서 줄여서 아래처럼 작성할 수 있다. 스위프트 String 타입은 부등호를 '아하 이 부등호는 2개의 String을 parameter로 가지면서 Bool 타입을 리턴하는 메소드 같은 거구나!' 라고 인식한다. 2개의 String parameter를 가지면서 Bool 타입을 리턴한다? ... 는 sorted 메소드에 argument로 전달하는 closure와 타입이 일치하기 때문에 아래처럼 closure를 넣어야 하는 자리에 부등호만 딱 넣을 수 있는 것이다. 

var reversedNames = names.sorted(by: >)

 

 

escaping도 있는데 이미 너무 길어졌다... 이건 다음에...

반응형