Swift 뿌수기 - Functions

Wonbi·2022년 8월 24일
2

Swift 뿌수기

목록 보기
5/12

✅학습 내용

💎 Functions(함수)

Functions are self-contained chunks of code that perform a specific task. You give a function a name that identifies what it does, and this name is used to “call” the function to perform its task when needed.

  • 함수 (Functions)"특정 임무를 수행하는 독립된 코드 조각" 입니다. 함수의 역할을 식별하는 이름을 지정하고, 필요할 때 이 이름을 사용하여 임무를 수행할 함수를 호출 (call) 합니다.

  • 스위프트의 통합 함수 구문은 매개 변수가 없는 단순한 C-스타일 메서드 부터 각 매개변수에 대한 이름과 아규먼트 레이블이 있는 복잡한 Objective-C-스타일 메서드까지 어떤 것이든 표현할 만큼 충분히 유연하다. 매개 변수는 함수 호출이 단순해지도록 기본값을 제공할 수도 있고, 함수 실행을 한 번 완료하고 나면 전달한 변수 (매개 변수) 를 수정하는 입-출력 (in-out) 매개 변수로 전달할 수도 있다.

  • 스위프트의 모든 함수는, 함수의 매개 변수 타입과 반환타입으로 구성된 타입을 가진다. 스위프트의 다른 타입과 마찬가지로 이 타입을 사용할 수 있기 때문에, 함수를 매개 변수로 다른 함수에 전달하고 함수에서 함수를 반환할 수 있다. 또한 함수는 내포된 함수 범위 내에서 유용한 기능을 캡슐화하기 위해 다른 함수 내에서 작성할 수 있다.

✏️ 함수 정의하기와 호출하기

  • 함수를 정의할 때 매개변수 (parameters) 라는 이름과 타입을 지정한 값을 선택적으로 정의할 수 있다. 또한 함수가 완료되었을 때 출력으로 반환되는 값의 타입을 반환타입 (return type) 을 선택적으로 정의할 수도 있다.

  • 모든 함수에는 함수가 수행할 작업을 설명하는 함수 이름 (function name) 이 있다. 함수를 사용하려면 함수를 이름으로 호출하고 함수의 매개 변수 타입과 일치하는 (인자 (arguments) 라고 하는) 입력 값을 전달한다. 함수의 인자는 반드시 함수 매개 변수 목록과 같은 순서로 제공해야 한다.

  • 아래 예제에 있는 함수는 하는 작업이 "사람 이름을 입력받아 해당 사람에게 인사말을 반환"하는 것이기 때문에 greet(person:)이라는 이름으로 호출된다. 이 작업을 수행하기 위해 person 이라는 String 값을 입력받는 매개변수 하나와, 해당 사람에게 할 인사말을 담을 String 반환 타입을 정의한다.

func greet(person: String) -> String {
  let greeting = "Hello, " + person + "!"
  return greeting
}
  • 이 모든 정보를 func키워드 뒤에 있는 함수의 정의 (definition) 로 끌어모아 지정한다. 함수의 반환 타입은 (대쉬 기호 다음에 오른쪽 꺽쇠 괄호 기호를 붙인) 반환 화살표 (return arrow) -> 와 그 다음에 반환할 타입의 이름으로 지시합니다.
  • 이 정의는 함수가 무엇을 수행하는지, 무엇을 받을 것인지, 그리고 무엇을 반환할 지 설명한다. 이 정의는 코드 어디서든 함수 호출을 헷갈리지 않고 명확하게 하도록 한다.
print(greet(person: "Wonbi"))
// "Hello, Wonbi!" 를 인쇄함
print(greet(person: "Sunny"))
// "Hello, Sunny!" 를 인쇄함
  • greet(person:) 함수는, greet(person: "Anna") 같이, person 인자 이름표 뒤에 String 값을 전달하여 호출한다. 함수가 String 값을 반환하기 때문에, 위에 보는 것처럼, print(_:separator:terminator:) 함수 안에 greet(person:) 함수를 포장한 채로 호출하여 해당 문자열을 인쇄하고 반환 값을 볼 수 있다.

  • greet(person:) 함수 본문은 greeting 이라는 새로운 String 상수를 정의하고 단순한 인사말 메시지를 설정하는 것으로 시작한다. 그다음 return키워드로 이 인사말을 함수 밖으로 반환한다.

  • 서로 다른 입력 값을 가지고 greet(person:)함수를 여러 번 호출할 수도 있다. 위 예제는 "Wonbi" 라는 입력 값과, "Sunny" 이라는 입력 값을 가지고 호출하면 무슨 일이 발생하는 지를 보여준다. 함수는 각 경우마다 맞춤 인사말을 반환한다.

  • 메시지 생성문과 반환문을 한 줄로 조합하면 함수 본문을 더 짧게 만들수도 있다.

