본문 바로가기

학습 노트/Swift (2021)

049 ~ 060. Functions (함수)

Functions

특정한 기능을 수행하는 코드 조각이다.
코드의 재사용이 가능해 지기 때문에 유지보수에 유리해진다.

Calling Functions (함수 호출)

Syntax

functionName(parameters)
print("hello")

 

결과

hello

Defining Functins (함수 정의)

Syntax

func name(parameters) -> ReturnType {
    statements
}
func printHello() {
    print("hello")
}

printHello()

 

결과

hello

Return Values (반환)

Syntax

func name(parameters) -> ReturnType {
    statements
    return expression
}
func add() -> Int {
    return 1 + 2
}

let r = add()
print(r)

 

결과

3

생성한 함수는 조건문과 반복문에서 사용할 수도 있고,
반대로 함수를 생성할 때 조건문과 반복문을 포함할 수도 있다.

func add() -> Int {
    return 1 + 2
}

if add() == 3 {
    print("three")
}

func example() {
    let rnd = Int.random(in: 1...100) //1~100까지의 정수 중 랜덤 저장.
    
    if rnd % 2 == 1 {
        print(1)
    } else {
        print(2)
    }
}

example()
결과

three
1 //홀수인 경우 1, 짝수인 경우 2

 

Parameters

함수의 전달 값.

Statement

func name(parameters) -> ReturnType {
    statements
}

(name1: Type, name2: Type)
//정수 a와 정수 b를 받아 합한 결과를 Int로 반환하는 함수
func add(a: Int, b: Int) -> Int {
    return a + b
}

print(add(a: 12, b: 34))

 

결과

46

함수의 생성과 호출시 사용되는 parameter는 다음과 같이 구분할 수 있다.

(a: Int, b: Int)
a, b Formal parameter
Int Actual parameter
Argument

또한 해당 parameter는 함수가 생성되고 동작하는 동안에만 보장된다.

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

func minus(a: Int, b: Int) -> Int {
    return a - b
}

print(add(a: 12, b: 34))
print(minus(a: 34, b: 12))

 

결과

46
22

위와 같이 a와 b가 중복되지만 각각의 함수에서만 유효하므로 오류도 발생하지 않고, 동작도 정상이다.

 

Argument Label

Syntax

(name: Type)

(label name: Type)
( name : Type )
. Argument Label
Parameter Label
. . .
( label name : Type )
. Argument Label Parameter Name . . .
func hello(to name: String) {
    print("hello, \(name)")
}

hello(to: "there")

 

결과

hello, there

to는 argument label이므로 함수를 호출할 때 사용한다.
name은 parameter name이므로 함수 내에서 사용한다.

func hello(name: String) {
    print("long time no see, \(name)")
}

func hello(to name: String) {
    print("hello, \(name)")
}

hello(name: "there")
hello(to: "there")

 

결과

long time no see, there
hello, there

두 함수 모두 hello라는 같은 이름의 함수이지만
첫번째 함수는 argument label과 parameter name이 name으로 같은 hello name 함수이고,
두 번째 함수는 argument label이 to인 hello to 함수이므로 다르게 취급된다.

func hello(_ name: String) {
    print("sneaky, \(name)")
}

hello("there")

 

결과

sneaky, there

또한 wildcard를 사용하여 argument label을 생략할 수 있다.
이 경우 parameter name이 argument label을 '대신'하는 것이 아닌 '생략'하는 것이므로,
함수를 호출하는 경우에도 생략하여 parameter를 전달할 수 있다.

Tip

함수의 이름은 동사
argument label은 전치사
parameter name은 명사
로 구성하여 함수를 읽었을 때 한 문장이 되도록 구성하면 편리하다.


Variadic Parameters (가변 파라미터)

Statement

(name: Type...)
func printSum(_ nums: Int...) {
    var sum = 0
    for num in nums {
        sum += num
    }
    print(sum)
}

printSum(1, 2, 3)

 

결과

6

가변 파라미터를 사용하면 한 번에 여러 개의 argument를 전달할 수 있다.
이때, argument는 배열의 형식이다.

func printSum(_ nums: Int..., b: Double...) {
    var sum = 0
    for num in nums {
        sum += num
    }
    print(sum)
}

printSum(1, 2, 3)

 

결과

//error

가변 파라미터는 함수마다 하나씩밖에 생성하지 못한다.

func printSum(_ nums: Int... = 0) {
    var sum = 0
    for num in nums {
        sum += num
    }
    print(sum)
}

printSum(1, 2, 3)

 

결과

//error

또한, 가변 파라미터는 기본값을 가질 수 없다.

 

In-Out Parameters (입출력 파라미터)

Statement

(name: inout Type)

