본문 바로가기
iOS/SWIFT

[Swift] Chapter 07. 함수

by 원만사 2021. 10. 8.
반응형

 

7.1 함수와 메서드


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

즉, 함수가 위치하거나 사용되는 범위 등에 따라 호칭이 달라질 뿐 기본적으로 같다.

 

7.2 함수의 정의와 호출


조건문이나 반복문 같은 스위프트의 다른 문법과 달리 함수에서는 소괄호를 생략할 수 없다.

재정의(오버라이드)와 중복 정의(오버로드)를 모두 지원한다. 따라서 매개변수의 타입이 다르면 같은 이름의 함수를 여러 개 만들 수 있고, 매개변수의 개수가 달라도 같은 이름의 함수를 만들 수 있다.

 

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

func 키워드를 이용해서 정의한다. 반환 타입을 명시하기 전에는 → 를 사용하여 어떤 타입이 반환될 것인지 명시해준다. 반환을 위한 키워드는 return이다. 함수의 기본 형태는 다음과 같다.

func 함수 이름(매개변수...) -> 반환 타입 {
    실행 구문
    return 반환 값
}

 

/*
	코드 7-1 기본 형태의 함수 정의와 사용
*/

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

let helloJenny: String = hello(name: "Jenny")
print(helloJenny) // Hello Jenny!

func introduce(name: String) -> String {
    "제 이름은 " + name + "입니다."
}

let introduceJenny: String = introduce(name: "Jenny")
print(introduceJenny) // 제 이름은 Jenny입니다.

 

[코드 7-1]의 introduct: 함수에서 보듯이 return 키워드를 생략할 수도 있다. 함수 내부의 코드가 단 한 줄의 표현이고, 그 표현의 결괏값의 타입이 함수의 반환 타입과 일치한다면 return 키워드를 생략해도 그 표현의 결괏값이 함수의 반환값이 된다.

 

7.2.2 매개변수

매개변수가 없는 함수와 매개변수가 여러 개인 함수

함수에 매개변수가 필요 없다면 매개변수 위치를 공란으로 비워둔다.

/*
	코드 7-2. 매개변수가 없는 함수 정의와 사용
*/

func helloWorld() -> String {
    return "Hello, world!"
}

print(helloWorld())

 

매개변수가 여러 개 필요한 함수를 정의할 때는 쉼표(,)로 매개변수를 구분한다. 주의할 점은 함수를 호출할 때, 매개변수 이름을 붙여주고 콜론(:)을 적어준 후 전달인자를 보내준다는 점이다. 이렇게 호출 시에 매개변수에 붙이는 이름을 매개변수 이름(Parameter Name)이라고 한다.

 

/*
	코드 7-3. 매개변수가 여러 개인 함수의 정의와 사용
*/9

func sayHello(myName: String, yourName: String) -> String {
    return "Hello \(yourName)! I'm \(myName)"
}

print(sayHello(myName: "yagom", yourName: "Jenny")) // Hello Jenny! I'm yagom

 

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

[코드 7-3]에서 sayHello(myName: yourName:) 함수를 호출할 때 myName과 yourName이라는 매개변수 이름을 사용했다. 매개변수 이름과 더불어 전달인자 레이블(Argument Label)을 지정해줄 수 있다. 보통 함수를 정의할 때 둘을 같은 이름으로 사용할 수 있지만 전달인자 레이블을 별도로 지정하면 함수 외부에서 매개변수의 역할을 좀 더 명확히 할 수 있다. 전달인자 레이블은 함수 정의에서 매개변수 이름 앞에 한 칸을 띄운 후 전달인자 레이블을 지정하여 사용한다.

 

func 함수 이름(전달인자 레이블 매개변수 이름: 매개변수 타입, 전달인자 레이블 매개변수 이름: 매개변수 타입..) -> 반환 타입 {
    실행 구문
    return 반환 값
}

 

/*
	코드 7-4. 매개변수 이름과 전달인자 레이블을 가지는 함수 정의와 사용
*/

// from과 to라는 전달인자 레이블이 있으며
// myName과 name이라는 매개변수 이름이 있는 sayHello 함수
func sayHello(from myName: String, to name: String) -> String {
    return "Hello \(name)! I'm \(myName)"
}

print(sayHello(from: "yagom", to: "Jenny")) // Hello Jenny! I'm yagom