func greetAgain(person: String) -> String {
  return "Hello again, " + person + "!"
}
print(greetAgain(person: "Sunny"))
// "Hello again, Sunny!" 를 인쇄함

✏️ 함수의 매개변수와 반환값

  • 스위프트의 함수 매개 변수와 반환 값은 매우 유연하다. '이름 없는 단일 매개 변수를 가진 단순한 유틸리티성 함수'부터 '풍부한 표현력의 매개변수 이름과 서로 다른 매개변수 옵션을 가진 복잡한 함수'까지 어떤 것이든 정의할 수 있다.
1. 매개변수가 없는 함수
  • 함수에서 매개변수를 정의하는 것이 필수는 아니다. 다음 예제는 호출할 때 마다 항상 동일한 String 메시지를 반환하는, 매개변수가 없는 함수입니다.
func sayHelloWorld() -> String {
  return "hello, world"
}
print(sayHelloWorld())
// "hello, world" 를 인쇄함
  • 어떤 매개변수도 취하지 않더라도 함수 정의에서 함수 이름 뒤에 괄호는 반드시 필요하다. 함수를 호출할 때도 함수 이름 뒤에 빈 괄호쌍이 존재해야 한다.
2. 매개변수가 여러 개인 함수
  • 함수는 함수의 괄호 안에 쉼표로 구분되어 작성한 여러개의 입력 매개변수를 가질 수 있다.
  • 다음 예제의 함수는 매개변수로 이미 사람과 인사를 했는지에 대한 정보를 받아, 해당 사람에 대한 적절한 인사말을 반환한다.
func greet(person: String, alreadyGreeted: Bool) -> String {
  if alreadyGreeted {
    return greetAgain(person: person)
  } else {
    return greet(person: person)
  }
}
print(greet(person: "Wonbi", alreadyGreeted: true))
// "Hello again, Wonbi!" 를 인쇄함
  • greet(person:alreadyGreeted:) 함수는 괄호 안에 person 매개변수에 String 값과 alreadyGreeted 매개변수의 Bool 값을 쉼표로 구분하여 전달함으로써 호출한다. 이 함수는 앞 부분에서 본 greet(person:) 함수와 서로 별개라는 것을 기억하자. 두 함수 다 greet 으로 시작하는 이름을 가지긴 하지만, greet(person:alreadyGreeted:) 함수는 두 매개변수를 받지만 greet(person:) 함수는 하나만 받는다.
3. 반환 값이 없는 함수
  • 함수에서 반환 타입을 정의하는 것 역시 필수가 아니다. 다음 예제는 자신의 String값을 반환하기 보다 인쇄를 하는 버전의 greet(person:)함수이다.
func greet(person: String) {
  print("Hello, \(person)!")
}
greet(person: "Moon")
// "Hello, Moon!" 를 인쇄함
  • 값을 반환할 필요가 없기 때문에, 함수를 정의할 때 반환 화살표 (->) 나 반환 타입을 포함하지 않는다.

    엄밀히 말해서, 이 버전의 greet(person:) 함수가, 반환 값을 정의하지 않았을지라도, 여전히 값을 반환 합니다 (does). 반환 타입을 정의하지 않은 함수는 Void 타입의 특수한 값을 반환합니다. 이는 단순히 ‘빈 튜플 (tuple)’ 이며, () 라고 작성합니다.

  • 함수를 호출할 때 반환 값을 무시할 수 있다.

