본문 바로가기

문법

[Swift] Functions의 개념과 활용점 알아보기!

주의 : Swift프로그래밍 3판 야곰책을 참고했으며 배우는 과정이기에 오탈자나 정확하지않는 정보가 있을 수 있습니다.

 

카테고리
- 함수란
- 기본적인 함수의 정의와 호출
- 매개변수
- 매개변수 이름과 전달인자 레이블
- 전달인자 레이블 변경을 통한 함수 중복정의
- 매개변수 기본값
- 가변 매개변수와 입출력 매개변수
- 반환이 없는 함수
- 데이터 타입으로서의 함수
- 중첩함수
- 종료되지않는 함수
- 반환값을 무시할 수 있는 함수

 

함수란(= 가장 작은 단위이자 하나의 작은프로그램)

함수 대부분은 작업의 가장 작은 단위이자 하나의 작은프로그램이다.

'하나의 프로그램은 하나의 큰 함수다' 라는 말이 있듯이, 함수는 프로그램을 이루는 중요한 요소 중 하나이다.

**스위프트에서 함수는 일급객체이기 때문에 하나의 값으로도 사용할 수 있다.**

 

여기서 잠깐 일급객체란?

1. 변수나 상수에 저장 및 할당 할 수 있어야 한다.

2. 파라미터로 전달할 수 있어야한다.

3. 함수에서 return할 수 있어야한다.

 

함수와 메서드는 기본적으로 같은 말이다. 상황이나 위치에 따라 다른 용어로 부르는 것일 뿐

구조체, 클래스, 열거형 등 특정 타입에 연관되어 사용하는 함수를 메서드 라고 부르고

모듈전체에서 전역적으로 사용할 수 있는 함수를 그냥 함수라고 부른다.

즉, 함수가 위치하거나 사용되는 범위 등에 따라 호칭이 달라질 뿐, 함수라는 것 자체에는 변함이 없다

 

함수 사용시 참고할 부분

- () 소괄호 생략 불가

- 재정의와 중복 정의를 모두 지원함, 따라서 타입이 다르면 같은 이름의 함수를 여러개 만들 수 있고

  매개변수의 개수가 달라도 같은 이름의 함수를 만들 수 있다. (=같은 함수명이라도 성질이 다르면 컴파일 오류 발생하지않음)

 

기본적인 함수의 정의와 호출

스위프트의 함수는 다른언어에 비해 자유도가 상대적으로 높은 언어이다.

기본으로 이름과 매개변수, 반환타입등을 사용해 함수를 정의함

func sayGreet(message: String) -> String {
    return "\(message)!"
}

let korean: String = sayGreet(message: "안녕하세요")
print(korean)

// 결과값과 반환값의 타입이 일치하며 그게 한줄이라면 return을 생략할 수 있다. (아래참고)
func introduce(name: String) -> String {
    "제 이름은 " + name + "입니다"
}

 

매개변수(Parameter) - 함수를 정의할 때 외부로부터 받아들이는 전달 값의 이름을 의미한다.

전달인자(Argument) 혹은 인자 - 함수를 실제로 호출할 때 전달하는 값을 의미한다.

ex) introduce(name: "쿼카") name은 매개변수 , 쿼카는 인자임

 

매개변수를 어떻게 정의하냐에 따라서 함수의 모습도 달라질 수 있다.

//함수에 매개변수가 필요없다면 매개변수 위치를 공란으로 비워둔다.
func eat() -> String {
    "밥을 먹다."
}
print(eat())

매개변수

함수호출시 매개변수 이름을 부여주고 콜론(:)을 적어준 후 전달인자를 보내준다는 점이다.

이렇게 호출 시에 매개변수에 붙이는 이름을 매개변수 이름(Parameter Name)이라고 부른다.

 

func registerSubject(name: String, subject: String) -> String {
    "성함: \(name), 등록과목: \(subject)로 등록되었습니다."
}

print(registerSubject(name: "쿼카", subject: "영어"))

매개변수 이름과 전달인자 레이블

registerSubject(name: , subject: )를 호출할때 name 과 subject라는 매개변수를 사용했따.

매개변수(Parameter Name) 이름과 더불어 전달인자레이블(Argument Label)을 지정해줄 수 있다.

보통 함수를 정의할 때 매개변수를 정의하면 매개변수 이름과 전달인자 레이블을 같은 이름으로 사용할 수 있지만

전달인자 레이블을 별도로 지정하면 함수 외부에서 매개변수의 역할을 좀 더 명확히 할 수 있다. 전달인자 레이블을

