함수는 대부분의 프로그래밍 언어에서 지원하는 개념으로, 프로그램의 실행 과정 중에서 독립적으로 처리될 수 있는 부분을 분리하여 구조화한 객체를 의미한다. 이렇게 독립적으로 작성한 함수는 여러 번 호출할 수 있어서 같은 코드를 반복하여 작성할 수 없다.
스위프트에서도 함수를 이용하여 한번 작성된 코드를 여러 곳에서 호출하여 사용할 수 있다. 특히 스위프트는 함수형 프로그래밍 패러다임을 채택하고 있는 언어이므로 함수형 프로그래밍의 특성을 이해하는 것은 매우 중요하다.
함수의 기본 개념
우리가 중고등학교 시절 배운 함수를 생각해보자. f(x) = y 라는 함수가 있다면 x라는 입력값을 받아 내부 처리 과정을 거친 후 y라는 결과를 반환받는다. 여기서 x를 인자값 혹은 파라미터라고 하며, y를 반환값 혹은 리턴값 이라고 한다. 프로그래밍에서의 함수도 이와 같다.
함수는 일반 함수와 사용자 정의 함수로 나뉜다. 일반 함수는 프로그래밍 언어나 프레임워크 수준에서 제공하는 함수로 기본적인 데이터의 처리나 연산 등을 수행하기 위한 목적으로 사용된다. 대표적으로 출력을 위한 함수인 print() 가 있다. 우리는 print() 함수를 만들지 않았지만 사용하기 때문이다.
하지만 개발을 하다보면 이러한 일반 함수로만은 충분하지 않는다. 이 때 우리는 필요한 함수를 직접 만들어야 하는데 이것을 사용자 정의 함수라고 한다.
함수를 사용하지 않고도 프로그래밍은 가능하다. 함수 내부 코드만 사용하면 되기 때문이다. 하지만 함수의 중요한 의미를 갖는 것은 다음과 같은 이점이 있기 때문이다.
- 동일한 코드가 여러 곳에서 사용될 때 이를 함수화 하면 재작성할 필요 없이 함수 호출만으로 처리할 수 있다.
- 전체 프로세스를 하나의 소스 코드에서 연속적으로 작성하는 것보다 기능 단위로 함수화하면 가독성이 좋아지고, 코드와 로직을 이해하기 쉽다.
- 비즈니스 로직을 변경해야 할 때 함수 내부만 수정하면 되므로 유지보수가 용이하다.
사용자 정의 함수
사용자 정의 함수를 정의하는 방식은 아래와 같다.
func 함수이름(매개변수1: 타입, 매개변수2: 타입) -> 반환타입 {
실행내용
return 반환값
}
함수를 정의할 때 function의 약자인 func 키워드를 사용한다. 그 다음 함수의 이름을 작성하는데, [+,-,*,/] 같은 연산자와 예약어는 사용할 수 없다. 함수 이름에 사용할 수 있는 문자들은 영어, 숫자, 한자, 바이너리 이미지 등올 다양하지만 첫 글자는 반드시 영어 혹은 언더바(_)로 시작해야 한다. 언더바 이외 특수문자나 숫자로 시작할 경우 컴파일러에 의해 오류가 발생한다. 대신 두 번째 글자부터는 이러한 제약이 없으므로 영어, 숫자, 일부 특수문자를 충분히 활용할 수 있다.
하지만 일반적으로 함수명을 지을 땐 숫자를 사용하지 말고, 의미를 잘 전달할 수 있는 문자로만 만드는 것이 좋다.
다음으로는 함수의 인자값 갯수와 형태를 정의하기 위해 소괄호 영역을 표시해 주는 것이다. 입력값을 대입 받기 위한 변수의 이름과 타입이 정의되는데 이를 매개변수, 영어로는 파라미터라고 한다. 매개변수가 여러개라면 쉼표(',')를 통해 구분하며, 매개변수가 없는 경우 소괄호 내 아무것도 작성하지 않아도 된다.
함수의 반환 타입을 표시할 때는 "->" 기호와 함께 표시한다. 이 기호 다음에 작성된 자료형이 함수가 반환하는 값의 타입이다. 함수의 성격에 따라서는 반환값이 전혀 없는 함수를 작성할 수도 있다. 이 경우에는 "->" 기호와 반환타입을 작성하지 않으면 된다.
함수 내부에서 실행내용이 모두 실행되고 결과값을 반환하는데 이 때 사용하는 키워드가 return이다. 아래 예시를 통해 더 자세히 볼 수 있다.
// 1. 매개변수와 반환값이 모두 없는 함수
func printHello(){
print("안녕하세요")
}
// 2. 매개변수는 없지만 반환값이 있는 함수
func sayHello() -> String {
let returnValue = "안녕하세요"
return returnValue
}
// 3. 매개변수는 있으나 반환값이 없는 함수
func printHelloWithName(name : String) {
print("\(name)님 안녕하세요")
}
// 4. 매개변수와 반환값이 모두 있는 함수
func sayHelloWithName(name : String) -> String {
let returnValue = "\(name)님 안녕하세요"
return returnValue
}
2번과 4번 예제에서 "->" 뒤의 반환타입과 return 뒤의 변수 타입은 일치해야 한다.
반환값이 없는 함수의 경우에도 return 키워드를 종종 사용하는데, 이 때의 return은 함수의 실행을 명시적으로 종료할 목적으로 사용된다. 아래 함수는 옵셔널 바인딩이 실패했을 경우 return 을 호출하여 실행을 종료한다.
func hello(name : String?){
guard let _name = name else {
return
}
print("\(_name)님 안녕하세요")
}
함수의 호출
함수를 실행하는 것을 함수를 호출한다고 표현하는데 기본적으로 함수의 이름에 괄호를 붙이면 된다.
printHello()
// 결과값
// 안녕하세요
함수 호출시에는 func 키워드는 사용하지 않는다. 그리고 뒤 ()는 선언시의 ()와는 다르다. 선언시의 소괄호는 매개변수를 선언하기 위한 영역이라면, 호출시에 사용하는 괄호는 함수를 호출하는 연산자이다.
매개변수가 없다면 빈 괄호만 붙여서 호출하면 되지만, 매개변수가 있다면 괄호 안에 인자값을 넣고 호출해야 한다.
let inputName = "홍길동"
printHelloWithName(name: inputName)
// 결과값
// 홍길동님 안녕하세요
호출 구문에서 인자값 입력하는 부분이 특이하다. "name:"이 들어있다. 만약 이 부분을 빼버리면 "name: 이라는 레이블이 누락되었다"는 오류 메세지가 뜰 것이다. "name"을 인자 레이블 이라고 하는데 이 인자 레이블 없이 호출하면 컴파일 오류가 발생한다. 아래는 다른 예시이다.
// 입력된 (값 x 횟수) 만큼 카운트 변수의 값을 증가하는 함수
func incrementBy(amount: Int, numberOfTimes: Int){
var count = 0
count = amount * numberOfTimes
}
이 함수를 호출할 때에는 아래와 같이 인자값 앞에 인자 레이블을 붙여야 한다.
incrementBy(amount: 5, numberOfTimes: 2)
스위프트에서는 두 가지 방식으로 함수를 호출할 수 있다.
func times(x: Int, y: Int) -> Int {
return (x * y)
}
// 1. 함수의 이름만으로 호출한 구문
times(x: 5, y: 10)
// 2. 함수의 식별자를 사용하여 호출한 구문
times(x:y:)(5, 10)
함수명 뒤에 ()를 붙이고 여기에 인자 레이블과 인자값을 넣어 호출해주는 방법 하나, 그리고 함수 식별자 뒤에 ()를 붙이고 인자 레이블 없이 호출하는 방법 하나이다.
함수의 반환값과 튜플
함수는 반드시 하나의 값만을 반환한다. 여러 개의 값을 반환해야 한다면, 집단 자료형에 담아 반환해야 한다. 이 때 말하는 집단 자료형은 딕셔너리나 배열, 튜플, 또는 구조체나 클래스가 있다. 이러한 타입 모두 반환 타입으로 사용될 수 있다. 이 중 활용도가 높은 튜플의 사용 예시이다.
func getindvInfo() -> (Int, String) {
let height = 100
let name = "Peter"
return (height, name)
}
var uInfo = getindvInfo()
uInfo.0 // 100
uInfo.1 // Peter
튜플을 반환하는 함수의 반환값을 대입 받은 변수나 상수는 튜플의 인덱스를 이용하여 내부의 요소를 사용할 수 있다.
var(a, b) = getindvInfo()
a // 100
b // Peter
이렇게 튜플 요소 각각을 변수로 받을 수도 있다.
func getIndvInfo2() -> (h: Int, n: String) {
let height = 100
let name = "Peter"
return (height, name)
}
var uInfo2 = getIndvInfo2()
uInfo2.h // 100
uInfo2.n // Peter
반환값 튜플 인자 앞에 각각의 변수를 붙여주면 함수의 결과를 받을 때도 이들이 자동으로 바인딩 된다.
특정 튜플 타입이 여러 곳에서 사용될 경우에는 타입 알리어스를 통해 새로운 축약형 타입으 정의하는 것이 좋다. 타입알리어스는 이름이 길거나 사용하기 복잡한 타입 표현을 새로운 타입명으로 정의해주는 문법으로, typealias 키워드를 통해서 사용하여 정의한다. 이를 사용하면 전체적으로 소스 코드가 간결해지는 효과를 가져올 수 있다.
typealias <새로운 타입 이름> = <타입 표현>
사용 방법은 간단하다. typealias 키워드 다음 새로 정의할 타입 이름을 쓰고, 축약할 타입 표현을 대입해주면 된다. 이를 정의하면 컴파일러는 새로운 타입 이름을 타입 표현과 동일하게 간주한다.
typealias infoResult = (Int, Character, String)
func getuserInfo() -> infoResult {
let gender : Character = "M"
let height = 100
let name = "Peter"
return (height, gender, name)
}
let info = getuserInfo()
info.0 // 100
info.1 // "M"
info.2 // "Peter"
typealias를 적용하기 전과 후를 비교하면 딱히 달라진점은 없다. 단지 함수를 정의하는 부분만 약간의 변경이 있으 뿐이다. 타입알리어스를 이용해 축약 표현을 만들 때 변수가 바인딩된 튜플을 정읳라 수도 있다.
typealias infoResult = (h: Int, g: Character, n: String)
func getuserInfo() -> infoResult {
let gender : Character = "M"
let height = 100
let name = "Peter"
return (height, gender, name)
}
let info = getuserInfo()
info.h // 100
info.g // "M"
info.n // "Peter"