[Swift] Functions

비나·2022년 8월 30일

Swift

목록 보기
7/8
post-thumbnail

📌 Function vs. Methods

함수와 메서드는 기본적으로 같다.
다만, 상황이나 위치에 따라 다른 용어로 부르는 것 뿐이다.
암튼 둘다 함수이다.
대충 북어 동태 명태 같은거다.
아래와 같이 다르게 쓰인다.

  • 함수 : 모듈 전체에서 전역적으로 사용할 수 있는 함수
  • 메서드 : 구조체, 클래스, 열거형 등 특정 타입에 연관되어 있는 함수



📌 Function

그래서 Swift에서 함수는 뭐냐고..
일단 조건문이나 반복문처럼 Swift에서 괄호()를 생략할 수 없다.
스위프트의 함수는 오버라이드(재정의)와 오버로드(중복 정의)를 모두 지원한다.
따라서 이름이 같은 함수를 구현해도 오류가발생하지 않을 수 있다.

✅ 정의

기본적으로 함수를 정의하는 방법은 아래와 같다.

func 함수명(매개변수) -> 반환 타입{
	실행 코드
    return 	반환 값
}

📖 예제

아래 함수의 예제를 통해 정의를 확인해보자.

📄 입력

import UIKit

func helloFunction(name: String) -> String {
    return "Hello \(name)"
}

let helloVina = helloFunction(name: "Vina")
print(helloVina)

💻 출력

Hello Vina

✅ 매개변수

매개변수는 외부에서 함수에 전달하는 값의 이름이다.
위의 예제에 있는 코드의 경우 helloFunction(name: "Vina") 에서 name이 매개변수이다.
그리고 "Vina"의 경우 함수에 실제로 전달되는 값이므로 전달인자라고 한다.

📖 매개변수 없는 경우

매개변수가 필요없으면 그냥 안쓰면 된다.
만약 아래와 같이 그냥 Hello World를 print를 하는 함수가 있다고 가정해보자.

import UIKit

func helloFunction() -> String {
    return "Hello World"
}

print(hellofuction())

이런 함수에는 매개변수가 딱히 필요없다.
이럴때는 그냥 안쓰면 된다.

📖 매개변수 여러개인 경우

여러개인 경우에는 그냥 콤마로 여러개를 써주면 된다.

import UIKit

func helloFunction(sayHi: String, name: String) -> String {
    return "\(sayHi) \(name)"
}

print(helloFunction(sayHi: "Hello", name: "Vina"))

그리고 한개인 경우와 마찬가지로 함수를 호출하는 경우 매개변수 이름에 전달인자를 넣어주면 된다.

📖 Argument Label

함수에서 매개변수 이름과 함께 Argument Label(전달인자 레이블)을 사용할 수 있다.
사용하는 방법은 매개변수 이름 앞에 한칸을 띄우고 써주면 된다.

func 함수명(전달인자 매개변수: 타입, 전달인자 매개변수: 타입) -> 반환타입{
  실행 코드
  return 반환 값
}

함수를 호출하는 경우 매개변수가 아니라 전달인자에 값을 주면 된다.
예제를 통해 보면 아래와 같이 쓸 수 있다.

import UIKit

func helloFunction(from name1: String, to name2: String) -> String {
    return "\(name1) says hi to \(name2)"
}

print(helloFunction(from: "Mei", to: "Vina"))

전달인자를 사용하면 마치 우리가 평소에 말하는 것과 같이 함수를 호출할 수 있다.
위와 같은 예제를 보면, from Mei Mei로 부터 to Vina Vina에게 이렇게 표현한 것을 볼 수 있다.
이렇게 하면 정말 말하는 것처럼 보여서 좀더 이해하기가 쉬울 수 있다.

전달인자가 없는 경우에는 그냥 와일드카드 식별자(_)를 사용하면 된다.
아래와 같이 하면 되는데 전달인자 레이블만 다르게 써줘도 오버로드(중복 정의)가 되는 것을 볼 수 있다.

import UIKit