func printAndCount(string: String) -> Int {
  print(string)
  return string.count
}
func printWithoutCounting(string: String) {
  let _ = printAndCount(string: string)
}
printAndCount(string: "hello, world")
// "hello, world" 를 인쇄하고 12 라는 값을 반환함
printWithoutCounting(string: "hello, world")
// "hello, world" 를 인쇄하지만 값을 반환하지 않음
  • 첫 번째 함수 printAndCount(string:)은, 문자열을 인쇄하고 문자 갯수를 Int로 반환한다. 두 번째 함수 printWithoutCounting(string:)은 첫 번째 함수를 호출하지만 반환 값은 무시한다. 두 번째 함수가 호출되면 첫 번째 함수에 의해 메시지는 여전히 인쇄되지만 반환 값은 사용하지 않는다.

    반환 값을 무시할 순 있지만, 값을 반환할거라고 한 함수는 반드시 항상 그래야 합니다. 반환 타입을 정의한 함수는 값을 반환하지 않고 함수 밑을 빠져나가는 제어를 허용하지 않으며, 그럴려는 시도는 컴파일 에러가 될 것입니다.

4. 반환 값이 여러개인 함수
  • 튜플 타입을 함수의 반환 타입으로 사용하면 여러 개의 값을 반환할 수 있다.
  • 다음 예제는 inMax(array:) 라는 함수로 Int 값 배열에서 가장 작은 수와 가장 큰 수를 찾는 함수를 정의한다.
func minMax(array: [Int]) -> (min: Int, max: Int) {
  var currentMin = array[0]
  var currentMax = array[0]
  for value in array[1..<array.count] {
    if value < currentMin {
      currentMin = value
    } else if value > currentMax {
      currentMax = value
    }
  }
  return (currentMin, currentMax)
}
  • minMax(array:) 함수는 두 Int 값을 담은 튜플을 반환한다. 이 값에는 minmax 라는 이름표가 붙어 있어서 함수 반환 값을 조회할 때 이름으로 접근할 수 있다.

  • 튜플의 멤버 값은 함수 반환 타입에서 이름을 붙였기 때문에, 점 구문 (dot syntax) 으로 접근해서 최소 및 최대 값을 가져올 수 있다.

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
// "min is -6 and max is 109" 를 인쇄함
  • 튜플 멤버의 이름을 함수 반환 타입에서 이미 지정했기 때문에, 함수가 튜플을 반환하는 시점에는 이름을 붙일 필요가 없다는 걸 기억하자.
5. 옵셔널 튜플 반환 타입
  • 함수가 반환하는 튜플 타입에서 전체 튜플의 “값이 없을 (no value)” 가능성이 있는 경우, 옵셔널 (optional) 튜플 반환 타입을 사용하면 전체 튜플이 nil 일 수 있다는 사실을 반영할 수 있다. 옵셔널 튜플 반환 타입은, (Int, Int)?(String, Int, Bool)? 같이, 튜플 타입 닫는 괄호 뒤에 물음표를 작성하여 표현한다.

(Int, Int)? 같은 ‘옵셔널 튜플 타입’ 은 (Int?, Int?) 같이 ‘옵셔널 타입을 담은 튜플’ 과는 다릅니다. 옵셔널 튜플 타입에선, 튜플 안의 각 개별 값만이 아니라, 전체 튜플이 옵셔널입니다.

  • minMax(array:) 함수는 두 Int 값을 담은 튜플을 반환한다. 하지만, 함수가 전달한 배열에 대한 어떤 안전성 검사도 하지 않는다. array 인자가 빈 배열을 담고 있다면, 위에 정의한 minMax(array:) 함수가 array[0]에 접근하려고 할 때 런타임 에러를 발동할 것이다.
func minMax(array: [Int]) -> (min: Int, max: Int)? {
  if array.isEmpty { return nil }
  var currentMin = array[0]
  var currentMax = array[0]
  for value in array[1..<array.count] {
    if value < currentMin {
      currentMin = value
    } else if value > currentMax {
      currentMax = value
    }
  }
  return (currentMin, currentMax)
}
  • 옵셔널 바인딩 (optional binding) 을 사용하면 minMax(array:) 함수가 실제 튜플 값을 반환하는 지 nil 을 반환하는 지 검사할 수 있다.
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
  print("min is \(bounds.min) and max is \(bounds.max)")
}
// "min is -6 and max is 109" 를 인쇄함
6.암시적으로 반환하는 함수
  • 전체 함수 본문이 단일 표현식인 경우, 함수가 해당 표현식을 암시적으로 반환한다.