함수 내부에서 전달인자 레이블을 사용할 수 없으며, 함수를 호출할 때는 매개변수 이름을 사용할 수 없다.

전달인자 레이블을 사용하고 싶지 않다면 와일드카드 식별자를 사용하면 된다. [코드 7-5]는 와일드카드 식별자를 사용하여 전달인자 레이블을 사용하지 않는 함수를 구현한 것이다.

 

/*
	코드 7-5. 전달인자 레이블이 없는 함수 정의와 사용
*/

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

print(sayHello("Chope", 2)) // Hello Chope! Hello Chope!

 

또, 전달인자 레이블을 변경하면 함수의 이름 자체가 변경된다. 그렇기 때문에 전달인자 레이블만 다르게 써주더라도 함수 중복 정의(오버로드)로 동작할 수 있다. 전달인자 레이블을 사용하는 경우 매개변수 이름은 함수의 이름에 포함되지 않으므로 매개변수 이름과 타입이 같은 함수를 매개변수 이름만 바꿔서 중복 정의할 수 없다.

 

/*
	코드 7-6. 전달인자 레이블 변경을 통한 함수 중복 정의
*/

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

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

print(sayHello(to: "Chope", 2))
print(sayHello(to: "Chope", repeatCount: 2))

 

매개변수 기본값

매개변수마다 기본값을 지정할 수 있다. 즉, 매개변수가 전달되지 않으면 기본값을 사용한다. 매개변수 기본값이 있는 함수는 함수를 중복 정의한 것처럼 사용할 수 있다.

/*
	코드 7-7. 매개변수 기본값이 있는 함수의 정의와 사용
*/

// times 매개변수가 기본값 3을 갖는다.
func sayHello(_ name: String, times: Int = 3) -> String {
    var result: String = ""
    
    for _ in 0..<times {
        result += "Hello \(name)!" + " "
    }
    
    return result
}

// times 매개변수의 전달 값을 넘겨주지 않아 기본값 3을 반영해서 세 번 출력한다.
print(sayHello("Hana"))


// times 매개변수의 전달 값을 2로 넘겨주었기 때문에 전달 값을 반영해서 두 번 출력한다.
print(sayHello("Joe", times: 2))

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

 

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

매개변수로 몇 개의 값이 들어올지 모를 때, 가변 매개변수를 사용할 수 있다. 가변 매개변수는 0개 이상(0개 포함)의 값을 받아올 수 있으며, 가변 매개변수로 들어온 인자 값은 배열처럼 사용할 수 있다. 함수마다 가변 매개변수는 하나만 가질 수 있다.

 

/*
	코드 7-8. 가변 매개변수를 가지는 함수의 정의와 사용
*/

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: "yagom", friends: "Joe", "Jenny", "Choi"))
// Hello Joe! Hello Jenny! Hello Choi! I'm yagom!

함수의 전달인자로 값을 전달할 때는 보통 값을 복사해서 전달한다. 값이 아닌 참조를 전달하려면 입출력 매개변수를 사용한다. 값 타입 데이터의 참조를 전달인자로 보내면 함수 내부에서 참조하여 원래 값을 변경한다.

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

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

 

연산 프로퍼티 또는 감시자가 있는 프로퍼티가 입출력 매개변수로 전달된다면, 함수 호출 시점에 그 프로퍼티의 접근자가 호출되고 함수의 반환 시점에 프로퍼티의 설정자가 호출된다.

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

 

/*
	코드 7-9. inout 매개변수의 활용
*/

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

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

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

nonReferenceParameter(numbers)
print(numbers[1]) // 2

referenceParameter(&numbers) // 참조를 표현하기 위해 &를 붙여준다.
print(numbers[1]) // 1

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

 

7.2.3 반환이 없는 함수

값의 반환이 굳이 필요하지 않는 함수는 반환 타입을 '없음'을 의미하는 Void로 표현하거나 아예 반환 타입 표현을 생략해도 된다. 즉, 반환 타입이 Void이거나 생략되어 있다면 반환 값이 없는 함수이다.

 

/*
	코드 7-10. 반환 값이 없는 함수의 정의와 사용
*/

func sayHelloWorld() {
    print("Hello, world!")
}
sayHelloWorld() // Hello, world!

func sayHello(from myName: String, to name: String) {
    print("Hello \(name)! I'm \(myName)")
}
sayHello(from: "yagom", to: "Mijeong") // Hello Mijeong! I'm yagom

