📱 Mobile/🚀 Swift

[Swift] 함수 - (2)

exception_log 2021. 1. 25. 13:11

<!— 본 포스트는 "꼼꼼한 재은씨의 Swift : 문법편"을 공부하며 직접 정리한 포스트입니다. —>

 

🚀작성자의 Swift 정리 원본인 Notion 구경하기 -> www.notion.so/Swift-59150070adb0467ea11d4f69090dbb24

 

꼼꼼한 재은씨의 Swift 문법편

A new tool for teams & individuals that blends everyday work apps into one.

www.notion.so

일급 객체로서의 함수

일급 함수의 특성

객체가 다음의 조건을 만족하는 경우 이 객체를 일급 객체로 간주한다.

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

🚀 함수가 이런 조건을 만족하면 이를 일급 함수라고 하고, 그 언어를 함수형 언어로 분류한다. 즉, 함수형 언어에서는 함수가 일급 객체로 대우받는다.

🚀 함수가 일급 객체로 대우받는다면 런타임에도 함수의 생성이 가능하고, 매개변수나 반환값으로 함수를 전달할 수 있으며, 함수를 변수나 데이터 구조 안에 저장할 수 있을 뿐 아니라 함수의 이름과 관계없이 고유한 구별이 가능하다.

 

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

func foo(base: Int) -> String {
	return "결과값은 \(base + 1)입니다.")
}

let fn1 = foo(base: 5)

🚀 함수의 결과값을 변수나 상수에 대입하는 것이 아니라 "함수 자체를 대입"하는 것.

[예시 1] - 변수나 상수에 함수를 대입할 떄에는 함수가 실행되는 것이 아니라 함수라는 객체 자체만 대입된다.

func foo(base: Int) -> String { // 정수 인수를 받고 문자열 타입을 반환하는 함수
	print("함수 foo가 실행됩니다.")
	return "결과값은 \(base + 1)입니다.")
}

let fn2 = foo(base: 5)

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

[예시 2] - 함수 자체를 대입하는 구문

let fn4 = foo
// 출력 결과 없음

fn4(7)
// 함수 foo가 실행됩니다!

🚀 함수 타입 작성

(인자 타입1, 인자 타입2, ...) -> 반환 타입

위의 foo 함수를 함수 타입 형태로 표현하면 아래와 같다.
(Int) -> String

foo : 함수의 이름
foo(base:) : 함수의 식별자

🚀 함수 타입에 대한 타입 어노테이션을 누락하는 경우 에러가 발생한다.

func boo(age: Int) -> String {
	return "\(age)"
}

func boo(age: Int, name: String) -> String {
	return "\(name)의 나이는 \(age)세 입니다.")
}

let t = boo // (X) 위의 두 함수 중 어느 것인지 모호하기 때문

// 해결 방법 1 : 타입 어노테이션을 통해 입력받을 함수의 타입을 지정
let t1: (Int, String) -> String = boo

// 해결 방법 2 : 함수의 식별값을 통해 입력받을 정확한 함수를 지정
let t2 = boo(age:name:)

🚀 타입 어노테이션을 사용하는 경우에는 입력받을 값의 타입이 명확하기 때문에 컴파일러가 알아서 함수를 찾아서 대입할 수 있다.

🚀 그러나 타입 어노테이션과 함수 이름의 조합으로 대입 구문을 구성하면 안되는 경우도 있다. 동일한 함수 타입을 사용하지만 매개변수명이 서로 다른 함수의 경우가 그러하다.

func boo(age: Int, name: String) -> String {
	return "\(name)의 나이는 \(age)세 입니다."
}

func boo(height: Int, nick: String) -> String {
	return "\(nick)의 키는 \(height)입니다.")
}

let fn03: (Int, String) -> String = boo // (X)
let fn04: (Int, String) -> String = boo // (X)

함수의 식별자를 사용해 명확하게 구분해주어야 한다. 

let fn03: (Int, String) -> String = boo(age:name:)
let fn04: (Int, String) -> String = boo(height:nick:)

or

let fn03 = boo(age:name:)
let fn04 = boo(height:nick:)

🚀 다양한 함수 타입

// 인자값이 없는 경우
func foo() -> String {
	return "Empty values"
}

// 인자값이 없는 경우의 함수 타입
() -> String

// 반환값이 없는 경우
func boo(base: Int) {
	print("param = \(base)")
}

// 반환값이 없는 경우 함수 타입
(Int) -> ()

// 인자값, 반환값 모두 없는 경우
func too() {
	print("empty values")
}

// 인자값 반환값 모두 없는 경우의 함수 타입
() -> ()

🚀 함수 타입을 표현할 때 반환값이 없는 경우 빈 괄호 대신 Void를 사용하여 몀시적으로 값이 없음을 표시하기도 함

public typealias Void = ()
(Int) -> Void
() -> Void

 

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

func desc() -> String {
	return "this is desc()"
}