func greeting(for person: String) -> String {
  "Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// "Hello, Dave!" 를 인쇄함

✏️ 함수 인자 이름과 매개변수 이름

  • 각 함수 매개변수에는 인자 이름표 (argument label) 와 매개변수 이름 (paramenter name) 이 둘 다 있다. 인자 이름표는 함수를 호출할 때 사용한다. 각 인자는 함수가 호출될 때 인자 이름표를 앞에 작성된다. 매개변수 이름은 함수 구현에서 사용한다. 기본적으로 매개변수가 매개변수 이름을 자신의 인자 이름표로 사용한다.
func someFunction (firstParameterName: Int, secondParameterName: Int) {
  // 함수 본문에서, firstParameterName 과 secondParameterName 은
  // 첫 번째 및 두 번째 매개 변수의 인자 값을 참조합니다.
}
someFunction(firstParameterName: 1, secondParameterName: 2)
  • 모든 매개변수는 반드시 유일한 이름을 가져야 한다. 여러 개의 매개변수가 동일한 인자 이름표를 가지는 게 가능은 하지만, 유일한 인자 이름표는 코드를 더 쉽게 이해하도록 도와준다.
인자 이름표 지정하기
  • 인자 이름표는 매개변수 이름 앞에 공백으로 구분하여 작성한다.
func someFunction(argumentLabel parameterName: Int) {
  // 함수 본문에서, parameterName 은
  // 해당 매개 변수의 인자 값을 참조합니다.
}
func greet(person: String, from hometown: String) -> String {
  return "안녕하세요 \(person)! \(hometown)으로부터의 방문을 환영합니다."
}
print (greet(person: "Bill", from: "Cupertino"))
// "안녕하세요 Bill! Cupertino으로부터의 방문을 환영합니다." 를 인쇄함
  • 인자 이름표를 사용하면 함수가 풍부한 표현력을 가지고 일반적인 문장 방식으로 호출될 수 있으며, 동시에 읽기 쉽고 명확한 의도를 가진 함수를 제공할 수 있다.
인자 이름표 생략하기
  • 매개변수의 인자 이름표를 원하지 않으면 해당 매개변수에 대한 명시적인 인자 이름표 대신 밑줄 (underscore; _) 을 작성하면 된다.
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
  // 함수 본문에서, firstParameterName 과 secondParameterName 은
  // 첫 번째 및 두 번째 매개 변수의 인자 값을 참조합니다.
}
someFunction (1, secondParameterName: 2)
  • 매개변수에 인자 이름표가 있다면, 함수를 호출할 때 반드시 이름표를 붙여야 한다.
기본 매개변수 값
  • 해당 매개변수 타입 뒤에 값을 할당함으로써 어떤 매개변수에든 기본 값 (default value) 을 정의할 수 있다. 기본 값을 정의하면 함수를 호출할 때 해당 매개변수를 생략할 수 있다.