사용하려면 함수 정의에서 매개변수 이름안에 한칸을 띄운 후 전달인자 레이블을 지정한다.

func sayHello(from myName: String, to name: String) -> String {
    "안녕 \(name)! 난 \(myName)라고해 만나서 반가워"
}
print(sayHello(from: "쿼카", to: "캥거루"))

 

func sayHello(_ name: String, _ times: Int) -> String {
    var result: String = ""
    
    for _ in 0..<times {
        result += "Hello \(name)! " + ""
    }
    return result
}

print(sayHello("쿼카", 3))

 

전달인자 레이블을 사용하고 싶지않다면 와일드 카드 식별자를 사용하세요

와일드 카드 식별자를 사용하여 전달인자 레이블을 사용하지 않는 함수를 구현한 것

또, 전달인자 레이블을 변경하면 함수의 이름 자체가 변경된다.

그렇기 때문에 전달인자 레이블만 다르게 써주더라도 함수 종복 정의(오버로드)로 동작할 수 있다.

 

전달인자 레이블 변경을 통한 함수 중복정의

func sleepTimer( _ minute: String, _ message: String) -> String {
    "\(minute)뒤에 " + "\(message) 라는 메세지와함께 알람이 울립니다."
}

func sleepTimer(_ hour: Int, _ message: String) -> String {
    "\(hour)시간뒤에 " + "\(message) 라는 메세지와 함께 알람이 울립니다."
}

print(sleepTimer("30", "코딩문제풀어야함."))
print(sleepTimer(30, "코딩문제풀어야함"))

 

매개변수는 다르지만 같은 함수명의 매개변수 타입이 같으면 invalid로 오류가난다. 컴파일러 입장에서는 타입이 다르면 확실한데

타입은 같고 매개변수만 다르면 헷갈리는 것 같다.

 

매개변수 기본값

스위프트함수에서는 매개변수마다 기본값을 지정할 수 있다. 즉, 매개변수가 전달되지않으면 기본값을 사용한다.

매개변수 기본값이 있는 함수는 중복 정의한 것처럼 사용할 수 있다.

func sleepTimerHaveDefault(hour: Int = 7, message: String) -> String {
    "\(hour) 시간뒤에 " + "\(message)라는 메세지와 함께 알림이 울립니다."
}

print(sleepTimerHaveDefault(message: "쿼카일어나세요"))

 

기본값이 없는 매개변수를 기본값이 있는 매개변수 앞에 사용하세요.

기본값이 없는 매개변수는 대체로 함수를 사용함에 있어 중요한 값을 전달할 가능성이 높습니다.

무엇보다 기본값이 있는지와 상관 없이 중요한 매개변수는 앞쪽에 배치하는 것이 좋다.

 

가변 매개변수와 입출력 매개변수

 

매개변수로 몇 개의 값이 들어올지 모를때 ,가변 매개변수를 사용할 수 있다. 가변 매개변수는 0개이상의 값을 받아올 수 있으며

가변 매개변수로 들어온 인자값은 배열처럼 사용할 수 있습니다. 함수마다 가변매개변수는 하나만 가질 수 있습니다.

func sayHelloToFriends(me: String, friends names: String...) -> String {
    var result: String = ""
    
    for friend in names {
        result += "Hello \(friend)!" + ""
    }
    result += " I'm " + me + "!"
    return result
}

print(sayHelloToFriends(me: "quokka", friends: "Kangaru"), "Nice To Meet you")

 

inout 입출력 매개변수(이해도 낮음)

함수의 전달인자로 값을 전달할때는 보통 값을 복사해서 잔달합니다. 값이 아닌 참조*를 전달하려면

입출력(inout) 매개변수를 사용합니다. 값 타입 데이터의 참조를 전달인자로 보내면 함수 내부에서 참조하여

원래 값을 변경합니다. C언어의 포인터와 유사합니다. 하지만 이 방법은 함수 외부의 값에 어떤 영향을 줄지

모르기때문에 함수형 프로그래밍 패터다임에서 지양하는 패턴이다. 물론 객체 지향 프로그래밍 패러타딤에서는 종종

사용됩니다. 애플의 프레임워크(iOS, macOS등)에서는 객체지향 프로그래밍 패러다임을 사용하므로 유용할 수 있지만,

애플 프레임워크를 벗어난 다른 환경에서 함수형 프로그래밍 패러다임을 사용할 때는 입출력 매개변수를 사용하지 않는것이 좋다.

 

1. 함수를 호출할 때, 전달인자의 값을 복사한다.

2. 해당 전달인자의 값을 변경하면 1에서 복사한 것을 함수내부에서 변경한다.