func helloFunction(from name1: String, _ sayHi: String) -> String {
    return "\(name1) says \(sayHi)"
}

func helloFunction(from name1: String, to name2: String) -> String {
    return "\(name1) says hi to \(name2)"
}

print(helloFunction(from: "Mei", "Hello"))
print(helloFunction(from: "Mei", to: "Vina"))

📖 매개변수 기본값

함수에서 매개변수가 기본값을 가지는 경우는 아래와 같이 정의하면 된다.

import UIKit

func helloFunction(_ name: String, times: Int = 5) -> String {
    var result: String = ""
    
    for _ in 1...times {
        result += "Hello \(name) !" + "\n"
    }
    return result
}

print(helloFunction("Vina"))
print(helloFunction("Mei", times: 2))

위의 예제처럼 함수를 호출할때 매개변수를 재정의해도 된다.

✨ 매개변수 배치

주로 기본값이 없는 매개변수를 기본값이 있는 매개변수 앞에 사용한다.
이 이유는 기본값이 없는 매개변수가 대체로 함수에서 중요한 값을 전달할 가능성이 높기 때문이다.
하지만 기본값을 떠나서 그냥 중요한 매개변수를 앞쪽에 배치하는 것이 좋다.

📖 가변 매개변수

매개변수로 몇개의 값이 들어올지 모르는 경우 가변 매개변수를 사용하면 된다.
아래와 같이 친구들의 이름이 몇개가 들어오는지에 따라 인사말을 출력하는 함수 예제를 보자.

import UIKit

func helloFunction(me: String, friends: String...) -> String {
    var result: String = ""
    
    for friend in friends {
        result += "Hi \(friend) !" + "\n"
    }
    
    result += "I am \(me) !" + "\n"
    return result
}

print(helloFunction(me: "Vina", friends: "Hoho", "Jojo"))
print(helloFunction(me: "Mei"))

📖 입출력 매개변수

함수의 전달인자로 값을 전달하는 경우 보통 값을 복사해서 전달한다.
하지만 포인터처럼 값이 아니라 참조를 전달하려면 매개변수를 사용한다.
애플의 프레임워크에서는 객체지향 프로그래밍 패러다임을 사용하기에 유용할 수 있다.
하지만 애플 프레임워크를 벗어난 환경에서는 함수의 외부의 값에 어떤 영향을 줄지 모르기 때문에
함수형 프로그래밍에서는 지양하는 패턴이다.

입출력 매개변수의 전달 순서는 다음과 같다.

  1. 함수를 호출할 때, 전달인자 값을 복사한다.
  2. 해당 전달인자 값을 변경하면 1에서 복사한 것을 함수 내부에서 변경한다.
  3. 함수를 반환하는 시점에 2에서 변경된 값을 원래의 매개변수에 할당한다.

예제를 통해 살펴보자.

import UIKit

func nonRefPara(_ arr: [Int]) {
    var copiedArr: [Int] = arr
    copiedArr[1] = 1
}

func doRefPara(_ arr: inout [Int]) {
    arr[1] = 1
}

var nums: [Int] = [1, 2, 3, 4, 5]

nonRefPara(nums)
print(nums)

doRefPara(&nums)	// 참조 표시(&)
print(nums)

✨ 입출력 매개변수 주의

입출력 매개변수는 아래의 특성을 갖는다.

  • 기본값을 가질 수 없다.
  • 가변 매개변수로 사용될 수 없다.
  • 상수는 변경될 수 없으므로 입출력 매개변수의 전달인자로 사용될 수 없다.

입출력 매개변수는 잘 사용하면 괜찮은데, 잘못 사용하면 메모리 안전을 위협한다.
따라서 몇몇 사용에 제약이 있다는 사실을 알아두자.

✅ 반환과 함수 타입

반환과 함수 타입에 대하여 보자.
반환은 우리가 했던 그 반환 값을 말하는 거고,
함수 타입은 매개변수 타입과 반환 값의 타입에 따라 정해지는 그 함수의 타입을 말한다.
예제를 보는 것이 이해가 빠르니 예제를 보자.