func someFunction (parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
  // 이 함수를 호출할 때 두 번째 인자를 생략하면,
  // parameterWithDefault 값이 함수 본문 안에서 12 가 됩니다.
}
someFunction (parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault 는 6 임
someFunction (parameterWithoutDefault: 4) // parameterWithDefault 는 12 임
  • 기본 값이 없는 매개변수가 함수의 의미상 대체로 더 중요하기 때문에 기본 값이 있는 매개변수는 함수 매개변수 목록 맨 뒤에 두어야 한다. 이를 맨 뒤에 두면, 어떤 기본 값이 있는 매개변수를 생략하던 상관없이 동일 함수를 호출하고 있음을 더 인식하기 쉽게 해준다.
가변 매개변수
  • 가변 매개변수 (variadic parameter) 는 특정 타입의 값을 0개, 혹은 그 이상 받는다. 가변 매개변수는 함수 호출할 때 매개변수에다가 다양한 개수의 입력 값을 전달할 수 있음을 지정하고자 할 때 사용한다. 매개변수의 타입 이름 뒤에 마침표 문자 세 개 (...) 를 집어 넣어서 가변 매개변수를 작성한다.

  • 가변 매개변수에 전달한 값은 함수 본문 안에서 적절한 타입의 배열로 사용이 가능하다. 예를 들어, numbers 라는 이름과 Double... 이라는 타입을 가진 가변 매개변수는 함수 본문 안에서 [Double] 타입의 numbers 라는 상수 배열로 사용이 가능하다.

func arithmeticMean(_ numbers: Double...) -> Double {
  var total: Double = 0
  for number in numbers {
    total += number
  }
  return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// 이 다섯 수들의 산술 평균인, 3.0 을 반환함
arithmeticMean(3, 8.25, 18.75)
// 이 세 수들의 산술 평균인, 10.0 을 반환함
  • 함수에는 여러 개의 가변 매개변수를 가질 수 있다. 가변 매개변수 뒤에 오는 첫 번째 매개변수에는 반드시 인자 이름표가 있어야 한다. 이 인자 이름표는 인자가 가변 매개변수로 전달되었는지 가변 매개변수 뒤에 오는 매개변수로 전달되었는지 헷갈리지 않도록 한다.
입-출력 매개변수
  • 기본적으로 함수의 매개변수는 상수이다. 함수 본문 안에서 함수 매개변수의 값을 바꾸려고 하면 컴파일 에러가 나온다. 이는 매개변수 값을 실수로 바꿀 순 없다는 의미이기도 하다. 하지만 함수에서 매개변수 값을 수정하고 싶고, 함수 호출이 끝난 후에도 바꾼 값을 지속하고 싶다면 해당 매개변수를 입-출력 매개변수 (in-out parameter) 로 대신 정의하면 된다.

  • 매개변수 타입 바로 앞에 inout 키워드를 둬서 입-출력 매개변수를 작성할 수 있다. 입-출력 매개변수는 원본 값을 교체하기 위해, 함수 안으로 입력 (in) 하여, 함수가 그 값을 수정한 다음, 다시 함수 밖으로 출력 (out) 되는 값을 가지게 된다. 입-출력 매개변수 및 이와 관련된 컴파일러 최적화의 동작에 대한 더 자세한 논의는, In-Out Parameters (입-출력 매개 변수) 부분을 참고하면 된다.

  • 변수만 입-출력 매개변수 인자로 전달할 수 있다. 상수나 글자 값 (literal) 은 인자로 전달할 수 없는데, 상수와 글자 값은 수정할 수 없기 때문이다. 입-출력 매개변수의 인자로 전달할 때는, 함수가 수정할 수 있음을 지시하기 위해, 변수 이름 바로 앞에 ‘앰퍼샌드 (ampersand; &)’ 를 붙인다.

    입-출력 매개 변수는 기본 (default) 값을 가질 수 없으며, 가변 매개 변수는 inout 으로 표시할 수 없습니다.

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
  let temporaryA = a
  a = b
  b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// "someInt is now 107, and anotherInt is now 3" 를 인쇄함
  • 위 예제는 원래의 someIntanotherInt 를 함수 밖에서 정의했지만, swapTwoInts(_:_:) 함수가 이들의 원본 값을 수정함을 보여준다.

    입-출력 매개 변수는 함수의 값 반환과 똑같지 않습니다. 위의 swapTwoInts 예제는 반환 타입을 정의하지도 값을 반환하지도 않지만, 여전히 someIntanotherInt 값을 수정합니다. 입-출력 매개 변수는 함수가 자신의 함수 본문 영역 밖으로 효과를 주기 위한 대안입니다.

✏️ 중첩함수

  • 이 장에서 지금까지 마주친 모든 함수는 전역 범위에서 정의한 전역 함수 (global functions) 였다. 반면, 다른 함수 본문 안에서 함수를 정의할 수도 있는데, 이를 중첩 함수 (nested functions) 라고 한다.
  • 중첩 함수는 기본적으로 외부 세계로부터 숨겨져 있지만, 자신을 둘러싼 함수에선 여전히 호출하고 사용할 수 있다. 중첩 함수를 둘러싼 함수는 또 다른 영역에서 사용할 수 있도록 자신의 중첩 함수 하나를 반환할 수도 있다.
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
  func stepForward(input: Int) -> Int { return input + 1 }
  func stepBackward(input: Int) -> Int { return input - 1 }
  return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero 는 이제 중첩 함수인 stepForward() 를 참조함
while currentValue != 0 {
  print("\(currentValue)... ")
  currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!

0개의 댓글