3. 함수를 반환하는 시점에 2에서 변경된 값을 원래의 매개변수에 할당한다.

 

연산 프로퍼티 또는 감시자가 있는 프로퍼티가 입출력 매개변수로 전달된다면, 함수 호출 시점에 그

프로퍼티의 접근자가 호출되고 함수의 반환 시점에 프로퍼티의 설정자가 호출된다. 이에 대한 내용은

프로퍼티 감시자에서도 확인 할 수 있다.

 

참조는 inout 매개변수로 전달된 변수 또는 상수 앞에 앰퍼샌드(&)를 붙여서 표현합니다.

 

var number: [Int] = [1,2,3]

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

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

nonReferenceParameter(number)
print(number[0])
print(number[1])
print(number[2])

referenceParameter(&number)
print(number[0])
print(number[1])
print(number[2])

// String값도 활용해보자.

var quokkaFamily = ["아빠쿼카","오빠쿼카","누나쿼카","형쿼카"]

func printFamilyName(name: inout [String]) {
    name[1] = "쿼카"
}

printFamilyName(name: &quokkaFamily)
print(quokkaFamily)

중간 포인트 정리

- 함수의 매개변수는 상수이기때문에 값을 변경할 수 없다.

- 입출력 매개변수는 매개변수 기본값을 가질 수 없으며, 가변 매개변수로 사용될 수 없다.

  또한 상수는 변경될 수 없으므로 입출력 매개변수의 전달인자로 사용될 수 없다.

 

입출력 매개변수는 잘 사용하면 문제없지만 잘못 사용하면 메모리안전을 위협하기도 한다. 따라서 사용에 몇몇 제약이있어

알아본후에 사용하는것이 좋을것..

반환이 없는 함수

 

함수는 특정 연산을 실행한 후 결괏값을 반환한다. 그러나 값의 반환이 굳이 필요하지않은 함수도 있다.

그럴때는 반환값이 없는 함수를 만들어 줄 수 있다. 만약 반환값이 없는 함수라면 반환타입을 없을 을 의미하는 Void로

표기하거나 아예 반환 타입 표현을 생략해도된다. 즉, 반환 타입이 Void이거나 생략되어 있다면 반환 값이 없는 함수이다.

func sayMoringHello() -> Void {
    print("굿모닝입니다..")
}
sayMoringHello()
func sayAfternoonHello(name: String) {
    print("\(name)씨 식사맛있게 하세요.")
}
sayAfternoonHello(name: "쿼카")
func sayEveningHello() {
    print("굿밤입니다")
}
sayEveningHello()

 

 

데이터 타입으로서의 함수

 

앞서 언급했듯 스위프트의 함수는 입급 객체이므로 하나의 데이터타입으로 사용할 수 있다.

즉, 각 함수는 매개변수 타입과 반환 타입으로 구성된 하나의 타입으로 사용할 수 있다는 뜻이다.

함수를 하나의 데이터타입으로 나타내는 방법은 다음과 같다.

 

(매개변수 타입의 나열) -> 반환타입

 

예시를 들어보자

func sayHello(name: String, times: Int) -> String {
    ""
}
// sayHello함수의 타입은 (String, Int) -> String 입니다. 다음 함수도 살펴보자

func sayHelloToFriends(me: String, names: String...) -> String {
    ""
}
// sayHelloToFriends(String, String) -> String

func sayHelloWorld() {
    
}

//sayHelloWorld 함수의 타입은 (Void) -> Void입니다. 참고로 Void키워드를 빈 소괄호의 묶음으로 표현할 수도 있습니다.

// (Void) -> Void
// () -> Void
// () -> ()

 

함수의 축약 표현

함수의 데이터 타입과는 조금 무관한 이야기지만 함수를 간략히 표현하고자 할때 앞에서 소개한 축약 표현을 사용하기도 합니다.

앞의 sayHello(name: String, times: Int) -> String 함수 같은 경우는 sayHello(name: times:)와 같이

이름과 매개변수 개수 등을 이용해 함수를 표현할 수 있습니다. 앞으로 이 책의 각 부분에서 또는 다른 스위프트 문서에서도 이런

축약표현을 자주 볼것이다.

 

- 함수를 데이터타입으로 사용할 수 있다는 것은 타입을 지정하는 라인에 함수를 넣어 줄 수 있다는 얘기, 예시를 하단에 들어보자

- 상황에 맞는 함수를 전달인자로 넘겨 적절히 처리할 수도 있으며 상황에 맞는 함수를 반환해주는것도 가능하다는 뜻이다. 일급객체이기때

 

typealias CalculateTwoInts = (Int, Int) -> Int