반환이 없는 함수를 먼저 보자.
반환 값이 없다면, 아래처럼 그냥 -> 다음에 Void 를 쓰던가 아니면 생략해버리면 된다.
아래의 예제는 함수의 매개변수도 Void인 경우이다.
이런 경우는 (Void) -> Void 타입의 함수라고 한다.

import UIKit

func helloFunction() -> Void {
    print("Hello World")
}

func helloFunction2() {
    print("Hello World")
}

이번에는 가변 매개변수에서 했던 예제를 다시 가지고 왔다.
다음과 같은 예제의 경우에는 (String, String...) -> String 타입이 함수의 타입이다.

func helloFunction(from name1: String, to name2: String) -> String {
    return "\(name1) says hi to \(name2)"
}

📖 함수 타입의 사용

이렇게 함수의 타입이 있다는 것은 함수를 전달인자로 받거나, 반환 값으로 돌려줄 수도 있다는 의미다.
Swift는 일급 객체이기 때문에 상황에 맞는 함수를 전달인자로 넘겨 처리하거나 상황에 맞는 함수를 반환하는 것이 가능하다.

아래 예제를 통해 무슨 의미인지 이해할 수 있다.

import UIKit

typealias CalculateTwoNums = (Int, Int) -> Int

func addTwoNums(_ a: Int, _ b: Int) -> Int {
    return a+b
}

func multiTwoNums(_ a: Int, _ b: Int) -> Int {
    return a*b
}

var mathFunction: CalculateTwoNums = addTwoNums(_:_:)
print(mathFunction(1, 2))

mathFunction = multiTwoNums(_:_:)
print(mathFunction(2, 4))

이것을 잘 활용하면, 특정 조건에 따라 적절한 함수를 반환하는 함수도 만들 수 있다.

✅ 중첩 함수

중첩 함수는 말 그대로 중첩되어 있는 함수라는 뜻으로, 함수 안에 함수가 있다는 말이다.
중첩 함수에서 주의할 점은 함수가 중첩되어 사용되기에 함수 안의 함수는 모듈 어디서든 사용가능 하지 않고,
함수 블록 내에서만 사용이 가능하다.
즉, 중첩된 함수는 전역 함수가 아니라는 의미다.

예제를 통해 중첩 함수를 보자.
내가 보는 책에서는 아래와 같은 게임판을 예제로 들고 있다.

-3-2-10+1+2+3

원점이 0이고, 왼쪽으로는 음수, 오른쪽으로는 양수로 이루어진 게임판이다.
이 게임판에서 왼쪽으로 한칸 이동하는 함수와 오른쪽으로 한칸 이동하는 함수를 구현하는 예제이다.

우선 중첩 함수는 생각하지말고 구현해보자.

import UIKit

// (Int) -> Int 타입의 MoveFunc 선언
typealias MoveFunc = (Int) -> Int

// 왼쪽으로 한칸 이동하는 moveLeft
func moveLeft(_ currentPosition: Int) -> Int {
    return currentPosition-1
}

// 오른쪽으로 한칸 이동하는 moveRight
func moveRight(_ currentPosition: Int) -> Int {
    return currentPosition+1
}

// 왼쪽으로 움직여야 하는지 오른쪽으로 움직여야 하는지에 따라 moveLeft 또는 moveRight 함수를 반환 값으로 줌
func moveFunction(_ needToMoveLeft: Bool) -> MoveFunc {
    return needToMoveLeft ? moveLeft(_:) : moveRight(_:)
}

var position: Int = 3

let moveToZero: MoveFunc = moveFunction(position>0)

print("Start from \(position)")
while position != 0 {
    position = moveToZero(position)
    print("\(position)")
}
print("Finish !!")

책에 나온 예제는 위와 같다.

이 예제에서 moveLeftmoveRight 의 경우 이동하는 방향에 대한 함수인데, 모듈 전체에서 필요하지 않다.
그냥 moveFunction 에서만 필요한 함수들이다.
따라서 이 예제를 위에 있는 moveLeftmoveRight
moveFunction 안쪽으로 배치하여 중첩 함수로 바꾸어 보자.

