본문 바로가기
문법관련/Swift

함수(2) (꼼꼼한 재은 씨의 Swift : 문법편)

by print_soo 2022. 5. 26.

일급 객체로서의 함수

Swift는 함수형 패러다임을 따르고 있다. 즉, Swift라는 언어는 일급 객체에 부합하는 언어이다.

 

일급 함수의 특성

1. 객체가 런타임에도 생성이 가능해야 한다.
2. 인자값으로 객체를 전달할 수 있어야한다.
3. 반환값으로 객체를 사용할 수 있어야한다.
4. 변수나 데이터 구조안에 저장할 수 있어야한다.
5. 할당에 사용된 이름과 관계없이 고유한 구별이 가능해야한다.

 

함수가 위 조건을 만족하는 프로그래밍 언어는 함수형 언어가 되며, 함수형 프로그래밍에서는 "함수가 일급 객체"가 될 수 있다.

아래의 몇가지 예시를 통해서 Swift가 왜 함수형 언어이고 왜 일급 객체인지에 대해서 알아보자.

 

 

 

일급 함수의 특성 (1) - 변수나 상수에 함수를 대입할 수 있다.

변수나 상수에 함수를 대입한다는 말은 함수 자체를 변수에 할당한다는 뜻이다. 이렇게 함수가 대입된 변수나 상수는 함수처럼 실행할 수 있고, 인자값을 입력받을 수도 있다.

 

//정수를 입력받는 함수
func foo(base: Int) -> String {
		return "결과 값은 \(base + 1)입니다."
}

let fn1 = foo(base: 5)
//결과 값은 6입니다.

 

let fn2 = foo // fn2 상수에 foo함수 할당

fn2(5) ////결과 값은 6입니다.

 

위의 코드는 fn2에 foo 함수를 대입하고 있다. 함슈 자체가 대입되었기 때문에 fn2는 foo와 이름만 다를 뿐  값은 인자값, 같은 기능, 같은 반환 값을 가지는 함수가 된다. 그렇기 때문에 호출 연산자를 붙이면 함수를 호출할 수 있게 된다. 


💡  변수나 상수에 함수를 대입할 때에는 함수가 실행되 것이 아니라 함수라는 객체 자체를 대입하는 것을 의미한다.

 

위의 말이 무슨 뜻인가는 아래의 예제로 살펴 보겠다.

 

// [함수의 결과 값을 상수에 할당]

//정수를 입력받는 함수
func foo(base: Int) -> String {
    print("함수 foo가 실행됩니다.")
    return "결과 값은 \(base + 1)입니다."
}

let fn = foo(base:5)

// >> 함수 foo가 실행됩니다.


// [함수를 상수에 할당]

//정수를 입력받는 함수
func foo(base: Int) -> String {
    print("함수 foo가 실행됩니다.")
    return "결과 값은 \(base + 1)입니다."
}

let fn = foo

// >> 결과 없음

fn(5)
// >> 함수 foo가 실행됩니다.

 

1번 예시를 보면 함수값 자체를 할당했기 때문에 함수가 실행된다.

2번 예시를 보면 함수 자체를 대입하기 때문에 함수가 실행되지 않는다. 하지만 함수를 할당 받은 상수에 인자값을 넣는다면 함수 실행되는 것을 볼 수 있다.

 

따라서 변수나 상수에 함수를 대입하는 과정에서는 함수 객체 자체만 전달되기 때문에 함수가 실행되지 않는다. 

 

 

 

 

 

일급 함수의 특성(2) - 함수의  반환 타입으로 함수를 사용할 수 있다.

일급 객체인 함수는 반환 값으로 데이터가 아닌 함수 자체를 반환할 수 있다.

 

func first() -> String {
	print("second()에 의해서 first가 호출되었습니다.")
    return "this is first()"
}

func second() -> String {
print("second()가 first를 호출하였습니다.")
	return first()
}

let p = second()

p

// 결과
// second()가 first를 호출하였습니다.
// second()에 의해서 first가 호출되었습니다.

 

 

 

 

 

일급 함수의 특성(3) - 함수의  인자값으로 함수를 사용할 수 있다.

일급 객체인 함수는 인자(파라미터) 값으로 변수 또는 상수가 아닌 함수를 전달할 수 있다.

 

func increase(param: Int -> Int {
	print("base값이 param으로 되어 증가합니다.")
    return param + 1
}

func broker(base: Int, function fn: (Int) -> Int) -> Int {
	print("increase함수에 base인자를 할당합니다.")
    return fn(base)
}

broker(base: 3, function: increase) //4

 

위의 코드에서 broker 함수는 Int인자와, 함수 인자를 받고 있다. 즉 리턴할 때 사용할 함수와 그 함수의 인자값을 인자값으로 할당 받으려고 하고 있다.

 

 

위의 특성을 기준으로 아래의 콜백 함수 예시를 만들어낼 수 있다.

아래의 콜백함수는 몫을 구하는 함수이다. 만약 base가 0이면 fail함수를 호출하고 0이 아니고 계산을 완료하면 defer을 이용하여 success 함수를 호출한다.

 

import UIKit

func successThrough() {
    print("연산 처리 성공")
}

func failThrough() {
    print("처리 과정 오류")
}

func divide(base: Int, success sCallBack: () -> Void, fail fCallBack: () -> Void) -> Int {
    guard base != 0 else { //base 값이 0이면 실패
        fCallBack()
        return 0
    }
    
    defer {
        sCallBack()
    }
    
    return 100 / base
}

divide(base: 0, success: successThrough, fail: failThrough)

 

💡  defer: 함수나 메소드에서 코드의 흐름과 상관없이 가장 마지막에 실행되는 블럭

 

 

defer의 특성(1) - 작성된 위치와 순서에 관계없이 함수가 종료되기 직전에 실행된다.

 

 

 

defer의 특성(2) - defer 블록을 읽기 전에 함수의 실행이 종료될 경우 defer 블록은 실행되지 않는다.

 

 

 

defer의 특성(3) -나의 함수나 메소드 내에서 defer 블록을 여러 번 사용할 수 있다. 이때에는 가장 마지막에 작성된 defer 블록부터 역순으로 실행된다.

 

 

 

defer의 특성(4) - defer 블록을 중첩해서 사용할 수 있다. 이때에는 바깥쪽 defer 블록부터 실행되며 가장 안쪽에 있는 defer 블록은 가장 마지막에 실행된다.

 

 

 

 

 

함수의 중첩

함수의 중첩이란 함수 내에 다른 함수를 작성해서 사용할 수 있다는 것이다.

 

 

순서

  1. 외부함수 호출
  2. 내부함수 호출
  3. 내부함수 반환
  4. 외부함수 반환

내부 함수와 외부함수의 생명주기

  1. 내부 함수: 외부 함수가 실행되는 순간 생성되고 외부 함수가 종료되는순간 소멸된다.
  2. 외부 함수: 프로그램이 실행되는 순간 생성되고 프로그램아 종료되는순간 소멸된다.