[Swift] 스위프트 함수 선언하는 방법과 클로저(closure) : 매개변수 앞에 &는 언제 붙이는가?
스위프트 함수는 다음과 같은 구문을 사용하여 선언된다.
func <함수명> (<매개변수 이름>: <매개변수 타입>, <매개변수 이름>: <매개변수 타입>, ... ) -> <반환 결과 타입> {
// 함수 코드
}
기본예제
func sayHello() {
print("Hello")
}
반면, 다음의 예제 함수는 매개변수로 문자열 하나와 정수 하나를 받으며 문자열 결과를 반환한다.
func buildMessageFor(name: String, count: Int) -> String {
return
}
반환값 처리하기
let message = buildMessageFor(name: "John", count: 100)
스위프트로 개발할 때 메서드나 함수를 호출하여 반환된 결괏값을 사용하지 않는 경우가 생긴다.
이럴 때는 다음과 같이 반환값을 ‘_’에 할당하여 그 값을 버린다.
_ = buildMessageFor(name: "John", count: 100)
함수에 디폴트 매개변수 선언하기
스위프트는 함수가 호출될 때 인자로 쓸 값이 들어오지 않은 경우에 사용할 디폴트 매개변수 값을 지정할 수 있다. 이것은 함수를 선언할 때 매개변수에 디폴트 값을 할당하면 된다. 디폴트 매개변수를 사용하도록 함수를 선언할 때 디폴트 값을 매개변수의 끝에 둔다. 이렇게 하여 컴파일러가 어떤 매개변수가 함수 호출 시에 생략된 건지를 혼동하지 않도록 한다. 또한, 스위프트는 함수를 호출하면 반드시 사용되는 디폴트 값을 설정한 매개변수에 대해 지역 매개변수명을 기반으로 한 디폴트 외부 매개변수명을 제공한다.
디폴트 매개변수에 대해 확인하기 위해, 인자로 고객 이름을 전달하지 않은 경우에 디폴트 값인 ‘Customer’라는 문자열이 사용되도록 buildMessageFor 함수를 수정할 것이다.
func buildMessageFor(_ name: String = "Customer", count: Int ) -> String {
return "(name), you are customer number (count)"
}
이제 이 함수는 name 인자를 전달하지 않고 호출될 수 있다.
let message = buildMessageFor(count: 100)print(message)
이렇게 실행하면, 앞의 함수는 콘솔 패널에 다음과 같이 출력할 것이다.
Customer, you are customer number 100
여러 결괏값 반환하기
결괏값들을 튜플로 래핑하면 여러 개의 결괏값을 함수가 반환할 수 있다. 다음의 함수는 길이에 대하여 인치(inch) 단위의 측정값을 매개변수로 받는다. 이 함수는 매개변수로 받은 값을 야드, 센티미터, 미터로 변환하고, 이들 값을 하나의 튜플 인스턴스에 넣어 반환한다.
func sizeConverter(_ length: Float) -> (yards: Float, meters: Float) {
let yards = length * 0.0277778
let meters = length * 0.0254
let centimeters = length * 2.54
return (yards, centimeters, meters)
}
이 함수의 반환 타입을 보면, 이 함수는 yards, centimeters, 그리고 meters라는 이름의 값 3개를 포함하는 튜플을 반환하며, 모두 Float 타입임을 나타낸다.
-> (yards: Float, centimeters: Float, meters: Float)
이 함수를 이용하여 변환을 해보면, 이 함수는 튜플 인스턴스를 생성하고 그것을 반환한다. 이 함수를 사용하는 방법은 다음과 같다.
let lengthTuple = sizeConverter(20)
print(lengthTuple.yards)
print(lengthTuple.centimeters)
print(lengthTuple.meters)
가변개수 매개변수
애플리케이션 코드 내에서 함수가 호출될 때 함수가 받게 될 매개변수가 몇 개인지 알 수 없는 경우도 있다.
스위프트는 가변개수 매개변수(variadic parameter)를 사용하여 이러한 경우를 처리할 수 있게 한다.
함수가 지정된 데이터 타입의 매개변수 0개 또는 그 이상을 받는다는 것을 의미하는 점 세 개(…)를 이용하여 가변개수 매개변수를 선언한다.
함수 내에서 매개변수는 배열 개체의 형태로 사용할 수 있다. 예를 들어, 다음의 함수는 문자열 값들을 매개변수로 받아 콘솔 패널에 출력하는 함수다.
func displayStrings(_ strings: String…) {
for string in strings {
print(string)
}
}
displayStrings("one", "two", "three", "four")
입출력 매개변수로 작업하기
어떤 변수가 매개변수로 함수에 전달되면 그 매개변수는 해당 함수 내에서 상수로 취급된다는 것을 배웠다. 또한, 앞 절에서 매개변수의 값을 변경하고 싶을 때는 섀도 복사본을 생성해야 한다는 것도 배웠다. 이것은 복사본이기 때문에 어떻게 변경한다고 해도 기본적으로는 원본 변수에 반영되지 않는다. 예를 들어, 다음의 코드를 보자.
var myValue = 10
func doubleValue(_ value: Int) -> Int {
var value = value
value += value
return value
}
print("Before function call myValue = (myValue)")
print("doubleValue call returns (doubleValue(myValue))")
print("After function call myValue = (myValue)")
함수가 값을 반환한 뒤에도 매개변수에 대한 변경을 유지하려면, 함수 선언부 내에서 매개변수를 입출력 매개변수(in-out parameter)로 선언해야 한다. 이를 확인하기 위해서 다음과 같이 doubleValue 함수를 수정하여 inout 키워드를 추가하고 섀도 복사본을 삭제한다.
func doubleValue (_ value: inout Int) -> Int {
//var value = value
value += value
return value
}
마지막으로, 함수를 호출할 때 입출력 매개변수 앞에 &를 붙여야 한다.
print("doubleValue call returned (doubleValue(&myValue))")
이렇게 하고 코드를 실행해보면 원본인 myValue 변수에 값이 할당되도록 함수가 수정되었음을 알 수 있다.
Before function call myValue = 10
doubleValue call returns 20
After function call myValue = 20
매개변수인 함수
스위프트에서 재미있는 기능으로, 함수가 데이터 타입처럼 취급될 수 있다는 것이다. 예를 들어, 다음의 선언부처럼 함수를 상수나 변수에 할당하는 것도 가능하다.
func inchesToFeet (_ inches: Float) -> Float {
return inches * 0.0833333
}
let toFeet = inchesToFeet
앞의 코드는 inchesToFeet이라는 이름의 새로운 함수를 선언하고 그 함수를 toFeet이라는 이름의 상수에 할당한다. 이렇게 할당을 했다면, 원래의 함수 이름이 아니라 상수 이름을 이용하여 함수를 호출할 수도 있다.
let result = toFeet(10)
겉보기에는 그렇게 매력적인 기능이 아닌 것처럼 보인다. 왜냐하면 상수나 변수 데이터 타입에 함수를 할당하지 않아도 우리는 이미 함수를 호출할 수 있기 때문에 큰 장점이 없어 보인다.
하지만, 상수나 변수에 할당된 함수는 여러 데이터 타입의 기능을 가질 수 있다는 점을 고려하면, 앞으로 이 기능은 많이 활용될 수 있을 것이다. 게다가, 이제 함수는 다른 함수의 인자로 전달될 수 있으며, 함수의 반환값으로 반환될 수도 있다.
또한, 함수의 타입을 반환 타입으로 선언하면 함수도 데이터 타입으로 반환될 수 있다. 다음 함수는 불리언 매개변수의 값에 따라 우리가 만든 toFeet 함수나 toYards 함수 타입을 반환하도록 구성되어 있다.
func decideFunction(_ feet: Bool) -> (Float) -> Float{
if feet {
return toFeet
} else {
return toYards
}
}
클로저 표현식
클로저(closure) 클로저 표현식(closure expression)의 개념을 살펴보자. 이 두 가지 용어가 혼용되고 있지만, 몇 가지 큰 차이가 있다.클로저 표현식은 독립적인 코드 블록이다.
예를 들어, 다음은 클로저 표현식을 선언하고 그것을 sayHello라는 이름의 상수를 할당한 다음에 상수 참조를 통해 함수를 호출한다.
let sayHello = { print("Hello") }
sayHello()
클로저 표현식은 매개변수를 받아 결괏값을 반환하도록 구성할 수도 있다. 다음의 구문을 살펴보자
{(<매개변수 이름>: <매개변수 타입>, <매개변수 이름>: <매개변수 타입>, ... ) -> <반환 타입> in
// 클로저 표현식 코드가 온다
}
예를 들어, 다음의 클로저 표현식은 두 개의 정수를 매개변수로 받아 하나의 정수를 결과로 반환한다.
let multiply = {(_ val1: Int, _ val2: Int) -> Int in
return val1 * val2
}
let result = multiply(10, 20)
이 구문은 함수를 선언할 때 사용하는 것과 비슷하지만, 클로저 표현식은 이름을 갖지 않으며, 매개변수와 반환 타입은 괄호 안에 포함되고, 클로저 표현식 코드의 시작을 가리키기 위하여 in 키워드를 사용한다. 사실, 함수는 이름이 있는 클로저 표현식일 뿐이다.클로저 표현식은 비동기 메서드 호출에 대한 완료 핸들러를 선언할 때 종종 사용된다. 다시 말해, iOS 애플리케이션을 개발할 때 어떤 작업을 백그라운드에서 작업하게 해서 애플리케이션이 다른 작업을 계속 할 수 있도록 운영체제에게 요청해야 하는 경우가 종종 생긴다. 이런 경우에는 보통 시스템이 애플리케이션에게 작업이 완료된 것을 알리고 작업(메서드)을 호출할 때 선언했던 완료 핸들러를 호출하여 결과를 반환한다.
완료 핸들러에 대한 코드는 주로 클로저 표현식의 형태로 구현된다. 이 책의 후반부에서 사용되는 다음의 예제 코드를 살펴보자.
eventstore.requestAccess(to: .reminder, completion: {(granted: Bool, error: Error?) -> Void in
if !granted {
print(error!.localizedDescription)
}
})
requestAccess(to:) 메서드 호출로 수행된 작업이 완료되면, completion: 매개변수로 선언된 클로저 표현식이 실행된다. 다음의 선언부와 같이 이 완료 핸들러는 불리언 값과 Error 객체를 매개변수로 받으며 아무런 결과도 반환하지 않는다.
{(granted: Bool, error: Error?) -> Void in
사실, 스위프트 컴파일러는 이 메서드 호출에 대한 완료 핸들러의 매개변수와 반환값에 대해 이미 알고 있기 때문에 클로저 표현식 내에서 선언되지 않은 정보를 유추할 수 있다. 따라서 클로저 표현식의 선언부를 다음과 같이 간략하게 할 수 있다.
eventstore.requestAccess(to: .reminder, completion: {(granted, error) in
if !granted {
print(error!.localizedDescription)
}
})
스위프트의 클로저
컴퓨터 공학 용어에서의 클로저(closure)는 함수나 클로저 표현식과 같은 독립적인 코드 블록과 코드 블록 주변에 있는 하나 이상의 변수가 결합된 것을 말한다. 예를 들어, 다음의 스위프트 함수를 살펴보자.
func functionA() -> () -> Int {
var counter = 0
func functionB() -> Int {
return counter + 10
}
return functionB
}
let myClosure = functionA()
let result = myClosure()
의 코드에서 functionA는 functionB라는 이름의 함수를 반환한다. 사실, functionB는 functionB의 내부 영역 밖에 선언된 counter 변수에 의존하기 때문에 functionA는 클로저를 반환하고 있다. 다시 말해, functionB는 counter 변수를 잡고 있다(captured). 또는 가두고 있다(closed over)라고 말할 수 있으므로 전통적인 컴퓨터 공학 용어인 클로저(closure)로 간주된다.스위프트와 관련해서 클로저(closure)와 클로저 표현식(closure expression) 용어가 넓게 쓰이다 보니 혼용되기 시작했다. 어쨌거나 중요한 점은 스위프트는 둘 다 지원한다는 것이다.
-알라딘 eBook <핵심만 골라 배우는 SwiftUI 기반의 iOS 프로그래밍> (닐 스미스 지음, 황반석 옮김) 중에서