import UIKit

typealias MoveFunc = (Int) -> Int

func moveFunction(_ needToMoveLeft: Bool) -> MoveFunc {
    
    func moveLeft(_ currentPosition: Int) -> Int {
        return currentPosition-1
    }

    func moveRight(_ currentPosition: Int) -> Int {
        return currentPosition+1
    }
    
    return needToMoveLeft ? moveLeft(_:) : moveRight(_:)
}

var position: Int = -5

let moveToZero: MoveFunc = moveFunction(position>0)

print("Start from \(position)")
while position != 0 {
    position = moveToZero(position)
    print("\(position)")
}
print("Finish !!")

위의 예제는 너무 간단해서 음 뭐지 그냥 똑같아 보이는데 싶을 수 있다.
하지만 이런식으로 하면 전역함수가 많은 프로젝트와 같은 경우에서
불필요한 함수의 사용 범위를 깔끔하게 정리할 수 있다.

✅ 비반환 함수

Swift에는 비반환 함수( 또는 비반환 메서드)가 있다.
함수의 반환 값이 Void인 경우와는 다르다.
이 경우는 비정상적으로 끝나는 함수를 뜻한다.
이 함수가 실행되면, 프로세스 동작은 끝났다고 볼 수 있다.
주로 오류 보고를 하는 경우에 사용된다.

반환 타입은 Never 를 사용한다.
Never 타입이 사용되는 대표적인 예시는 fatalError 함수가 있다고 한다.
아래 예제를 통해 보자.

import UIKit
import Foundation

func sthWrongFunc() -> Never {
    fatalError("Hey, Something Wrong here !!")
}

func myHome(isEverythingOK: Bool) {
    guard isEverythingOK else {
        print("Warning !!")
        sthWrongFunc()
    }
    print("Everything is OK!")
}

myHome(isEverythingOK: true)
myHome(isEverythingOK: false)

✅ 반환 값을 무시

함수의 반환 값이 꼭 필요하지 않은 경우도 있다.
그런데 프로그래머가 의도적으로 함수의 반환 값을 사용하지 않을 경우 컴파일러가 경고를 보낼 수 있다.
따라서 이런 경우에는 함수의 반환 값을 무시해도 된다는 @discardableResult 선언 속성을 사용하면 된다.

이 속성은 Swift 표준 라이브러리 메서드에서도 종종 사용한다.
어떤 상황에서 해당 속성을 사용하는지 라이브러리에 구현된 함수나 메서드를 살펴보면 좋다.



📌 마무리

정말이지 천천히 천천히,, 함수 정리를 마쳤다. (무려 3일에 걸친 정리)
사실,, 맘 잡으면 한두시간이면 했을 것을,,
대학원 개강이 일주일도 안남은 시점에서,, 주말에 좀 놀아줬다.
솔직히 먹고 살자고 하는 건데, 놀건 놀아야지.
시험기간에도 맥주 한캔은 마셔주자는 마인드기에 암튼 잘 놀고 왔다.
이번주는 약간 쉬엄쉬엄 공부할 예정.

얼레벌레 일단 함수까지 정리 했으니까,
이제 Swift로 간단한 앱을 만들어 보는 강의들을 무작정 따라하며 남은 책 내용도 정리할 생각이다.
다음 포스팅은 아마도 옵셔널,, 난 진짜 옵셔널이 뭔지 이해가 안가더라,,
옵셔널도 조만간 정리 한번 하면서 완벽하게 이해하자.

그럼 오늘도 이번주도 아자아자 💎✨




참고
- 야곰, 『스위프트 프로그래밍 3판』, 한빛미디어, 2021.04
- the swift programming language swift 5.7
- Swift | Apple Developer Documentation
profile
아자아자 코딩라이푸 ٩(๑❛ʚ❛๑)۶

1개의 댓글

comment-user-thumbnail
2023년 12월 17일

정리가 잘 되어있어서 전달인자 레이블에서 헷갈렸던 부분을 공부하는데 도움이 많이 되었습니다. 감사합니다!

답글 달기