Swift의 함수, 메서드, 클로저는 체계적인 구조와 효율적인 코드를 작성하는 핵심 부분이며, 코드의 반복을 피하면서 프로그램을 구조화하는 방법을 제공한다.
함수는 특정 작업을 수행하기 위해 호출할 수 있게 이름 붙여진 코드 블록이다.
작업을 수행하기 위한 데이터가 제공될 수 있고 작업의 결과를 호출한 코드로 반환할 수도 있다.
예를 들어, Swift 프로그램에서 수행해야 할 값을 받아(매개변수-parameter) 산술식을 수행하면, 계산 결과가 반환 되도록 프로그래밍할 수 있다.
프로그램 코드 어디서든지 산술 계산이 필요하면 매개변수의 값을 인자(argument)로 이 함수에 전달하여 호출하면 결괏괎이 반환될 것이다.
함수에 대해 이야기할 때 매개변수(parameter)와 인자(argument)라는 용어가 서로 혼용되곤 한다. 하지만 이들에겐 미묘한 차이가 있다.
함수가 호출할 때 받게 되는 값을 매개변수라고 한다.
하지만, 실제로 함수가 호출되고 값이 전달된 시점에서는 인자라고 부른다.
본질적으로 메서드는 특정 클래스나 구조체 또는 열거형과 연관된 함수다.
예를 들어, 여러분이 Swift 클래스 내에서 함수를 선언했다면 이것은 메서드로 간주된다.
특별한 언급이 없다면 함수에 대해 설명하는 규칙과 동작 모드가 메서드에서도 동일하게 적용된다.
func 함수명 (매개변수 이름: 매개변수 타입, 매개변수 이름: 매개변수 타입,...) -> 반환결과 타입 {
// 함수 코드
}
매개변수를 받지 않고 결과를 반환하지도 않으며 메시지를 표시하는 함수 예제
func sayHello () {
print("Hello")
}
매개변수로 문자열 하나와 정수 하나를 받으며 문자열 결과를 반환하는 함수 예제
func buildMessageFor(name: String, count: Int) -> String {
return "\(name), you are customer number \(count)"
}
만약 함수가 단일 표현식을 가지고 있다면 return 구문을 생략할 수 있다.
func buildMessageFor(name: String, count: Int) -> String {
"\(name), you are customer number \(count)"
}
return 구문은 함수가 단일 표현식을 가지고 있을 때만 생략할 수 있다.
다음의 코드는 함수가 두 개의 표현식을 가지고 있기 때문에 컴파일 에러가 발생한다.
func buildMessageFor(name: String, count: Int) -> String {
let uppername = name.uppercased()
"\(uppername), you are customer number \(count)" // 컴파일 에러
}
함수를 선언했다면 다음과 같은 구문을 이용하여 호출하게 된다.
함수명 (인자1, 인자2, ...)
함수를 통해 전달되는 각각의 인자는 함수가 받도록 구성된 매개변수와 일치해야 한다.
예를 들어, 매개변수를 받지 않고 아무런 값도 반환하지 않는 sayHello라는 이름의 함수를 호출하려면 다음과 같이 한다.
sayHello()
두 개의 매개변수를 받아 결과를 반환하는 buildMessageFor라는 이름의 함수를 호출하기 위해서는 다음과 같은 코드를 작성하게 될 것이다.
let message = buildMessageFor(name: "John", count: 180)
위 예제에서 message라는 이름의 새로운 상수를 생성하고 함수로부터 반환되는 결과를 저장하기 위해 할당 연산자( = )를 사용하였다.
Swift로 개발할 때 메서드나 함수를 호출하여 반환된 결괏값을 사용하지 않는 경우가 생긴다. 이럴 때는 반환값을 _ 에 할당하여 그 값을 버린다.
_ = buildMessageFor(name: "John", count: 180)
앞선 예제의 함수들은 선언할 때 함수 코드 내에서 참조할 수 있는 이름을 할당한 매개변수로 구성하였다.
이렇게 선언된 매개변수를 지역 매개변수명(local parameter name)이라고 한다.
함수 매개변수에는 지역 매개변수명 뿐만 아니라 외부 매개변수명(external parameter name)도 있을 수 있다.
이들 이름은 함수가 호출될 때 참조되는 매개변수의 이름이다.
기본적으로, 함수 매개변수에는 동일한 지역 매개변수명과 외부 매개변수명이 할당된다.
let message = buildMessageFor(name: "John", count: 180)
위에서 호출한 함수의 선언부를 보면 'name'과 'count'를 지역 매개변수이자 외부 매개변수명으로 사용한다고 선언했다.
func buildMessageFor(name: String, count: Int) -> String {
"\(name), you are customer number \(count)"
}
매개변수에 할당된 디폴트 외부 매개변수명은 다음과 같이 지역 매개변수명 앞에 밑줄 문자를 써서 없앨 수 있다.
func buildMessageFor(_ name: String, count: Int) -> String {
"\(name), you are customer number \(count)"
}
let message = buildMessageFor("John", count: 100)
다른 방법으로, 함수 선언부에서 지역 매개변수명 앞에 외부 매개변수명을 선언하면 간단하게 외부 매개변수명이 추가된다.
예를 들어, 다음의 예제코드는
첫 번째 매개변수명의 외부 매개변수명을 username으로 지정한다.
두 번째 매개변수명의 외부 매개변수명을 userCount로 지정한다.
func buildMessageFor(username name: String, userCount count: Int) -> String {
"\(name), you are customer number \(count)"
}
이렇게 함수를 선언했다면 함수를 호출할 때 반드시 외부 매개변수명을 참조해야 한다.
let message = buildMessageFor(username: "John", userCount: 100)
함수를 호출할 때 인자를 전달하기 위하여 외부 매개변수명이 사용됨에도 함수 내에서 매개변수를 참조할 때는 여전히 지역 매개변수명이 사용된다.
인자에 대한 외부 매개변수명을 사용하여 함수를 호출할 때는 함수를 선언했을 때와 동일한 순서로 인자를 넣어야 한다.
Swift는 함수가 호출될 때 인자로 쓸 값이 들어오지 않은 경우에 사용할 디폴트 매개변수 값을 지정할 수 있다.
이것은 함수를 선언할 때 매개변수에 디폴트 값을 할당하면 된다.
디폴트 매개변수를 사용하도록 함수를 선언할 때 디폴트 값을 매개변수 끝에 둔다.
이렇게 하여 컴파일러가 어떤 매개변수가 함수 호출 시에 생략된 건지를 혼돈하지 않도록 한다.
Swift는 함수를 호출하면 반드시 사용되는 디폴트 값을 설정한 매개변수에 대해 지역 매개변수명을 기반으로 한 디폴트 외부 매개변수명을 제공한다.
디폴트 매개변수에 대해 확인하기 위해, 인자로 고객 이름을 전달하지 않은 경우에 디폴트 값인 '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, centimeters: Float, meters: Float) {
let yards = length * 0.0277778
let centimeters = length * 2.54
let meters = length * 0.0254
return(yards, centimeters, meters)
}
let lengthTuple = sizeConverter(20)
print(lengthTuple.yards)
print(lengthTuple.centimeters)
print(lengthTuple.meters)
앱 코드 내에서 함수가 호출될 때 함수가 받게 될 매개변수가 몇 개인지 알 수 없는 경우도 있다.
Swift는 가변 매개변수를 사용하여 이러한 경우를 처리할 수 있게 한다.
함수가 지정된 데이터 타입의 매개변수 0개 또는 그 이상을 받는다는 것을 의미하는 점 세 개(...)를 이용하여 가변개수 매개변수를 선언한다.
함수 내에서 매개변수는 배열 개체의 형태로 사용할 수 있다.
예를 들어, 다음의 함수는 문자열 값들을 매개변수로 받아 콘솔 패널에 출력하는 함수다.
func displayStrings(_ strings: String...) {
for string in strings {
print(string)
}
}
displayStrings("one", "two", "three", "four")
함수가 받는 모든 매개변수는 기본적으로 상수로 취급된다.
즉, 함수의 코드 내에서 매개변수의 값이 변경되는 것을 막는다.
만약 함수 내에서 매개변수의 값을 변경하고 싶다면, 매개변수의 섀도 복사본(shadow copy)을 반드시 생성해야 한다.
다음의 함수는 인치 단위의 길이와 너비를 매개변수로 전달받아 두 값에 대한 섀도 변수를 생성하고 각각의 값을 센티미터로 변환한 후에 면적을 계산하여 반환한다.
func calcurateArea(length: Float, width: Float) -> Float {
var length = length
var width = width
length = length * 2.54
width = width * 2.54
return length * width
}
print(calcurateArea(length: 10, width: 20))
어떤 변수가 매개변수로 함수에 전달되면 그 매개변수는 해당 함수 내에서 상수로 취급된다는 것을 배웠다.
또한, 매개변수의 값을 변경하고 싶을 때는 섀도 복사본을 생성해야 한다는 것을 배웠다.
이것은 복사본이기 때문에 어떻게 변경한다고 해도 기본적으로는 원본 변수에 반영되지 않는다.
var myValue = 10
func doubleValue(_ value: Int) -> Int {
var value = value
value += value
return value
}
print("Before function call myValue = \(myValue)")
prunt("doubleValue call returns \(doubleValue(&myValue))")
print("After function call myValue = \(myValue)")
확실히 이 함수는 원본인 myValue 변수를 변경하지 않았다. 왜냐하면 myValue 변수가 아닌 복사본을 가지고 수학적 연산이 수행되었기 때문이다.
함수가 값을 반환한 뒤에도 매개변수에 대한 변경을 유지하려면, 함수 선언부 내에서 매개변수를 입출력 매개변수(in-out parameter)로 선언해야 한다.
그리고 함수 호출입출력 매개변수 앞에 &를 붙여야 한다.
var myValue = 10
func doubleValue(_ value: inout Int) -> Int {
var value = value
value += value
return value
}
print("Before function call myValue = \(myValue)")
prunt("doubleValue call returns \(doubleValue(&myValue))")
print("After function call myValue = \(myValue)")
Swift에서는 함수가 데이터 타입처럼 취급될 수 있다.
다음의 선언부처럼 함수를 상수나 변수에 할당하는 것도 가능하다.
func inchesToFeet(_ inches: Float) -> Float {
return inches * 0.833333
}
let toFeet = inchesToFeet
위 코드는 inchesToFeet이라는 이름의 새로운 함수를 선언하고 그 함수를 toFeet이라는 이름의 상수에 할당한다.
이렇게 할당을 했다면, 원래의 함수 이름이 아니라 상수 이름을 이용하여 함수를 호출할 수도 있다.
let result = toFeet(10)
겉보기에는 그렇게 매력적인 기능이 아닌 것처럼 보인다.
왜냐하면 상수나 변수 데이터 타입에 함수를 할당하지 않아도 우리는 이 함수를 호출할 수 있기 때문에 큰 장점이 없어 보인다.
하지만, 상수나 변수에 할당된 함수는 여러 데이터 타입의 기능을 가질 수 있다는 점을 고려하면, 앞으로 이 기능은 많이 활용될 수 있을 것이다.
게다가 이제 함수는 다른 함수의 인자로 전달될 수 있으며, 함수의 반환값으로 반환될 수도 있다.
어떤 함수와 다른 함수를 어떻게 연결하는지에 대해 살펴보기 전에 먼저 함수 데이터 타입의 개념을 알아보도록 하자.
함수의 데이터 타입은 받게 될 매개변수의 데이터 타입과 반환될 데이터 타입을 조합하여 결정된다.
앞의 예제에서 함수는 부동소수점 매개변수를 받고 부동소수점 결과를 반환하기 때문에 함수의 데이터 타입은 다음과 같이 결정된다.
func inchesToFeet(_ inches: Float) -> Float {
return inches * 0.833333
}
let toFeet = inchesToFeet
반면, Int와 Double을 매개변수로 받고 String 결과를 반환하는 함수는 다음의 데이터 타입을 갖게 된다. (Int, Double) -> String
어떤 함수가 다른 함수를 매개변수로 받기 위해서는 매개변수로 받게 될 함수의 데이터 타입을 선언하면 된다.
예를 들어, 다음의 코드는 두 개의 함수를 상수에 할당한다.
func inchesToFeet(_ inches: Float) -> Float {
return inches * 0.833333
}
func inchesToYards(_ inches: Float) -> Float {
return inches * 0.0277778
}
let toFeet = inchesToFeet
let toYards = inchesToYards
어떤 함수와 다른 함수를 어떻게 연결하는지에 대해 살펴보기 전에 먼저 함수 데이터 타입의 개념을 알아보도록 하자.
함수의 데이터 타입은 받게 될 매개변수의 데이터 타입과 반환될 데이터 타입을 조합하여 결정된다.
앞의 예제에서 함수는 부동소수점 매개변수를 받고 부동소수점 결과를 반환하기 때문에 함수의 데이터 타입은 다음과 같이 결정된다.
func inchesToFeet(_ inches: Float) -> Float {
return inches * 0.833333
}
let toFeet = inchesToFeet
// (Float) -> Float
반면, Int와 Double을 매개변수로 받고 String 결과를 반환하는 함수는 다음의 데이터 타입을 갖게 된다.
// (Int, Double) -> String
이 예제코드는 단위를 변환하고 콘솔 패널에 결과를 출력하는 함수가 필요하다.
새롭게 만들 함수는 다양한 종류의 측정 단위를 변환할 수 있게 만들어 최대한 보편적인 함수가 되게 한다.
매개변수로 함수를 어떻게 사용하는지 확인하기 위해 새롭게 만들 함수는 inchesToFeet 함수 타입과 inchesToYards 함수 타입 모두와 일치하는 함수 타입과 함께 변환할 매개변수로 받을 것이다.
이들 함수의 데이터 타입은 (Float) -> Float와 같기 때문에 우리의 새로운 함수는 다음과 같이 된다.
func inchesToFeet(_ inches: Float) -> Float {
return inches * 0.833333
}
func inchesToYards(_ inches: Float) -> Float {
return inches * 0.0277778
}
let toFeet = inchesToFeet
let toYards = inchesToYards
|
v
func outputConversion(_ converterfunc: (Float) -> Float, value: Float) {
let result = converterfunc(value)
print("Result of conversion is \(result)")
}
이 함수는 불리언 매개변수의 값에 따라 우리가 만든 함수나 toYards 함수 타입을 반환하도록 구성되어 있다.
func inchesToFeet(_ inches: Float) -> Float {
return inches * 0.833333
}
func inchesToYards(_ inches: Float) -> Float {
return inches * 0.0277778
}
let toFeet = inchesToFeet
let toYards = inchesToYards
|
v
func decideFunction(_ feel: Bool) -> (Float) -> Float {
if feet {
return toFeet
} else {
return toYards
}
}
클로저(closure)와 클로저 표현식(closure expression)은 혼용되어 불리지만, 몇 가지 큰 차이점이 있다.
클로저 표현식은 독립적인 코드 블록이다.
예를 들어 다음은 클로저 표현식을 선언하고 그것을 sayHello라는 이름의 상수를 할당한 다음에 상수 참조를 통해 함수를 호출한다.
let sayHello = {print("Hello")}
sayHello()
||
func sayHello() {
print("Hello")
}
클로저 표현식은 매개변수를 받아 결괏값을 반환하도록 구성할 수도 있다.
{(매개변수 이름: 매개변수 타입, 매개변수 이름: 매개변수 타입, ...) -> 반환결과 타입 in
//클로저 표현식의 코드가 온다.
}
다음의 클로저 표현식은 두 개의 정수를 매개변수로 받아 하나의 정수를 결과로 반환한다.
let multiply = {(_ val1: Int, _ val2: Int) -> Int In
return val1 * val2
}
let result = multiply(10, 20)
이 구문은 함수를 선언할 때 사용하는 것과 비슷하지만
클로저 표현식은 이름을 갖지 않으며
매개변수와 반환타입은 괄호 안에 표현되고
클로저 표현식 코드의 시작을 가리키기 위하여 in 키워드를 사용한다.
사실, 함수는 이름이 있는 클로저 표현식일 뿐이다.