※ 본 포스팅은 유튜브 Swift Programming Tutorial 강의를 기반으로 제가 직접 정리한 Swift 문법에 대한 글입니다.
Swift에 대해 처음 배워나가는 과정을 블로그에 정리하였습니다. 기본적으로 다른 프로그래밍 언어를 다룬 적이 있기에 제가 아는 부분은 생략될 수 있습니다. 최대한 틀린 부분이 있지 않게 적겠지만 혹시 잘못된 부분이 있으면 알려주세요!
< Functions >
1. 함수의 기본적인 구조
함수의 기본적인 구조는 다음과 같습니다.
func functionName(parameter1: Type, parameter2: Type) -> ReturnType {
// 함수 내부에서 실행될 코드
return returnValue
}
함수의 이름을 정할 때는 camel case에 따라 정합니다. 단어 사이에 띄어쓰기가 없고 첫 글자는 소문자이며, 그다음 단어부터는 대분자인 형식입니다. 예를 들어 `calculateTotal`, `printMessage` 등이 있습니다.
아래는 매개변수와 반환타입이 둘 다 없는 가장 간단한 함수코드입니다.
func noArgumentsAndNoReturnValue() {
"I don't know what I'm doing"
}
noArgumentsAndNoReturnValue()
// result : I don't know what I'm doing
argument(인자)와 return값이 둘 다 필요 없는 함수입니다. playground에서는 마지막줄처럼 함수를 호출해도 값이 나타나지만 pure Swift에서는 print 하려면 print()를 사용해야 합니다.
다음은 parameter(매개변수)는 있으나 return값이 없는 함수 예제를 보겠습니다.
func plusTwo(value: Int) {
let newValue = value + 2
}
plusTwo(value: 30) // result 출력 안됨
해당 함수의 매개변수 value는 Int 타입이라고 명시되어 있습니다. 하지만 반환 타입을 명시하지 않고 있습니다. 함수 내부에서 `newValue`를 계산하는 과정은 있지만, 이 값을 반환하거나 외부로 노출시키는 작업이 없기 때문에 실제로 반환값이 없는 함수입니다.
다음은 parameter(매개변수)와 return값이 둘 다 있는 함수 예제입니다.
func newPlusTwo(value: Int) -> Int {
return value + 2
}
newPlusTwo(value: 30)
// 32
위의 코드는 `value`라는 이름의 `Int`타입 매개변수를 받으며, `Int`타입을 반환합니다. `return`키워드를 사용하여 `value`+2의 결과를 반환하고 있습니다. Swift에서는 return을 생략해도 반환값을 추론 가능한 경우가 많습니다.
위의 함수는 아래와 같이 `return` 코드를 생략해도 같은 결과를 가질 수 있습니다.
func newPlusTwo(value: Int) -> Int {
value + 2
}
newPlusTwo(value: 30)
// 32
Swift에서 위와 같이 단일 표현식의 경우 `return`키워드를 필수적으로 명시할 필요는 없습니다. 또한 Swift의 타입추론에 의해서 복잡한 함수 내에서도 적용될 수 있습니다. 다만 Swift 컴파일러가 함수 내부에서 마지막으로 실행되는 표현식 또는 문장을 자동으로 반환값으로 취급합니다. 이때 명시한 반환값의 타입과 컴파일러가 추론한 반환값의 타입이 일치하면 `return`키워드를 생략할 수 있습니다.
func multiply(a: Int, b: Int) -> Int {
if a > 0 {
a * b
} else {
return 0
}
}
위의 함수에서도 return 키워드 없이 if의 조건이 참일 때 `a * b`를 반환하는 것을 볼 수 있습니다. 이는 컴파일러가 추론한 `a+b`의 결과가 명시된 함수 반환 타입인 `Int`와 일치하기 때문입니다.
func customAdd(
value1: Int,
value2: Int
) -> Int {
value1 + value2
}
let customAdded = customAdd(
value1: 10,
value2: 20
)
또한 함수는 위 코드와 같이 반환값을 변수에 할당할 수 있습니다. 코드를 보시면 `customAdded`라는 상수에 `customAdd`의 반환값을 할당하는 것을 볼 수 있습니다.
이는 Swift가 함수를 일급 객체(First-class citizen)로 취급하기 때문입니다. Swift에서 함수는 값처럼 다뤄질 수 있고, 변수나 상수에 할당되어 사용할 수 있습니다. 이를 통해 함수의 반환값을 다른 연산에 참여시키거나, 다른 함수의 인수로 전달하거나, 조건문에서 사용하는 등 다양한 용도로 활용할 수 있습니다.
2. 인수(argument)
(1) 라벨링
Swift에서 기본적으로 함수의 인수는 내부 라벨과 외부 라벨이 동일하게 설정됩니다. 하지만 함수를 정의할 때 인수(argument)에 외부 라벨(external label)과 내부 라벨(internal label)을 따로 지정할 수 있습니다. 외부 라벨은 함수를 호출할 때 사용되며, 내부라벨은 함수 내부에서 사용됩니다. 해당 예제를 보면서 설명하겠습니다.
func customAdd(to x: Int, and y: Int) -> Int {
return x + y
}
let sum = customAdd(to: 3, and: 5)
코드를 보시면 `customAdd`함수를 호출할 때 인수에 `to`와 `and`라는 외부라벨을 사용할 수 있습니다. 외부라벨은 함수 호출 시 가독성을 높여줍니다. 함수 내부에서는 `x`와 `y`라는 내부 라벨을 사용하여 인수를 참조할 수 있습니다.
아래 예시는 외부라벨을 가지지 않는 함수입니다.
func customMinus(
_ lhs: Int,
_ rhs: Int
) -> Int {
lhs - rhs
}
let customSubtracted = customMinus(20, 10)
외부라벨이 없는 함수는 외부라벨을 `_`로 설정하면 생략이 가능합니다. 외부 라벨이 없기 때문에 함수를 호출할 때 인자 라벨링 없이 인자 값만 적을 수 있습니다. 해당 코드는 인자에 외부라벨은 존재하지 않으며 내부라벨은 각각 `lhs`, `rhs`가 사용되었습니다.
(2) argument의 default value
함수의 매개변수는 기본값을 가질 수 있습니다. 기본값(default value)이란 매개변수에 미리 할당된 초기값을 의미합니다. 함수를 호출할 때 해당 매개변수에 인자값을 전달하지 않으면, 기본값이 매개변수에 할당됩니다.
func getFullName(
firstName: String = "Foo",
lastName: String = "Bar"
) -> String {
"\(firstName) \(lastName)"
}
// 호출 방법
// 1. without argument
getFullName() // "Foo Bar"
// 2. mix&match
getFullName(firstName: "Baz") // "Baz Bar"
getFullName(lastName: "Foo") // "Foo Foo"
// 3. call it with both parameters
getFullName(firstName: "Baz", lastName: "Qux") // "Baz Qux"
위 코드와 같이 `=`을 통해 매개변수 타입 뒤에 초기값을 명시하면 됩니다. 기본값을 가진 매개변수를 사용하면 함수 호출 시 인자를 생략하거나 선택적으로 전달할 수 있습니다. (위의 코드에서 3가지 방법이 나와있습니다)
3. 함수 반환값 활용 여부
함수의 반환값을 활용하지 않고 호출하는 경우, 즉 함수의 반환값을 변수나 상수에 할당하지 않는 경우, Playground에서는 모든 함수의 결과를 암묵적으로 사용(consume)하지만, pure Swift 환경에서는 warning이 나타납니다.
경고가 발생하는 이유는 해당 함수가 값을 반환하고 있는데 그 값을 활용하지 않기 때문입니다. 이는 잠재적인 버그를 야기할 수 있고, 코드의 가독성과 유지 보수성을 저해할 수 있습니다.
func myCustomAdd(
_ lhs: Int,
_ rhs: Int
) -> Int {
lhs + rhs
}
myCustomAdd(
20,
30
)
이러한 경고를 해결하기 위해선 몇 가지 방법이 존재합니다.
(1) 반환값이 필요 없는 경우 `_`를 사용
_ = myCustomAdd(20, 30)
반환값을 활용하지 않는 의도적인 경우 `_`에 반환값을 할당해 의도적으로 값을 무시할 수 있습니다.
(2) `@discardableResult` 사용
@discardableResult
func myCustomAdd(
_ lhs: Int,
_ rhs: Int
) -> Int {
lhs + rhs
}
위와 같이 `@discardableResult` attribute를 함수의 정의 앞에 추가하면, 반환 값이 사용되지 않아도 경고가 발생하지 않습니다. 이 attribute는 반환 값을 활용하지 않을 것으로 예상되는 함수에서 유용하게 사용될 수 있습니다. 해당 attribute를 통해 개발자의 의도를 명시적으로 표현합니다.
함수의 반환값이 있는 경우, 그 결과를 활용하는 것은 중요한 부분입니다. 그러나 때로는 함수가 복잡한 작업을 수행하고 성공여부를 나타내는 Boolean값만 반환하는 경우가 있습니다. 이 Boolean값이 실제로 중요하지 않는 경우도 있을 수 있습니다. 이때 `@discardableResult`속성을 사용하면 반환값을 무시하고 함수를 호출할 수 있습니다. 이를 통해 코드의 명확성을 유지하면서 필요한 경우에만 반환값을 활용할 수 있습니다.
4. 중첩 함수
Swift에서는 함수 내부에 다른 함수를 정의할 수 있습니다. 아래의 코드를 통해 설명드리겠습니다.
func doSomethingComplicated(
with value: Int
) -> Int {
func mainLogic(value: Int) -> Int {
value + 2
}
return mainLogic(value: value + 3)
}
doSomethingComplicated(with: 30)
// 35
위의 코드에서 `mainLogic`이라는 중첩 함수가 정의되어 있습니다. 이 함수는 `doSomethingComplicated` 함수 내부에서만 사용할 수 있습니다. 이 외부함수를 호출하는 다른 부분이나 전역적인 범위에서는 중첩함수를 직접 호출할 수 없습니다. 오로지 외부함수 내에서만 필요한 지역적인 작업을 수행하도록 설계되어 있습니다.
이는 코드의 모듈화와 캡슐화와 관련 있습니다. 중첩함수는 외부에 노출되지 않고, 외부 함수의 내부 구현을 감추는 데 도움을 줍니다. 이는 코드의 가독성과 유지 보수성 향상에도 영향을 줍니다.
5. Function Signature
Function Signature(함수 시그니처)는 함수의 이름과 매개변수의 타입, 반환 타입을 의미합니다. 함수 시그니처는 함수의 고유한 식별자로 간주되며, 함수를 정의하고 호출할 때 사용합니다. 이는 함수의 형태와 동작을 명확하게 정의하는 데 도움을 줍니다.
func addNumbers(_ number1: Int, _ number2: Int) -> Int {
// 두 정수를 더한 결과를 반환
}
위의 함수의 시그니처는 다음과 같습니다. 이름이 `addNumbers`이고, 두 개의 `Int` 타입 매개변수(`number1`과 `number2`)를 가지며, 반환 타입은 `Int`입니다.
함수 시그니처는 함수를 호출할 때 사용됩니다. 호출할 때 함수 이름과 인자값의 타입 및 순서가 함수 시그니처와 일치해야 컴파일러가 올바른 함수를 찾아 실행하고, 타입 안정성을 보장할 수 있습니다.
또한 이는 함수의 오버로딩(Overloading)에도 중요한 역할을 합니다. 오버로딩은 동일한 이름을 가진 함수가 다른 매개변수 타입 또는 매개변수 개수를 가지는 것을 의미합니다. 이때 함수 시그니처인 고유한 식별자가 다른 함수들과 구분하여 호출 시 적절한 함수를 선택할 수 있도록 합니다.
func greet() {
print("Hello!")
}
func greet(name: String) {
print("Hello, \(name)!")
}
func greet(age: Int) {
print("You are \(age) years old!")
}
위의 코드에서 `greet`라는 이름을 가진 함수가 세 가지 버전으로 오버로딩되었습니다. 이처럼 오버로딩을 사용하여 유사한 작업을 수행하는 함수들을 하나의 이름으로 그룹화할 수 있습니다. 같은 이름이지만 각각 매개변수 타입이 다른 고유한 함수 시그니처를 가지고 있기에 컴파일러는 적절한 함수를 찾을 수 있습니다.
여기까지 functions에 대한 정리였습니다. 다음 포스팅은 Closures에 대한 내용으로 돌아오겠습니다.
강의는 freeCodeCamp 채널의 영상인데, Vandad라는 분이 진행하는 7시간 분량의 full tutorial for beginners입니다. 해당 강의는 프로그래밍 언어를 Swift로 처음 접하는 사람들에게는 다소 어려울 수 있다고 생각합니다. 저는 기초적인 부분의 디테일한 중요성을 깊게 알려주는 것 같아서 강의를 택했습니다. 영어 강의이며 영어자막 또는 자동번역 기능이 제공되기에, 혹시 프로그래밍 언어를 다룬 적이 있는 분들 중 Swift에 대해 새롭게 배우고자 한다면 강추드립니다!
해당 강의 영상은 아래에 걸어두겠습니다. 혹시 틀린 부분이나 추가하고 싶은 부분이 있으면 알려주세요.
강의 전체를 시리즈로 적을 수 있도록 노력하겠습니다.
<이전글>
2023.05.08 - [TIL/Swift] - [Swift] Swift 기본 문법 정리 1. Variables
2023.05.15 - [TIL/Swift] - [Swift] Swift 기본 문법 정리 2. Operators & If-else