ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Chapter 5: Functions
    Raywenderlich/Swift Apprentice 2021. 9. 2. 12:01

    Version

    Swift 5.5, iOS 15, Xcode 13

     

    함수(functions)는 많은 프로그래밍 언어(programming languages)의 핵심 부분(core part)이다. 간단히 말해서(simply put), 함수(function)를 사용하면 작업(task)을 수행(performs)하는 코드 블록(block of code)을 정의(define)할 수 있다. 그런 다음(then), 앱(app)에서 해당 작업(task)을 실행(execute)해야 할 때마다(whenever) 동일한 코드를 모든 곳(everywhere)에 복사(pasting)하여 붙여넣는(pasting) 대신(instead of) 함수(function)를 실행(run)할 수 있다.

    이 장(chapter)에서는 자신만의 함수(functions)를 작성(write)하는 방법을 배우고, Swift에서 이를 어떻게 쉽게 사용(easy to use)하는지 직접(firsthand) 확인(see)할 것이다.

     

    Function basics

    이름(name)을 자주(frequently) 출력(print)해야 하는 앱(app)이 있다고 상상(imagine)해 본다. 이를 수행하는 함수(function)를 다음과 같이 작성(write)할 수 있다:

    func printMyName() {
      print("My name is Matt Galloway.")
    }

    위 코드를 함수 선언(function declaration)이라고 한다. func 키워드(keyword)를 사용하여 함수(function)를 정의(define)한다. 그 다음에 함수(function) 이름(name)이 오고, 그 뒤(followed by)에 괄호(parentheses)가 온다. 다음 섹션(section)에서 이러한 괄호(parentheses)의 필요성(need)에 대해 자세히 알아볼 것이다.

    괄호(parentheses) 뒤에는 여는 중괄호(opening brace)가 오고, 그 다음(followed by)에는 함수(function)에서 실행(run)하려는 코드, 그리고 닫는 중괄호(closing brace)가 이어 나온다(followed by). 함수(function)를 정의(defined)하면 다음과 같이 사용(use)할 수 있다:

    printMyName()

    이는 다음(following)을 출력(prints out)한다:

    My name is Matt Galloway.

    이전 장(previous chapters)에서 이미(already) 함수(function)를 사용한 것으로 의심(suspect)된다면 정답(correct)이다. 콘솔(console)에 제공한 텍스트(text)를 출력(prints)하는 print는 실제로(indeed) 함수(function)이다. 이는 데이터를 함수(function)에 전달(pass)하고 반환(return)하는 방법을 배우는 다음 섹션(section)으로 정확하게(nicely) 이어진다.

     

    Function parameters

    이전 예제(previous example)에서 함수(function)는 단순히(simply) 메시지(message)를 출력(prints out)한다. 훌륭하지만(great) 때로는(sometimes) 함수(function)를 매개변수화(parameterize)하여, 매개변수(parameters)로 전달된(passed into) 데이터에 따라(depending on) 다르게 수행(perform)되도록 할 수 있다.

    예(example)를 들어, 다음 함수(function)를 고려(consider)해 본다:

    func printMultipleOfFive(value: Int) {
      print("\(value) * 5 = \(value * 5)")
    }
    printMultipleOfFive(value: 10)

    함수(function) 이름(name) 뒤의 괄호(parentheses) 안에서, value라는 이름(named)과 유형(type)이 Int인 매개변수(parameter) 정의(definition)를 확인(see)할 수 있다. 모든 함수(function)의 괄호(parentheses)에는 매개변수 목록(parameter list)이 포함(contain)된다. 이러한 괄호(parentheses)는 매개변수 목록(parameter list)이 비어(empty) 있더라도 함수(function)를 선언(declaring)할 때와 호출(invoking)할 때 모두 필요(required)하다. 이 함수(function)는 주어진 수의 5의 배수(multiple)를 출력(print out)한다. 이 예제(example)에서는 인수(argument) 10을 사용하여 함수(function)를 호출하므로 다음을 출력(prints)한다:

    10 * 5 = 50
    Note: "매개변수(parameter)"와 "인수(argument)"라는 용어(terms)를 혼동(confuse)하지 않도록 주의한다. 함수(function)는 매개변수 목록(parameter list)에서 매개변수(parameters)를 선언(declares)한다. 함수(function)를 호출(call)할 때, 함수(function)의 매개변수(parameters)에 대한 인수(arguments)로 값(values)을 제공(provide)한다.

    한 단계(step) 더 나아가, 함수(function)를 좀 더 일반화(general)할 수 있다. 두 개의 매개변수(parameters)를 사용하여 함수(function)는 두 값(values)의 곱(multiple)을 출력(print out)할 수 있다.

    func printMultipleOf(multiplier: Int, andValue: Int) {
      print("\(multiplier) * \(andValue) = \(multiplier * andValue)")
    }
    printMultipleOf(multiplier: 4, andValue: 2)

    이제 함수(function) 이름(name) 뒤의 괄호(parentheses) 안에는 두 개의 매개변수(parameters)가 있다. 각각 이름이 multiplierandValue이며 둘 다 Int 유형(type)이다.

    함수(function)를 호출(call)할 때 매개변수 목록(parameter list)의 레이블(labels)을 인수(arguments)에 적용(apply)해야 한다. 위(above)의 예제(example)에서는 곱할 값(multiplier) 앞에 multiplier:를, 곱해질 값(value to be multiplied) 앞에 andValue: 를 넣어야(put) 한다.

    Swift에서는 함수(function) 호출(calls)이 문장(sentence)처럼 읽히도록 해야 한다. 위(above)의 예(example)에서는 다음과 같이 코드의 마지막 행(line)을 읽는다:

    Print multiple of multiplier 4 and value 2

    매개변수(parameter)에 다른(different) 외부 이름(external name)을 지정하여, 이를 더욱 명확(clearer)하게 할 수 있다. 예를 들어(for example), andValue 매개변수(parameter)의 이름(name)을 변경(change)할 수 있다:

    func printMultipleOf(multiplier: Int, and value: Int) {
      print("\(multiplier) * \(value) = \(multiplier * value)")
    }
    printMultipleOf(multiplier: 4, and: 2)

    매개변수 이름(parameter name) 앞에 다른 외부 이름(external name)을 할당(assign)한다. 이 예(example)에서 매개변수(parameter)의 내부 이름(internal name)은 이제 value이고, 함수 호출(function call)의 외부 이름(external name, 인수 레이블(argument label))은 이제 and이다. 새로운 호출(call)을 다음과 같이 읽을 수 있다:

    Print multiple of multiplier 4 and 2

    다음 도표(diagram)는 함수 선언(function declaration)에서의 외부(external) 및 내부(internal) 이름을 설명(explains)한다:

     

    이러한 개념(idea)의 배경(behind)은 함수 호출(function call)을 문장과 같은(sentence-like) 방식으로 읽을 수(readable) 있도록 하면서 여전히 함수(function) 자체에서 표현적인 이름(expressive name)을 갖도록 하는 것이다. 위 함수(function)를 다음과 같이 작성(written)할 수 있다:

    func printMultipleOf(multiplier: Int, and: Int)

    이는 읽기 좋은(readable) 문장(sentence)이 되야 하는 함수 호출(function call)에서도 동일한 효과(effect)를 낸다. 그러나(however), 이제 함수(function) 내부(inside)의 매개변수(parameter)도 and로 호출(called)된다. 긴 함수(function)에서는 일반적으로(generically) 이름이 지정된 매개변수(named parameter)를 사용하는 것이 혼란(confusing)스러울 수 있다.

    외부 이름(external name)을 전혀 사용하지 않으려면, 이전 장(chapters)에서 본 것처럼 밑줄(underscore) _를 사용(employ)하면 된다:

    func printMultipleOf(_ multiplier: Int, and value: Int) {
      print("\(multiplier) * \(value) = \(multiplier * value)")
    }
    printMultipleOf(4, and: 2)

    이러한 변경(change)으로, 호출 시(call site) 읽기(readable)가 훨씬 더 쉬워진다. 함수 호출(function call)은 이제 다음과 같이 읽는다(reads):

    Print multiple of 4 and 2

    원한다면(if you so wished), 더 나아가(even further) 모든 매개변수(parameters)에 대해 _를 사용할 수 있다:

    func printMultipleOf(_ multiplier: Int, _ value: Int) {
      print("\(multiplier) * \(value) = \(multiplier * value)")
    }
    printMultipleOf(4, 2)

    이 예제(example)에서 모든 매개변수(parameters)에는 외부 이름(external name)이 없다. 그러나 이는 밑줄(underscore)을 현명하게(wisely) 사용하는 방법을 보여준다(illustrates). 여기(here)의 표현식(expression)은 여전히 이해(understandable)할 수 있지만, 많은 매개변수(parameters)를 사용하는 더 복잡한(complex) 함수(functions)는 외부 매개변수(external parameter) 이름(names)이 없다면 혼란스럽고(confusing) 다루기 어려울(unwieldy) 수 있다. 함수(function)에 5개의 매개변수(parameters)가 있다고 상상해 보면 명확해질 것이다.

    매개변수(parameters)에 기본값(default values)을 지정(give)할 수도 있다:

    func printMultipleOf(_ multiplier: Int, _ value: Int = 1) {
      print("\(multiplier) * \(value) = \(multiplier * value)")
    }
    printMultipleOf(4)

    차이점(difference)은 두 번째 매개변수(parameter) 뒤에 있는 = 1이다. 두 번째 매개변수(parameter)에 값(value)이 제공(provided)되지 않으면, 기본값(defaults)은 1이다.

    따라서(therefore), 이 코드는 다음을 출력(prints)한다:

    4 * 1 = 4

    매개변수(parameter)가 대부분의 경우에 하나의 특정 값(one particular value)일 것으로 예상(expect)될 때, 기본값(default value)을 갖는 것이 유용(useful)할 수 있으며 함수(function)를 호출(call)할 때 코드를 단순화(simplify)한다.

     

    Return values

    지금까지 살펴본 모든 함수(functions)는 무언가를 출력하는 간단한 작업(simple task)을 수행(performed)했다. 함수(functions)는 값(value)을 반환(return)할 수도 있다. 함수(function) 호출자는 반환 값(return value)을 변수(variable)나 상수(constant)에 할당(assign)하거나 표현식(expression)에서 직접(directly) 사용할 수 있다.

    반환 값(return value)을 사용하면 함수(function)로 데이터를 변환(transform)할 수 있다. 매개변수(parameters)를 사용해 데이터를 가져와 계산(computations)을 수행(perform)하고 결과(result)를 반환(return)하기만 하면 된다.

    값(value)을 반환(returns)하는 함수(function)를 정의(define)하는 방법은 다음과 같다:

    func multiply(_ number: Int, by multiplier: Int) -> Int {
      return number * multiplier
    }
    let result = multiply(4, by: 2)

    함수(function)가 값(value)을 반환(returns)한다고 선언(declare)하려면, 괄호(parentheses) 세트(set) 뒤와 여는 중괄호(opening brace) 앞에 ->와 반환 값(return value) 유형(type)을 추가(add)한다. 이 예제(example)에서 함수(function)는 Int를 반환(returns)한다.

    함수(function) 내(inside)에서는 return 문(statement)을 사용하여 값(value)을 반환(return)한다. 이 예제(example)에서는 두 매개변수(parameters)의 곱(product)을 반환(return)한다. 튜플(tuples)을 사용하여 여러 값(multiple values)을 반환(return)하는 것도 가능(possible)하다:

    func multiplyAndDivide(_ number: Int, by factor: Int)
                       -> (product: Int, quotient: Int) {
      return (number * factor, number / factor)
    }
    let results = multiplyAndDivide(4, by: 2)
    let product = results.product
    let quotient = results.quotient

    이 함수(function)는 두 매개변수(parameters)의 곱(product)과 몫(quotient)을 적절한(appropriate) 이름(member value names)을 가진 두 개의 Int 값(values)이 포함(containing)된 튜플(tuple)로 모두 반환(returns)한다.

    튜플(tuples)을 사용해 여러 값(multiple values)을 반환(return)하는 기능(ability)은 Swift로 작업하는 것을 매우 즐겁게(pleasure)하는 많은 것들 중 하나이다. 그리고 곧 알게 되겠지만(as you’ll see shortly), 유용한 기능(handy feature)이기도 하다(turns out). 다음과 같이 return을 제거(removing)하여 두 함수(functions)를 더 간단하게(simpler) 만들 수 있다.

    func multiply(_ number: Int, by multiplier: Int) -> Int {
      number * multiplier
    }
    
    func multiplyAndDivide(_ number: Int, by factor: Int)
                       -> (product: Int, quotient: Int) {
      (number * factor, number / factor)
    }

    위 함수(function)는 단일 명령문(single statement)이기 때문에, 이 작업을 수행할 수 있다. 함수(function)에 더 많은 코드 행(lines)이 있으면 이 작업을 수행할 수 없다. 이 기능(feature)의 배경(behind)에 있는 개념(idea)은 이러한(such) 간단한 함수(functions)에서는 이것이 너무 명확(obvious)하고 return이 가독성(readability)을 방해한다는 것이다.

    다중 선택(multiple statements)에서는 여러 위치에서 반환(return)할 수 있으므로, return이 필요하다.

     

    Advanced parameter handling

    함수(function) 매개변수(parameters)는 상수(constants)이므로 수정(modified)할 수 없다.

    이를 설명(illustrate)하기 위해, 다음 코드를 고려(consider)해 본다:

    func incrementAndPrint(_ value: Int) {
      value += 1
      print(value)
    }

    매개변수(parameter) valuelet으로 선언(declared)된 상수(constant)와 동일(equivalent)하다. 따라서(therefore), 함수(function)에서 이를 증가(increment)시키려 하면 컴파일러(compiler)에서 오류(error)를 발생(emits)시킨다.

    Swift는 값(value)을 함수(function)에 전달(passing)하기 전에 이를 복사(copies)한다는 점에 유의(note)해야 한다. 이를 pass-by-value라고 한다.

    Note: pass-by-value과 복사(copies)는 지금까지 이 책에서 본 모든 유형(types)의 표준 동작(standard behavior)이다. 13장(chapter), "Classes"에서 함수(functions)로 값을 전달(passed into)하는 또 다른 방법을 확인할 수 있다.

    일반적으로(usually), 이 방식(behavior)을 사용한다(want). 이상적으로는(ideally), 함수(function)의 매개변수(parameters)를 변경(alter)하지 않는다. 만약 변경한다면 매개변수(parameter)의 값(values)을 확신(be sure of)할 수 없고, 잘못된(incorrect) 추정(assumptions)을 하여 코드에 버그가 생길 수(introduce) 있다.

    때로는(sometimes), 함수(function)가 매개변수(parameter)를 직접(directly) 변경(change)하도록 할 수 있다. 이를 copy-in copy-out 또는 값에 의한 호출(call by value result)이라고 한다. 다음과 같이 구현한다:

    func incrementAndPrint(_ value: inout Int) {
      value += 1
      print(value)
    }

    매개변수(parameter) 유형(type) 앞의 inout은 이 매개변수(parameter)가 복사(copied in)되어야 함을 나타내며, 해당 지역 복사본(local copy)은 함수(function) 내에서 사용된 후 반환(returns)될 때 다시 복사(copied back out)된다. 이 예제(example)를 완료(complete)하려면 함수(function) 호출(call)을 약간(slight) 수정(tweak to)해야 한다. 인수(argument) 앞에 앰퍼샌드(ampersand, &)를 추가하여, 호출 부분(call site)에서 copy-in copy-out를 사용하고 있음을 분명히(clear) 한다:

    var value = 5
    incrementAndPrint(&value)
    print(value)

    이제 함수(function)는 원하는(wishes) 대로 값(value)을 변경(change)할 수 있다.

    이 예(example)에서는 다음을 출력한다(print):

    6
    6

    함수(function)는 value를 증가시키고, 완료(finishes)된 후에도 수정된(modified) 데이터를 유지(keeps)한다. 값(value)은 함수(function)에 들어갔다가 다시 나오므로(comes back out), 키워드(keyword)는 inout이 된다.

    특정 조건에서(under certain conditions) 컴파일러(compiler)는 참조에 의한 전달(pass-by-reference)이라고 하는 copy-in copy-out을 단순화(simplify)할 수 있다. 인수 값(argument value)은 매개변수(parameter)에 복사(copied into)되지 않는다. 대신(instead) 매개변수(parameter)는 원래 값(original value)의 메모리(memory)에 대한 참조(reference)를 보유(hold)한다. 이 최적화(optimization)는 복사본(copies)의 필요성(need)을 제거(removing)하면서 copy-in copy-out의 모든 요구 사항(requirements)을 충족(satisfies)한다.

     

    Overloading

    이전 예제(previous examples)에서 여러(several) 다른 함수(functions)에 대해, 동일한 함수(function)명을 어떤 식으로 사용했는지 확인(notice)해 본다.

    func printMultipleOf(multiplier: Int, andValue: Int)
    func printMultipleOf(multiplier: Int, and value: Int)
    func printMultipleOf(_ multiplier: Int, and value: Int)
    func printMultipleOf(_ multiplier: Int, _ value: Int)

    이를 오버로딩(overloading)이라 하며, 단일 이름(single name)을 사용하여 유사한(similar) 함수(functions)를 정의(define)할 수 있다.

    그러나(however), 컴파일러(compiler)는 이러한 함수(functions) 간의 차이(difference)를 여전히 구별(tell)할 수 있어야 한다. 함수(function)를 호출(call)할 때마다(whenever), 어떤 함수(function)를 호출(calling)하는지 항상(always) 명확(clear)해야 한다.

    이는 일반적으로(usually) 매개변수 목록(parameter list)의 차이(difference)로 확인(achieved)한다:

    • 매개변수(parameters)의 개수(number)가 다르다.
    • 매개변수(parameter)의 유형(types)이 다르다.
    • printMultipleOf의 경우와 같이(such as) 외부 매개변수(external parameter)의 이름(names)이 다르다.

    다음과 같이 다른 반환(return) 유형(type)을 기반으로(based on), 함수 이름(function name)을 오버로드(overload)할 수도 있다:

    func getValue() -> Int {
      31
    }
    
    func getValue() -> String {
      "Matt Galloway"
    }

    여기에(here), 서로 다른 유형(types)을 반환(return)하는 두 가지 getValue() 함수(functions)가 있다. 하나는 Int를, 다른 하나는 String을 반환한다.

    이를 사용하는 것은 조금(a little) 더 복잡(complicated)하다. 다음을 고려(consider)해 본다:

    let value = getValue()

    Swift는 어떤 getValue()를 호출(call)해야할 지 알 수 없다. 다음과 같은 오류(error)가 출력(print)된다:

    error: ambiguous use of 'getValue()'

    어느 것을 호출(call)해야 할지 알 수 있는 방법이 없는 닭이 먼저냐, 달걀이 먼저냐의 문제(chicken and egg situation)와 같다. value의 유형(type)이 무엇인지 알 수 없으므로(unknown), Swift는 호출(call)할 getValue() 와 getValue()의 반환 유형(return type)을 알 수 없다.

    이 문제를 해결(fix)하기 위해 다음과 같이 value를 원하는 유형(type)으로 선언(declare)할 수 있다.

    let valueInt: Int = getValue()
    let valueString: String = getValue()

    이렇게 하면 첫 번째 인스턴스(instance)에서 getValue()Int 버전(version)을, 두 번째 인스턴스(instance)에서 getValue()String 버전(version)을 올바르게(correctly) 호출(call)한다.

    오버로딩(overloading)은 주의해서 사용해야 한다(should be used with care). 동작(behavior)이 관련(related)되어 있고 유사한(similar) 함수(functions)에 대해서만 오버로딩(overloading)을 사용한다.

    위 예(above example)에서와 같이 반환 유형(return type)만 오버로드(overloaded)되면, 유형 추론(type inference)이 어려워(loose)지기 때문에 권장(recommended)되지 않는다.

     

    Mini-exercises

    1. firstNamelastName이라는 두 개의 문자열(strings)을 사용하는 printFullName 함수(function)를 작성한다. 함수(function)는 firstName + " " + lastName으로 정의(defined)된 전체 이름(full name)을 출력(print out)해야 한다. 결과 확인에 자신의 이름을 사용해 본다.
    2. 두 매개변수(parameter)에 대해 외부 이름(external name)이 없도록 printFullName 선언(declaration)을 변경(change)한다.
    3. 전체 이름(full name)을 문자열(string)로 반환(returns)하는 countFullName 함수(function)를 작성(write)한다. 상수(constant)에 자신의 전체 이름(full name)을 저장(store)해 본다
    4. 전체 이름(full name)과 이름의 길이(length)를 모두 포함(containing)하는 튜플(tuple)을 반환(return)하도록 computeFullName을 변경(change)한다. count 속성(property)을 사용하여 문자열(string)의 길이(length)를 확인할 수 있다. 이 함수(function)를 사용하여 자신의 전체 이름(full name) 길이(length)를 확인(determine)한다.

     

    Functions as variables

    놀랄(surprise) 수도 있지만, Swift의 함수(functions)는 단지(simply) 또 다른 데이터 유형(data type)일 뿐이다. IntString과 같은 다른 유형(type)의 값(value)과 마찬가지로, 변수(variables) 및 상수(constants)에 할당(assign)할 수 있다.

    이것이 어떻게 작동하는지 확인하려면, 다음 함수(function)를 고려(consider)해 본다:

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

    이 함수(function)는 두 개의 매개변수(parameters)를 사용하고, 해당 값(values)의 합계(sum)를 반환(returns)한다.

    다음과 같이, 이 함수(function)를 변수(variable)에 할당(assign)할 수 있다:

    var function = add

    여기에서 변수(variable)의 이름은 function이고, 그 유형(type)은 할당(assign)한 add 함수(function)에서 (Int, Int) -> Int로 추론(inferred)된다.

    함수(function) 선언(declaration)에서 매개변수 목록(parameter list)과 반환 유형(return type)을 작성하는 것과 동일한 방식으로 함수 유형(function type)(Int, Int) -> Int이 작성되는 것을 확인해본다

    여기서 function 변수(variable)는 두 개의 Int 매개변수(parameters)를 사용하고 Int를 반환(returns)하는 함수(function) 유형(type)이다.

    이제 다음과 같이(like so) add를 사용하는 것과 같은 방식으로 function 변수(variable)를 사용할 수 있다.

    function(4, 2)

    6을 반환(returns)한다.

    이제 다음 코드를 고려(consider)해 본다:

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

    두 개의 Int 매개변수(parameters)를 사용(takes)하고, Int를 반환(returns)하는 또 다른 함수(function)를 선언(declare)한다. 매개변수 목록(parameter list)과 subtract의 반환(return) 유형이 function 변수(variable)의 유형(type)과 호환(compatible)되기 때문에, 이전의 function 변수(variable)를 새로운 subtract 함수(function)로 설정(set)할 수 있다.

    function = subtract
    function(4, 2)

    이번에는 function 호출(call)이 2를 반환(returns)한다.

    변수(variables)에 함수(functions)를 할당(assign)할 수 있다는 사실은, 함수(functions)를 다른 함수(functions)에 전달(pass)할 수 있다는 의미이기 때문에 유용하게(handy) 사용할 수 있다. 이 작업의 예(example)는 다음과 같다:

    func printResult(_ function: (Int, Int) -> Int, _ a: Int, _ b: Int) {
      let result = function(a, b)
      print(result)
    }
    printResult(add, 4, 2)

    printResult는 세 개의 매개변수(parameters)를 사용한다:

    1. function은 두 개의 Int 매개변수(parameters)를 사용하고, (Int, Int) -> Int와 같이 선언(declared)된 Int를 반환(returns)하는 함수(function) 유형(type)이다.
    2. a는 Int 유형(type)이다.
    3. b는 Int 유형(type)이다.

    printResult는 전달된(passed-in) 함수(function)를 호출(calls)하여, 두 개의 Int 매개변수(parameters)를 전달(passing into)한다. 그런 다음 결과(result)를 콘솔(console)에 출력(prints)한다.

    6

    함수(functions)를 다른 함수(functions)에 전달(pass)할 수 있다는 것은 매우(extremely) 유용(useful)하며, 재사용 가능한(reusable) 코드를 작성(write)하는 데 도움(help)이 될 수 있다. 데이터(data)를 전달(pass)하여 조작(manipulate)할 수 있을 뿐만 아니라, 함수(functions)를 매개변수(parameters)로 전달(passing)하면 코드 실행(executes)이 유연(flexible)해질 수 있다.

     

    The land of no return

    일부 함수(functions)는 호출자(caller)에게 제어(control)를 반환(return)하지 않는다. 예를 들어(for example), 응용 프로그램(application)이 충돌(crash)하도록 설계된(designed to) 함수(function)가 있다. 아마(perhaps) 이것이 이상하게 들릴 수(sounds strange)도 있다. 하지만 응용 프로그램(application)이 손상된(corrupt) 데이터로 작업하는 경우, 알 수 없는(unknown) 잠재적(potentially) 위험(dangerous) 상태(state)로 계속 진행(continue)하는 것보다 충돌(crash)을 일으키는 것이 더 낫다. fatalError("reason to terminate")는 이와 같은 함수(function)의 예시(example)이다. 치명적인 오류(fatal error)의 원인(reason)을 출력(prints)한 다음, 추가적인 손상(further damage)을 방지(prevent)하기 위해 실행(execution)을 중지(halts)한다.

    반환되지 않는(non-returning) 함수(function)의 또 다른 예(example)는 이벤트 루프(event loop)를 처리(handles)하는 함수(function)이다. 이벤트 루프(event loop)는 사용자의 입력(input)을 받아 화면(screen)을 표시(displays)하는 모든 최신(modern) 응용 프로그램(application)의 핵심(heart)이다. 이벤트 루프(event loop)는 사용자(user)로부터 요청(requests)을 받아 이러한 이벤트(events)를 응용 프로그램(application) 코드로 전달(passes)하여 정보(information)가 화면(screen)에 표시(displayed)되도록 한다. 그리고 루프(loop)는 다시 순환(cycles back)하여 다음 이벤트(event)를 처리(services)한다.

    이러한 이벤트 루프(event loops)는 애플리케이션(application)에서 종종 반환되지 않는(never return) 것으로 알려진 함수(function)를 호출(calling)하여 시작된다. 

    다음 함수(function)에서 Swift는 반환되지 않는다(never return)고 컴파일러(compiler) 오류(complain)를 발생시킬 것이다:

    func noReturn() -> Never {
    
    }

    함수(function)가 절대 반환(never return)되지 않음을 나타내는 특수 반환 유형(special return type) Never에 주목(notice)한다.

    이 코드를 작성하면, 다음 오류(error)가 발생한다:

    Function with uninhabited return type 'Never' is missing call to another never-returning function on all paths

    이는 함수(function)가 자기 자신을 반환(returns)하기 전에 다른 "no return" 함수(function)를 호출(call)하지 않는다고 말하는 다소 장황한(long-winded) 방식이다. 끝에 도달(reaches)하면 함수(function)가 호출된(called) 위치로 반환(returns)되어 Never 반환 형식(return type)의 조건(contract)을 위반(breaching)한다.

    반환(return)되지 않는 함수(function)의 조잡(crude)하지만 정직(honest)한 구현(implementation)은 다음과 같다:

    func infiniteLoop() -> Never {
      while true {
      }
    }

    왜 이 특수 반환 유형(special return type)에 신경써야(bother) 하는지 궁금할(wondering) 수도 있다. 해당 함수(function)는 반환(return)되지 않는다는 것을 컴파일러(compiler)가 알고 있기 때문에 함수(function) 호출(call) 코드를 생성(generating)할 때 특정(certain) 최적화(optimizations)를 수행할 수 있어 유용(useful)하다. 본질적으로(essentially), 이 함수(function)를 호출(calls)하는 코드는 함수 호출(function call) 이후에 아무 것도 하지 않아도 된다. 왜냐하면(because) 이 함수(function)는 응용 프로그램(application)이 종료(terminated)되기 전에는 결코(never) 끝나지 않는다는 것을 알고 있기 때문이다.

     

    Writing good functions

    함수(functions)을 사용하여 많은 문제(problems)를 해결(solve)할 수 있다. 가장 좋은 방법(best)은 하나의 간단한 작업(simple task)을 수행하여, 보다 복잡한 동작(complex behaviors)으로 쉽게 혼합(mix), 일치(match) 및 모델링(model)할 수 있도록 하는 것이다.

    사용(use)하기 쉽고 이해(understand)하기 쉬운 함수(functions)를 만들어야 한다. 매번 동일한 출력(output)을 생성(produce)하는 잘 정의된 입력(well-defined inputs)을 제공해야 한다. 훌륭(good)하고 깔끔(clean)하며 간단(simple)한 함수(functions)를 따로(isolation) 추론(about)하고 테스트(test)하는 것이 더 쉽다는 것을 알게 될 것이다.

     

    Commenting your functions

    모든 훌륭한 소프트웨어 개발자(software developers)는 코드를 문서화(document)한다. 함수(functions) 문서화(documenting)는 나중에 코드를 확인해야 하거나 다른 사람과 공유(share)해야 할 때, 코드를 뒤지지(trawl) 않고 이해(understood)할 수 있도록 하는 중요(important)한 단계(step)이다.

    다행히(fortunately), Swift에는 Xcode의 코드 완성(code completion) 및 기타 기능(other features)과 잘 통합(integrates)되도록 간편하게(straightforward) 함수(functions)를 문서화(document)할 수 있다.

    사실상(defacto) Swift 외의 많은 다른 언어(languages)에서도 사용되는 Doxygen 주석 표준(commenting standard)을 사용한다. 함수(function)를 문서화(document)하는 방법을 살펴본다(take a look):

    /// Calculates the average of three values
    /// - Parameters:
    ///   - a: The first value.
    ///   - b: The second value.
    ///   - c: The third value.
    /// - Returns: The average of the three values.
    func calculateAverage(of a: Double, and b: Double, and c: Double) -> Double {
      let total = a + b + c
      let average = total / 3
      return average
    }
    calculateAverage(of: 1, and: 3, and: 5)

    일반적인(usual) double-/ 대신(instead of) triple-/를 사용한다. 첫 번째 행(first line)은 함수(function)가 하는 일에 대한 설명(description)이다. 그 다음은 매개변수(parameters) 목록(list)이며 마지막은 반환 값(return value)에 대한 설명(description)이다.

    문서 주석(documentation comment) 형식(format)이 생각이 나지 않는다면(forget), 해당 함수(function)을 강조 표시(highlight)하고 Xcode에서 "Option-Command-/"를 누르기(press)만 하면 된다. Xcode 편집기(editor)는 사용자가 작성(fill out)할 수 있는 주석(comment) 템플릿(template)을 삽입(insert)한다.

    이러한 코드 문서(documentation)를 만들 때, 주석(comment)이 Xcode의 일반적인(usual) 고정폭 글꼴(monospace font)을 변경(changes)한다. 깔끔(neat)하지만, 더 확인해 볼 것이 있다.

    먼저(first), Xcode는 다음과 같이 코드 완성(code completion)이 나타날 때 해당 문서(documentation)를 보여(shows)준다:

     

    또한(also) 옵션(option) 키(key)를 누른(hold) 상태에서 함수(function) 이름을 클릭(click)하면, Xcode가 다음과 같은 유용한(handy) 팝오버(popover)로 문서(documentation)를 표시(shows)한다:

     

    이 두 가지 모두 매우 유용(useful)하며 모든 함수(functions), 특히(especially) 자주(frequently) 사용되거나 복잡(complicated)한 함수(functions)를 문서화(documenting)하는 것을 고려(consider)해야 한다. 나중에 스스로에게 감사하게 될 것이다(future you will thank you later)

     

    Challenges

    다음 단계로 넘어가기 전에(before moving on), 함수(functions)에 대한 지식(knowledge)을 확인(test)하기 위한 몇 가지 챌린지(challenges)가 있다. 스스로 해결(solve)해 보려고 하는 것이 가장 좋지만, 막힌다면(get stuck) 다운로드(download)나 책의 소스 코드 링크(source code link)에서 해답(solutions)을 참고할 수 있다.

     

    Challenge 1: Looping with stride functions

    4장(chapter), "Advanced Control Flow"에서 가산 범위(countable ranges)의 for 반복문(loops)을 작성했다. 가산 범위(countable ranges)는 항상 1씩 증가(increasing)해야 한다는 제한(limited)이 있다. Swift의 stride(from:to:by:)stride(from:through:by:) 함수(functions)를 사용하면 훨씬 더 유연하게(flexibly) 반복문(loop)을 수행할 수 있다.

    예를 들어(for example), 10에서 20까지 4씩 반복(loop)하려면 다음과 같이 작성(write)할 수 있다:

    for index in stride(from: 10, to: 22, by: 4) {
      print(index)
    }
    // prints 10, 14, 18
    
    for index in stride(from: 10, through: 22, by: 4) {
      print(index)
    }
    // prints 10, 14, 18, and 22
    • stride 함수(function) 오버로드(overloads)의 차이점(difference)을 확인해 본다.
    • 10.0에서 9.0까지(포함(including)하여) 0.1씩 감소(decrementing)하는 반복문(loop)을 작성해 본다.

     

    Challenge 2: It’s prime time

    프로그래밍 언어(programming language)에 익숙(acquainting)해질 때 가장 먼저 하는 일 중 하나는 숫자(number)가 소수(prime)인지 여부를 결정(determine)하는 함수(function)를 작성하는 것이다. 이것이 두 번째 챌린지(challenge)이다.

    먼저, 다음 함수(function)를 작성(write)한다:

    func isNumberDivisible(_ number: Int, by divisor: Int) -> Bool

    한 숫자(number)가 다른 숫자로 나누어(divisible) 떨어지는지 확인(determine)한다. numberdivisor로 나누어 떨어지면(divisible) true를 반환(return)해야 한다.

    Hint: 여기에서 나머지(modulo, %) 연산자(operator)를 사용하면 도움이 된다.

    다음으로(next), main 함수(function)를 작성한다.

    func isPrime(_ number: Int) -> Bool

    number가 소수(prime)이면 true를 반환(return)하고 그렇지 않으면(otherwise) false를 반환해야 한다. 1과 자기 자신(itself)으로만 나누어(divisible) 떨어지는 숫자(number)가 소수(prime)이다. 1에서 숫자(number)까지(through) 반복(loop)하여 숫자(number)의 제수(divisors)를 찾아야 한다. 1과 자신(itself) 이외의 약수(divisors)가 있다면 그 숫자(number)는 소수(prime)가 아니다. 이전(earlier)에 작성한 isNumberDivisible(_:by:) 함수(function)를 사용해야 한다.

    이 함수(function)를 사용하여 다음과 같은 경우(cases)를 확인(check)한다.

    isPrime(6) // false
    isPrime(13) // true
    isPrime(8893) // true

    Hint 1: 0보다 작은 숫자(numbers)를 소수(prime)로 간주(considered)해서는 안 된다. 함수(function) 시작 시 이 경우(case)를 확인(check for)하고, 해당 숫자(number)가 0보다 작다면 바로 반환(return)한다.

    Hint 2: for 반복문(loop)을 사용하여 제수(divisors)를 찾는다(find). 2에서 부터 시작(start)하여 해당 숫자(number)보다 먼저 끝(end)나는 경우 제수(divisor)를 찾는 즉시(as soon as) false를 반환(return)할 수 있다.

    Hint 3: 정말 영리(clever)하게 해결하고 싶다면 해당 number까지 진행하지 않고, 2부터 해당 number의 제곱근(square root)에 도달(reach)할 때까지 반복(loop)하면 된다. 왜 그런지는 스스로 알아내어(figure out) 본다. 제곱근(square root)이 4인 숫자(number) 16을 생각해 보면 도움(help)이 될 것이다. 16의 약수(divisors)는 1, 2, 4, 8, 16이다.

     

    Challenge 3: Recursive functions

    이 챌린지(challenge)에서는 함수(function)가 자기 자신을 호출(calls)하는 재귀(recursion)에서 어떤 일이 발생(happens)하는지 확인할 수 있다. 이는 이상하게(unusual) 들릴지 모르지만, 상당히 유용(useful)하다.

    피보나치 수열(fibonacci sequence)에서 값(value)을 계산(computes)하는 함수(function)를 작성(write)할 것이다. 시퀀스(sequence)의 모든 값(value)은 이전(previous) 두 값(values)의 합(sum)이다. 시퀀스(sequence)는 처음 두 값(values)이 1이 되도록 정의(defined)된다. 즉(that is), fibonacci(1) = 1fibonacci(2) = 1이다.

    다음 선언(declaration)을 사용하여 함수(function)를 작성(write)한다:

    func fibonacci(_ number: Int) -> Int

    그런 다음(then), 다음 숫자(following numbers)로 실행(executing)하여 함수(function)가 올바르게 작성됐는지 확인(verify)한다:

    fibonacci(1)  // = 1
    fibonacci(2)  // = 1
    fibonacci(3)  // = 2
    fibonacci(4)  // = 3
    fibonacci(5)  // = 5
    fibonacci(10) // = 55

    Hint 1: 0보다 작은 number 값(values)의 경우 0을 반환(return)해야 한다.

    Hint 2: 시퀀스(sequence)를 시작(start)하려면 number가 1 또는 2일 때 반환 값(return value) 1을 하드 코딩(hard-code)한다.

    Hint 3: 다른 값(value)의 경우, number - 1number - 2를 사용하여 호출(calling)하는 fibonacci의 합계(sum)를 반환(return)해야 한다.

     

    Key points

    • 함수(function)를 사용하여 코드를 여러 번(multiple times) 작성(writing)할 필요 없이, 원하는 만큼 실행(execute)할 수 있는 작업(task)을 정의(define)할 수 있다.
    • 함수(function)는 0개 이상의 매개변수(parameters)를 사용(take)하고 선택적으로(optionally) 값(value)을 반환(return)할 수 있다.
    • 함수(function) 매개변수(parameter)에 외부 이름(external name)을 추가(add)하여 함수(function) 호출(call)에서 사용하는 레이블(label)을 변경(change)하거나 밑줄(underscore)을 사용하여 레이블(label)이 없음을 나타낼(denote) 수 있다.
    • 매개변수(parameters)는 inout으로 표시(mark)하지 않는 한 상수(constants)로 전달(passed)되며, 이 경우(case) 값이 복사(copied-in and copied-out)되어 전달된다.
    • 함수(functions)는 서로 다른(different) 매개변수(parameters)를 가진 동일한 이름(name)이 될 수 있다. 이를 오버로딩(overloading)이라고 한다.
    • 함수(function)는 이 함수(function)가 절대 종료(never exit)되지 않을 것임을 Swift에 알리기 위한 특별한(special) Never 반환 유형(return type)을 가질 수 있다.
    • 함수(functions)를 변수(variables)에 할당(assign)하고 다른 함수(functions)에 전달(pass)할 수 있다.
    • 이름이 명확(clearly named)하고 반복 가능한 입력(repeatable inputs)과 출력(outputs)으로 하나의 작업(job)을 수행하는 함수(functions)을 만들기 위해 노력해야 한다.
    • ///를 사용하여 함수(function) 앞에 주석 섹션(comment section)을 추가해 함수 문서(function documentation)를 만들 수 있다.

    'Raywenderlich > Swift Apprentice' 카테고리의 다른 글

    Chapter 7: Arrays, Dictionaries & Sets  (1) 2022.08.27
    Chapter 6: Optionals  (0) 2021.10.08
    Chapter 4: Advanced Control Flow  (0) 2021.08.03
    Chapter 3: Basic Control Flow  (0) 2021.06.16
    Chapter 2: Types & Operations  (0) 2021.03.16
Designed by Tistory.