func sayGoodBye() -> Void {
    print("Good bye")
}
sayGoodBye() // Good bye

 

7.2.4 데이터 타입으로서의 함수

스위프트의 함수는 일급 객체이므로 하나의 데이터 타입으로 사용할 수 있다. 즉, 각 함수는 매개변수 타입과 반환 타입으로 구성된 하나의 타입으로 사용(정의)할 수 있다. 함수를 하나의 데이터 타입으로 나타내는 방법은 아래와 같다.

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

 

func sayHello(name: String. times: Int) -> String {
    // ...
}

위의 sayHello 함수의 타입은 (String, Int) → String이다.

 

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

위의 sayHelloToFriends 함수의 타입은 (String, String...) → String 이다. 만약 매개변수나 반환 값이 없다면 Void 키워드를 사용하여 없음을 나타낸다.

 

func sayHelloWorld() {
    // ....
}

sayHelloWorld 함수의 타입은 (Void) → Void이다. Void 키워드는 빈 소괄호의 묶음으로 표현할 수도 있다. 다음 표현은 모두 (Void) → Void와 같다.

  • (Void) → Void
  • () → Void
  • () → ()

 

함수를 데이터 타입으로 사용할 수 있다는 것은 함수를 전달인자로 받을 수도, 반환 값으로 돌려줄 수도 있다는 의미이다. 상황에 맞는 함수를 전달인자로 넘겨 적절히 처리할 수도 있으며 상황에 맞는 함수를 반환해주는 것도 가능하다는 뜻이다.

/*
	코드 7-11. 함수 타입의 사용
*/

typealias CalculateTwoInts = (Int, Int) -> Int

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

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

var mathFunction: CalculateTwoInts = addTwoInts

// var mathFunction: (Int, Int) -> Int = addTwoInts와 동일한 표현
print(mathFunction(2, 5))  // 2 + 5 = 7

mathFunction = multiplyTwoInts
print(mathFunction(2, 5))  // 2 * 5 = 10

 

[코드 7-12]처럼 전달인자로 넘겨줄 수도 있다.

/*
	코드 7-12. 전달인자로 함수를 전달받는 함수
*/

typealias CalculateTwoInts = (Int, Int) -> Int

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

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

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

printMathResult(addTwoInts, 3, 5) // Result: 8

 

[코드 7-13]처럼 반환 값으로 함수를 반환할 수도 있다.

/*
	코드 7-13. 특정 조건에 따라 적절한 함수를 반환해주는 함수
*/


func chooseMathFunction(_ toAdd: Bool) -> CalculateTwoInts {
    return toAdd ? addTwoInts : multiplyTwoInts
}


printMathResult(chooseMathFunction(true), 3, 5) // Reuslt: 8

 

Tip. 전달인자 레이블과 함수 타입 전달인자 레이블은 함수 타입의 구성요소가 아니므로 함수 타입을 작성할 때는 전달인자 레이블을 써줄 수 없다. let someFunction: (lhs: Int, rhs: Int) → Int // 오류 let someFunction: (_ lhs: Int, _ rhs: Int) → Int // OK let someFunction: (Int, Int) → Int // OK

 

 

7.3 중첩 함수


스위프트는 데이터 타입의 중첩이 자유롭다. 예를 들어 열거형 안에 또 하나의 '열거형'이 들어갈 수 있고 클래스 안에 또 다른 '클래스'가 들어올 수 있다. 함수의 중첩이라 함은 함수 안에 함수를 넣을 수 있다는 의미이다. 함수는 특별한 위치에 속해 있지 않는 한 모두 전역함수이다. 그러나 함수 안의 함수로 구현된 중첩 함수는 상위 함수의 몸통 블록 내부에서만 사용할 수 있다. 물론 중첩 함수를 담은 함수가 중첩 함수를 반환하면 외부에서도 사용할 수 있다.

 

/*
	코드 7-14. 원점으로 이동하기 위한 함수
*/

typealias MoveFunc = (Int) -> Int

func goRight(_ currentPosition: Int) -> Int {
    return currentPosition + 1
}

func goLeft(_ currentPosition: Int) -> Int {
    return currentPosition - 1
}

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

var position: Int = 3 // 현재 위치

// 현 위치가 0보다 크므로 전달되는 인자 값은 true가 된다.
// 그러므로 goLeft(_:) 함수가 할당될 것이다.
let moveToZero: MoveFunc = functionForMove(position > 0)
print("원점으로 갑시다.")