func addTwoInts(_ a: Int, _ b: Int) -> Int {
    a + b
}
 
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
    a * b
}

func divideTwoInts(_ a: Int, _ b: Int) -> Int {
    a / b
}

var mathFunction: CalculateTwoInts = addTwoInts

// var mathFunction: (Int, Int) -> Int = addTwoInts의 동일한 표현입니다.
print(mathFunction(2, 5))

var multiplyNumber: CalculateTwoInts = multiplyTwoInts
print(multiplyNumber(5, 5))

var divideNumber: (Int, Int) -> Int = divideTwoInts
print(divideNumber(100, 5))

 

매개변수 타입과 반환타입

함수형 프로그래밍에서 특정 로직에 관여할 함수의 매개변수와 반환 타입은 매우 중요합니다.

- 타입 별칭을 통해 손쉽게 함수를 관리할 수 있으며 매개변수와 반환 타입만 잘 연계된다면 굉장히 흘룽한 패턴을 완성할 수 있다.

 

그리고 addTwoIns(_:_:)와 multiplyTwoInts(_:_:)라는 간단한 함수 두개를 만들었다.

두 함수는 변수 mathFunction에 번갈아 가면서 할당되거나 mathFunction이라는 이름으로 호출 할 수 도 있었다.

 

 

전달인자로 함수를 전달받는 함수

// 전달인자로 함수를 전달받는 함수

func printMathResult(_ mathFunction: CalculateTwoInts, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a,b))")
}

printMathResult(addTwoInts, 100, 200)

CalculateTwoInts타입이 (Int, Int) -> Int 이니깐 이와 같은 함수타입을 가진 값이나 메서드만 인자로 넣어 사용할 수 있다.

 

특정 조건에 따라 반환값으로 함수를 반환할 수 도 있다.

// 특정 조건에 따라 반환값으로 함수를 반환할 수 도 있다.
func chooseMathFunction(_ toAdd: Bool) -> CalculateTwoInts {
    return toAdd ? addTwoInts : multiplyTwoInts
}

printMathResult(chooseMathFunction(false), 10, 20)

TIP

전달인자 레이블과 함수 타입

전달인자 레이블은 함수 타입의 구성요소가 아니므로 함수 타입을 작성할 때는 전달인자 레이블을 써줄 수 없습니다.

let someFunction: (lhs: Int, rhs: Int) -> Int // 오류

let someFunction: (_ lhs: Int, _ rhs: Int) -> Int // OK

let someFunction: (Int, Int) -> Int           // OK

 

매개변수를 사용하지않을경우 함수 타입을 타입으로써 사용하기 적합해짐

중첩함수(이해도 낮음)

 

스위프트는 데이터 타입의 중첩이 자유롭다. 예를들어 열거형 안에 또 하나의 열거형 이 들어갈 수 있고 클래스 안에 또 다른 클래스가 들어올 수 있는 등 다른 프로그래밍 언어에서 생각하지 못했던 패턴을 자유롭게 만들어 볼 수 있습니다.

함수의 중첩은 함수안에 함수를 넣을 수 있다는 의미인데 우리가 앞서 살펴보았던 함수는 특별한 위치에 속해있지않는 한 모두 전역함수 였다.

즉, 모듈 어디서든 사용할 수 있는 함수라는 뜻이다. 그러나 함수안의 함수로 구현된 중첩함수는 상위 함수의 몸통 블록내부에서만 함수를 사용할 수 있다. 물론 중첩 함수의 사용 범위가 해당 함수 안쪽이라고 해서 아예 외부에서 사용할 수 없는 것은 아니다. 함수가 하나의 반환값으로 사용될 수 잇으므로 중첩함수를 담은 함수가 중첩 함수를 반환하면 밖에서도 사용할 수 있다.

typealias MoveFunc = (Int) -> Int

var position = -4

func goLeft(_ move: Int) -> Int {
    return (position - move)
}

func goRight(_ move: Int) -> Int {
    return (position + move)
}

func moveToZeroPoint(_ shouldGoLeft: Bool) -> MoveFunc {
    return shouldGoLeft ? goLeft : goRight
}

let moveToZero: MoveFunc = moveToZeroPoint(position > 0)
print("원점으로 돌아갑니다.")

while position != 0 {
    print("\(position)...")
    position = moveToZero(position)
    
}
print("원점도착!")

 

중첩함수 사용

typealias MoveFunc = (Int) -> Int

// 중첩함수 사용

func moveToZeroPoint(_ shouldGoLeft: Bool) -> MoveFunc {
    func goLeft(_ move: Int) -> Int {
        return (move + 1)
    }

    func goRight(_ move: Int) -> Int {
        return (move - 1)
    }
    return shouldGoLeft ? goLeft : goRight
}