functionName(argLabel: &expr)
var num1 = 12
var num2 = 56

func swap(_ a: Int, with b: Int) {
    var ex = a
    a = b
    b = ex
}

 

결과

//error

함수에서 사용하는 parameter는 임시 변수가 아닌 임시 상수이기 때문에 조작할 수 없다.
또한, 함수에 값이 전달 될 때 값이 복사되어 전달되기 때문에 함수 내에서 변하더라도 함수 밖에선 영향이 없다.

var num1 = 12
var num2 = 56

func swap(_ a: inout Int, with b: inout Int) {
    let ex = a
    a = b
    b = ex
}

swap(&num1, with: &num2)
print(num1)
print(num2)

 

결과

56
12

함수호출 시 &를 사용해서 parameter를 전달해야 한다.

입출력 파라미터는 함수 호출 시 값을 copy in 하는 것은 일반 파라미터와 동일하지만
함수 종료시 copy out으로 값을 반환한다는 것이 다르다.

//1
var num1 = 12
var num2 = 56

func swap(_ a: inout Int, with b: inout Int) {
    let ex = a
    a = b
    b = ex
}

swap(&num1, with: &num1)

//2
let a = 12
let b = 56
swap(&a, with: &b)

//3
swap(&12, with: &56)

 

결과

//error

1의 경우에서 보듯 같은 parameter를 두 번 이상 전달할 수 없다.
2의 경우 상수를 전달할 수 없다.
3의 경우 literal을 전달할 수 없다.
가변 파라미터를 전달 할 수 없다.
또한, 가변 파라미터와 마찬가지로 기본값을 설정할 수 없다.

 

Function Notation (함수 표기법)

Syntax

functionName(label:)
함수 호출문에서 Argument가 빠진 형태를 가지고 있다.

functionName(label:label:)
parameter가 두 개 이상인 경우 위와 같이 사용한다.

functionName
parameter가 없는 경우 괄호까지 생략한다,
일반 문서에서 사용할 때 구분을 위해 괄호를 사용하는 것은 괜찮지만,
코드 내에서 사용 할 때는 반드시 괄호를 생략해야 한다.

함수 표기법을 알고 있으면 레퍼런스 문서를 참고하는 데 도움이 된다.

 

Function Types

함수는 First-class Citizen이다.

  • 변수나 상수에 저장할 수 있다
  • parameter로 전달할 수 있다.
  • 함수에서 반환할 수 있다.
Syntax

(ParameterTypes) -> ReturnType
//전달할 parameter가 없는 경우
func hello() {
    print("hello")
}

let f1 = hello
f1()

 

결과

hello

상수나 변수에 저장할 수 있다.

//전달할 parameter가 있는 경우
func prHello(with name: String) {
    print("hello \(name)")
}

let f2: (String) -> () = prHello(with:)

let f3 = prHello(with:)

f3("there")
f3("there")
결과

hello there
hello there

상수나 변수에 저장해서 사용 시 parameter의 argument label을 생략해야 한다.

//parameter가 두 개 이상인 경우
func add(a: Int, b: Int) -> Int {
    return a + b
}

var f4: (Int, Int) -> Int = add(a:b:)

f4(1, 2)

 

결과

3
//argument label이 생략된 경우
func add(_ a: Int, with b: Int) -> Int {
    return a + b
}

var f4: (Int, Int) -> Int = add(:with:)

f4(1, 2)

 

결과

3
//입출력 parameter를 사용하는 경우
var a = 1
var b =2

func swap(_ a: inout Int, _ b: inout Int) {
    let ex = a
    a = b
    b = ex
}

let f5 = swap(_:_:)
f5(&a, &b)

print(a)
print(b)

 

결과

2
1
//가변 parameter를 사용하는 경우
func sum(_ number: Int...) {
    var sum = 0
    for num in number {
        sum += num
    }
    print(sum)
}

let f6 = sum
f6(1, 2, 3)

 

결과

6
//함수 안에서 함수를 호출하는 경우
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

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

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

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

func refunc(from op: String) -> ((Int, Int) -> Int)? {
    switch op {
    case "+":
        return add(_:_:)
    case "-":
        return sub(_:_:)
    case "*":
        return mul(_:_:)
    case "/":
        return sub(_:_:)
    default:
        return nil
    }
}

let af = refunc(from: "+")
print(af?(1, 2))

 

결과

Optional(3)

return 화살표가 중첩돼 혼란스럽지만 첫 번째만 return 화살표이고, 뒤는 그냥 형심임에 유의하자.
default로 nil을 반황해야 하기 때문에 반환식에 Optional를 부여해야 할 필요가 있다.
위의 코드는 Typealias를 활용하여 보기 좋게 바꿀 수 있다.