func pass() -> String {
	return desc
}

let p = pass()
p()

// "this is desc()"

 

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

일급 함수는 반환값으로 함수를 사용할 수 있을 뿐만 아니라 다른 함수의 인자값으로 함수를 전달할 수 있는 특성도 가지고 있다.

func incr(paramL Int) -> Int {
	return param + 1
}

// 중개 역할을 하는 함수는 '브로커'라고 부른다.
func broker(base: Int, function fn: (Int) -> Int) -> Int {
	return fn(base)
}

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

🚀 콜백 함수를 사용하는 예

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

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

func divide(base: Int, success sCallBack: () -> Void, fail fCallBack: () -> Void) -> Int {
	guard base != 0 else {
		fCallBack() // 실패 함수 실행
		return 0
	}
	

	defer {
		sCallBack() // 성공 함수 실행
	}
	
	return 100 / base

}

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

// 실행 결과 >>
연산 처리 성공.

🚀 defer 블록 : 함수나 메소드에서 코드의 흐름과 상관없이 가장 마지막에 실행되는 블록이다. 종료 시점에 맞추어 처리해야 할 구문이 있다면 우리는 어디에 작성해야 할지 고민하지 않고 defer 블록에 넣어두기만 하면 된다.

이 블록은 함수에서 사용된 각종 리소스의 처리나 해제, 연결 종료 등의 구문을 처리하는 용도로 유용하게 사용된다. defer 블록의 특성은 아래와 같다.

  1. defer 블록은 작성된 위치와 순서에 관계없이 함수가 종료되기 직전에 실행된다.
  1. defer 블록을 읽기 전에 함수의 실행이 종료될 경우 defer 블록은 실행되지 않는다.
  1. 하나의 함수나 메소드 내에서 defer 블록을 여러 번 사용할 수 있다. 이때에는 가장 마지막에 작성된 defer 블록부터 역순으로 실행된다.
  1. defer 블록을 중첩해서 사용할 수 있다. 이때에는 바깥쪽 defer 블록부터 실행되며 가장 안쪽에 있는 defer 블록은 가장 마지막에 실행된다.

defer는 주로 함수가 연산을 처리하는 과정에 영향을 끼치지 않으면서 실행해야 할 다른 내용이 있을 때 사용하거나, 함수를 종료하기 직전에 정리해야 하는 변수나 상수값들을 처리하는 용도로 사용된다. 다시 말해, 함수를 종료하기 전에 처리해야 하는 변수나 상수들 중에서 처리 시점이 모두 달라서 적절한 처리 시점을 잡기 어려울 때 defer 구문을 통해 처리하면 된다는 뜻이다.

🚀 익명 함수 : 함수의 형태를 가지지만 이름이 부여되지 않으며, 일회용이기 때문에 여러 가지 간편한 작성 형식을 따른다는 특성이 있다. 스위프트에서는 클로저(Closure)라고 부른다.

divide(base: 30, 
	success: {
		() -> Void in
		print("연산 처리 성공")
	}, 
	fail: {
		() -> Void in
		print("처리 과정 오류")
	}
}

함수의 중첩

🚀 스위프트에서 함수는 중첩해서 사용할 수 있다. 함수 내에 다른 함수를 작성해서 사용할 수 있다는 것이다. 이렇게 작성된 함수를 중첩 함수라고 한다. 함수 내에 작성된 함수는 내부 함수, 내부 함수를 포함하는 바깥쪽 함수는 외부 함수로 구분한다.

🚀 함수를 중첩해서 정의하면 내부 함수는 외부 함수가 실행되는 순간 생성되고 종료되는 순간 소멸한다. 외부 함수는 프로그램이 실행될 때 생성되고 프로그램이 종료될 때 소멸하지만, 내부 함수는 외부 함수의 실행과 종료 사이에서 생겼다가 소멸한다. 이것이 내부 함수의 생명주기 이다.

// 외부 함수
func outer(base: Int) -> String {
	// 내부 함수
	func inner(inc: Int) -> String {
		return "\(inc)를 반환한다."
	}

	let result = inner(inc: base + 1) 
	return result

}

outer(base: 3)

let fn1 = outer(param: 3) // outer() 가 실행되고, 그 결과로 inner가 대입된다. 

// 위의 경우 내부함수인 inner는 결과값으로 반환되어 상수에 참조되었으므로 참조 카운트가 존재한다.
// 이로 인해 함수의 종료에도 혼자 생명을 유지할 수 있다.

 

반응형

'📱 Mobile > 🚀 Swift' 카테고리의 다른 글

[Swift] 구조체와 클래스 - (1)  (0) 2021.01.26
[Swift] 함수 - (3)  (0) 2021.01.25
[Swift] 함수 - (1)  (0) 2021.01.22
[Swift] 옵셔널 (Optional)  (0) 2021.01.21
[Swift] 집단 자료형  (0) 2021.01.20