var position = -4
let moveToZero: MoveFunc = moveToZeroPoint(position > 0)
print("원점으로 돌아갑니다.")


while position != 0 {
    print("\(position)...")
    position = moveToZero(position)
    
}
print("원점도착!")
//overflow

종료되지않는 함수(이해가 잘안됨)

 

스위프트에는 종료되지않는 함수가 잇습니다.

종료되지않는 다는 의미는 정상적으로 끝나지 않는 함수라는 의미이다. 이를 비반환 함수 또는 비반환 메서드 라고 한다.

비반환 함수는 정상적으로 끝날 수 없는 함수이다. 이 함수를 실행하면 프로세스 동작은 끝났다고 볼 수 있다.

왜 이런 이름을 붙이게 되었을까요 ? 비반환 함수 안에서는 오류를 던진다든가, 중대한 시스템 오류를 보고 하는 등의 일을

하고 프로세스를 종료해 버리기때문입니다. 비반환 함수는 어디에서든 호출이 가능하고 guard 구문의 else 블록에서도 호출할 수 있습니다.

비반환 메서드는 재정의 할 수 있지만 비반환 타입이라는 것은 변경할 수 없습니다.

비반환 함수는 반환 타입을 Never라고 명시해주시면 됩니다.

func crashAndBurn() -> Never {
    fatalError("Something very, very bad happened")
}

crashAndBurn() // 프로세스 종료후 오류보고

func someFunction(isAllIsWell: Bool) {
    guard isAllIsWell else {
        print("마을에 도둑이 들었습니다!")
        crashAndBurn()
    }
    print("All is well")
}

someFunction(isAllIsWell: true) // All is well
someFunction(isAllIsWell: false) // 마을에 도둑이 들었습니다.!

// Never 타입이 스위프트 표준 라이브러리에서 사용되는 대표적인 예로는 fatalError() 함수가 있습니다.

반환값을 무시할 수 있는 함수

 

가끔 함수의 반환 값이 꼭 필요하지 않는 경우도 있습니다. 프로그래머가 의도적으로 함수의 반환 값을

사용하지않을 경우 컴파일러가 함수의 결과 값을 사용하지 않았다는 경고를 보낼 때도 있습니다. 이런

경우 함수의 반환 값을 무시해도 된다는 @discardableResult 선언 속성을 사용하면됩니다.

func sayHallo(_ something: String) -> String {
    print(something)
    return something
}
// 반환 값을 사용하지 않았으므로 컴파일러가 경고를 표시할 수 있다.
sayHallo("안녕")

@discardableResult func sayHalloDiscardableResult(_ something: String) -> String {
    print(something)
    return something
}
// 반환값을 사용하지 않을 수 있다고 미리 알렸기때문에 컴파일러가 경고하지 않는다.
sayHalloDiscardableResult("Bye~~")

 

포인트정리

- 매개변수(Parameter) - 함수를 정의할 때 외부로부터 받아들이는 전달 값의 이름을 의미한다.

- 전달인자(Argument) 혹은 인자 - 함수를 실제로 호출할 때 전달하는 값을 의미한다.

- 무엇보다 기본값이 있는지와 상관 없이 중요한 매개변수는 앞쪽에 배치하는 것이 좋다.

- 스위프트의 함수는 입급 객체이므로 하나의 데이터타입 ex) let someFunction: (Int, Int) -> Int

- typealias로 타입의 이름을 변경해서 사용가능하다.

- 전달인자 레이블을 별도로 지정하면 함수 외부에서 매개변수의 역할을 좀 더 명확히 할 수 있다

- 전달인자 레이블을 사용할 필요없는 경우 와일드카드로 전달인자 레이블 생략이 가능하다.

- 축약표현이 가능하다 ex) sayHello(name: String, times: Int) -> String 함수 같은 경우는 sayHello(name: times:)와 같이

- 함수의 매개변수는 상수이기때문에 값을 변경할 수 없다.

- 입출력 매개변수는 매개변수 기본값을 가질 수 없으며, 가변 매개변수로 사용될 수 없다.

- 또한 상수는 변경될 수 없으므로 입출력 매개변수의 전달인자로 사용될 수 없다.

- 중첩함수를 담은 함수가 중첩 함수를 반환하면 밖에서도 사용할 수 있다.

- 함수의 반환 값을 사용하지않을 경우 무시해도 된다는 @discardableResult 선언 속성을 사용하면 된다.

 

참고책 - 야곰 Swift프로그래밍 3판