//함수 안에서 함수를 호출하는 경우
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

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

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

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

typealias arithfunc = (Int, Int) -> Int
func refunc(from op: String) -> arithfunc? {
    switch op {
    case "+":
        return add(_:_:)
    case "-":
        return sub(_:_:)
    case "*":
        return mul(_:_:)
    case "/":
        return sub(_:_:)
    default:
        return nil
    }
}

let af = refunc(from: "+")
print(af?(1, 2))

 

결과

Optional(3)

가독성을 위한 작업이기 때문에 결과는 바뀌지 않는다.

print(refunc(from: "*")?(12, 34))

 

결과

Optional(408)

왜?

  • print(refunc(from: "*")?(12, 34)) 에서 출력 함수인 print() 해체
  • refunc(from: "*")?(12, 34)에서 String 타입의 연산자를 전달한다. 이때, 해당하지 않는 문자열에 대해 nil을 반환하기 때문에 Optional로 선언한다.
  • 해당하는 연산자로 판단되었을 경우 refunc 내에서 다시 호출하고 있는 함수의 반환식이 '(Int, Int) -> Int'에 따라 정수 두 개를 전달하고 결과물인 정수 값을 반환받는다.
  • Optional chaining에 따라 결과는 Optional이며, 연산자 식별에 실패했을 경우 내부 호출 함수에 접근하지 않고 nil을 반환한다.


Nested Functions

다른 함수의 내부에 포함이 되면 nested functions이라고 부른다.

func outer() {
print("out")
}

func inner() {
print("in")
}

outer()
inner()

 

결과

out
in
func outer() {
    func inner() {
        print("in")
    }
    print("out")
}

outer()
inner()

 

결과

//error

nested function은 선언된 scope 외부에서 따로 호출할 수가 없다.

func outer() -> () -> () {
    func inner() {
        print("in")
    }
    print("out")
    return inner
}

let f = outer()
f()

 

결과

out
in

단, 함수를 반환하도록 바꾸면 외부 scope에서도 간접적으로 호출할 수 있다.

 

Implicit Return

Syntax

Explicit Return
func name(parameter) -> ReturnType {
    statements
    return expression
}

Implicit Return
func name(parameters) -> ReturnType {
    single_expression
}
func add(a: Int, b: Int) -> Int {
    return a + b
}

print(add(a: 1, b: 2))

 

결과

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

print(add(a: 1, b: 2))

 

결과

3

단일 표현식으로 구현된 함수의 body에 return 식만 존재하면 return 키워드를 생략할 수 있다.
참고로 아래의 코드는 단일 표현식이 아니다.

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

print(add(a: 1, b: 2))

 

결과

//error

 

Nonreturning Function

//1
func returnSome() -> Int {
    return 0
}

let result = returnSome()
print(result)

//2
func returnNone() {
    return
}

returnNone()
print("end")

 

결과

0
end

Nonereturning은 '값을 반환하지 않는다.' 혹은 '제어를 전달하지 않는다.'의 뜻을 가진다.
프로그램이 종료되거나 error처리에 사용된다.

func terminator() -> Never {
    fatalError("msg")
}

terminator()
print("after")
결과

__lldb_expr_28/MyPlayground.playground:110: Fatal error: msg

예정해 둔 "msg"를 포함한 에러가 출력되고, 이후의 'print("after")'는 실행하지 않는다.

enum CustomErr: Error {
    case something
}

func alertErr() throws -> Never {
    throw CustomErr.something
}

do {
    try alertErr()
    print("after")
} catch {
    print(error)
}

 

결과

something
func terminate() -> Never {
    fatalError("yeap")
}

func dothis(with value: Int) -> Int {
    guard value >= 0 else {
    terminate()
    }
    return 0
}

dothis(with: -1)

 

결과

__lldb_expr_35/MyPlayground.playground:114: Fatal error: yeap

guard문에선 else를 통해 반드시 종료해 줘야 하는데,
return과 throw 대신에 nonereturnning 함수를 사용할 수도 있다.

반환이 Never로 선언하는 동시에 프로그램을 종료하거나 에러를 throw 하도록 강제한다.
Never 대신 Void를 사용할 경우 값을 반환하지 않는다는 것은 동일하지만 제어는 전달하므로 반드시 구별해 사용해야 한다.

 

@discardableResult

함수 선언문 위에 작성하여 compiler의 경고를 숨긴다.

@discardableResult
func Name() Int {
    return 4
}

 

return이 존재하는 경우에만 효력이 있으며, 이외엔 어떠한 영향도 없다.
직접 선언한 함수가 아닌 경우 호출 시에 wildcard를 사용하여 구현한다.

_ = Name()