// 원점에 도착하면(현 위치가 0이면) 반복문이 종료된다.
while position != 0 {
    print("\(position)...")
    position = moveToZero(position)
}

 

왼쪽으로 이동하는 함수와 오른쪽으로 이동하는 함수의 사용 범위를 한정하고자 함수를 하나의 함수 안쪽으로 배치하여 중첩 함수로 구현하고, 필요할 때만 외부에서 사용할 수 있도록 구현해보자.

 

/*
	코드 7-15. 중첩 함수의 사용
*/

typealias MoveFunc = (Int) -> Int

func functionForMove(_ shouldGoLeft: Bool) -> MoveFunc {
    func goRight(_ currentPosition: Int) -> Int {
        return currentPosition + 1
    }
    
    func goLeft(_ currentPosition: Int) -> Int {
        return currentPosition - 1
    }
    
    return shouldGoLeft ? goLeft : goRight
}

var position: Int = -4 // 현재 위치

// 현 위치가 0보다 작으므로 전달되는 인자 값은 false가 된다.
// 그러므로 goRight(_:) 함수가 할당될 것이다.
let moveToZero: MoveFunc = functionForMove(position > 0)
print("원점으로 갑시다.")


// 원점에 도착하면(현 위치가 0이면) 반복문이 종료된다.
while position != 0 {
    print("\(position)...")
    position = moveToZero(position)
}

print("원점 도착!")

위와 같이 전역으로 사용이 불필요한 함수의 사용범위를 조금 더 명확하고 깔끔하게 표현해줄 수 있다.

 

7.4 종료되지 않는 함수


종료되지 않는다는 의미는 정상적으로 끝나지 않는 함수라는 뜻이다. 이를 비봔한 함수(Nonreturning function) 또는 비반환 메서드(Nonreturning method)라고 한다. 비반환 함수는 정상적으로 끝날 수 없는 함수이다. 이 함수를 실행하면 프로세스 동작은 끝났다고 볼 수 있다.

비반환 함수 안에서는 오류를 던진다는가, 중대한 시스템 오류를 보고하는 등의 일을 하고 프로세스를 종료한다. 어디서든 호출이 가능하고 guard 구문의 else 블록에서도 호출할 수 있다. 비반환 메서드는 재정의는 할 수 있지만 비반환 타입이라는 것은 변경할 수 없다.

반환 타입은 Never라고 명시해주면 된다.

 

/*
	코드 7-16. 비반환 함수의 정의와 사용
*/

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) // 마을에 도둑이 들었습니다!
// 프로세스 종료 후 오류 보고

 

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


가끔 함수의 반환 값이 꼭 필요하지 않은 경우도 있다. 함수의 반환 값을 무시해도 된다는 @discardableResult 선언 속성을 사용하면 컴파일러가 함수의 반환 값을 사용하지 않았다는 경고를 보내지 않는다.

 

/*
	코드 7-17. @discardableResult 선언 속성 사용
*/

func say(_ something: String) -> String {
    print(something)
    return something
}

@discardableResult func discadableResultSay(_ something: String) -> String {
    print(something)
    return something
}

// 반환 값을 사용하지 않으므로 컴파일러가 경고를 표시할 수 있다.
say("hello") // hello

// 반환 값을 사용하지 않을 수 있다고 미리 알렸기 때문에
// 반환 값을 사용하지 않아도 컴파일러가 경고하지 않는다.
discadableResultSay("hello") // hello

 

 

스위프트 프로그래밍: Swift 5 - 교보문고

객체지향, 함수형, 프로토콜 지향 패러다임까지 한 번에! | 스위프트를 제대로 이해하고 싶은 개발자를 위한 책스위프트는 iOS와 macOS용 애플리케이션 개발에 주로 사용하는 프로그래밍 언어입니

www.kyobobook.co.kr

 

반응형

'iOS > SWIFT' 카테고리의 다른 글

[Swift] Chapter 09. 구조체와 클래스  (0) 2021.10.08
[Swift] Chapter 08. 옵셔널  (0) 2021.10.08
[Swift] Chapter 06. 흐름 제어  (0) 2021.10.08
[Swift] Chapter 05. 연산자  (0) 2021.10.08
[Swift] Chapter 04. 데이터 타입 고급  (0) 2021.10.08

댓글