Swift 문법 CH.5_2

김성환·2020년 8월 23일
0

swift문법

목록 보기
6/11

안녕하세요. 본 포스트에서는 Swift의 함수에 대해 설명하겠습니다.

함수부분은 양이 많아 2부로 나눠 진행하겠습니다.
※ 참고자료 : 꼼꼼한 재은 씨의 Swift:문법편(타언어의 기본적인 개념은 생략합니다.)

목차

  • 1. 함수의 기본 개념
  • 2. 매개변수
  • 3. 일급객체로서의 함수
  • 4. 클로저

3. 일급객체로서의 함수

일급 객체란?

프로그램 언어 안에서 특정 종류의 객체가 일급의 지위를 가지는가에 대한 의미이다.
일급의 지위를 갖기 위해서는 5가지 조건을 만족해야 한다.

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

쉽게 말해, 일반 객체처럼 취급될 수 있다는 것을 일급 객체라고 말한다.

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

일급 함수의 특성

  • 변수나 상수에 함수를 대입할 수 있음
  • 함수의 반환 타입으로 함수를 사용할 수 있음
  • 함수의 인자값으로 함수를 사용할 수 있음

변수나 상수에 함수를 대입할 수 있음

말그대로 변수나 상수에 함수를 대입할 수 있다라는 것이다.
함수가 대입된 변수나 상수는 함수처럼 실행할 수도 있고, 인자값을 입력받을 수도 있다.(반환값도 갖을 수 있다.)
아래는 위의 특성을 잘못 이해한 예시

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

let fn1 = foo(base: 5) 이 문장은 함수를 상수에 넣은 것이 아니라 foo함수에 인자값 5를 넣은 결과를 넣은 것이다. 변수나 상수에 함수를 대입한다는 의미는 함수 그 자체를 넣는다는 뜻이다.
아래는 올바른 예시

func foo(base: Int) -> String {
	return "결과값은 \(base + 1)입니다"
}
let fn2 = foo // 상수 fn2에 foo함수를 넣은 모습
fn2(4) // 대입된 상수 fn2는 이제 함수처럼 사용 가능하다.

※ 변수나 상수에 함수를 대입할 경우 함수는 실행되지 않고 함수 그 자체만 대입이 된다.

func foo(base: Int) -> String {
	print("함수 foo가 실행됩니다")
	return "결과값은 \(base + 1)입니다"
}
let fn4 = foo // 이때, foo는 실행되지 않고 대입만 됨
// 대입 될때 foo 안에 있는 print문이 실행 되지 않는다는 뜻

함수타입이란?

변수나 상수에 함수를 대입하면 그때 그 변수나 상수가 갖는 타입

(인자타입1, 인자타입2, ...) -> 반환 타입
func foo(base: Int) -> String {
	print("함수 foo가 실행됩니다")
	return "결과값은 \(base + 1)입니다"
}
// foo 함수의 함수 타입
(Int) -> String
let fn5 : (Int) -> String = foo // 타입 어노테이션을 이용
let fn5 : (Int) -> String = foo(base:) // 함수이름을 인자레이블까지 본경우

이때, Void 타입은 함수를 선언할 때 생략이 가능했지만 반환값이 없는 함수 타입을 명시할 때는 Void 키워드를 사용해야 한다.
즉, () -> Int 는 가능하지만, (Int) -> () 는 불가능하다는 것이다.
(Int) -> Void
하지만 typealas를 이용해 Void를 ()로 바꾼다면 가능하다.

typealias Void = ()

함수의 반환 타입으로 함수를 사용할 수 있음

말그대로 함수의 반환값을 함수로 사용할 수 있다는 것이다.

func f() -> String{
	return "hello"
}
func pass() -> () -> String{ // pass 함수의 반환 타입은 () -> String이다.
	return f
}
let a = pass // pass함수 자체가 들어간 것
let b = pass() // pass함수의 반환값인 f함수가 들어간 것

함수의 인자값으로 함수를 사용할 수 있음

말그대로 함수의 인자값을 함수로 사용할 수 있다는 것이다.

func incr(param: Int) -> Int{
	return param + 1
}
func broker(base: Int, fn: (Int) -> Int) -> Int { // fn 인자에 함수를 넣음
	return fn(base)
}
broker(base: 3, fn: incr) // broker함수 호출

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

  • defer 블록의 특성
    • defer 블록을 읽기 전에 함수 실행이 종료될 경우 defer 블록은 실행되지 않는다.
    • defer 블록을 함수난 메소드 내에서 여러번 사용 가능하다.
      이때, 가장 마지막에(아래에) 작성된 defer블록부터 역순으로 실행됨.

함수의 중첩

함수 내에 다른 함수를 작성해서 사용할 수 있다.
이때, 함수내에 작성된 함수를 내부 함수, 내부 함수를 포함하는 바깥쪽 함수는 외부 함수로 구분한다.

내부함수의 생명주기

내부 함수는 외부 함수가 실행되는 순간 생성되고, 종료되는 순간 소멸한다.

func outer(base: Int) -> String {// 외부함수
	func inner(inc: Int) -> String {// 내부함수
    	return "\(inc)"
    }
    let result = inner(inc: base +1)
    return result
}
outer(base: 3)
// 실행 결과
"4"

내부함수만 이용하는 경우

일급함수의 특성에 따라 내부함수를 변수나 상수에 집어 넣으면 된다.

func outer(base: Int) -> (Int) -> String {// 외부함수
	func inner(inc: Int) -> String {// 내부함수
    	return "\(inc)"
    }
    return inner
}
let fn1 = outer(base: 1) // inner가 대입된다.

위의 예시의 경우 outer가 실행되고 종료되면서 내부 함수인 inner함수도 소멸되었다. 하지만 소멸되기 전 fn1에 inner함수를 넣었기 때문에 fn1은 inner함수가 있는 것이다.

4. 클로저

클로저란?

일회용 함수를 작성할 수 있는 구문

  • 일회용 함수 = 한 번만 사용할 구문들의 집합이면서, 그 형식은 함수로 작성되어야 하는 제약조건이 있을 때 만들어 사용할 수 있는 함수
  • 타 언어에서도 일회용 함수를 제공한다.(파이썬 : 람다, 자바스크립트 : 익명 함수...)

클로저 표현식

클로저를 표현 할 경우 {}와 in 키워드를 사용한다.

{ (매개변수: 매개변수 타입) -> 반환 타입 in
	실행할 구문
}

쉽게 생각해서 클로저 = 이름 없는 함수
함수 = 이름 있는 클로저 라고 생각하면 된다.
아래 예시와 같이 변수, 상수에 클로저를 넣고 사용할 수도 있다.

let f = { () -> Void in
	print("클로저가 실행됩니다.")
}

클로저의 경량 표현식

위에서 설명한 것은 클로저의 정식 표현식이다.
하지만 클로저를 간편하게 쓸 수 있는 표현식이 있다.
-> 반환 타입매개변수 타입을 생략할 수 있다.
반환 타입을 생략하게 되면 컴파일러가 반환값을 찾아 그 반환값의 타입에 해당하는 타입으로 반환 타입을 정의하게 된다.
매개변수 타입 역시 생략 시 컴파일러가 실제 대입되는 값을 기반으로 매개변수 타입을 추론한다.
아래는 예시이다.

{(s1: Int, s2: Int) -> Bool in // 정식표현
	return s1 > s2
}
{(s1: Int, s2: Int) in // 반환 타입이 생략된 표현 
	return s1 > s2
}
{(s1, s2) -> Bool in // 매개변수 타입이 생략된 표현 
	return s1 > s2
}
{(s1, s2) in // 2개 다 생략된 표현 
	return s1 > s2
}
{(s1, s2) in return s1 > s2} // 한 줄에 표현한 모습

트레일링 클로저

클로저를 다른 함수의 인자값으로 전달할 때에는 자칫 가독성을 해치는 복잡한 구문이 만들어질 수 있다.
아래의 예시를 보면 sort함수안에 인자값으로 클로저함수가 들어가는 모습을 볼 수 있다.

value.sort(by: {(s1, s2) in
	return s1 > s2
})

()와{}가 엉켜서 알아보기 힘들다. 그래서 나온 문법이 바로 트레일링 클로저이다.

트레일링 클로저란?

인자값으로 클로저를 전달하는 특수한 상황에서 문법을 변형할 수 있도록 지원하는 것으로 함수의 마지막 인자값이 클로저일 때, 이를 인자값 형식(위의 예시처럼)으로 작성하는 대신 함수의 뒤에 꼬리처럼 붙일 수 있는 문법을 의미한다.
즉, 함수의 인자값으로 클로저를 전달할 때, 인자값으로 클로저를 넣지말고 함수 뒤에 붙이는 것을 뜻한다.

value.sort() { (s1, s2) in // 위의 구문을 트레일링 클로저로 바꾼 모습
	return s1 > s2
}
  • 이때 인자값의 개수에 따라 문법이 변화한다.
  1. 인자가 1개인 경우 -> 생략 가능
  2. 인자가 여러개인 경우 -> 마지막 인자값만 생략가능
value.sort { (s1, s2) in // 인자가 1개라 생략한 경우
	return s1 > s2
}
divide(base: 100, success: { () in print("연산이 성공했습니다.")}) { () in
	print("연산이 실패했습니다.")
}
// 3개의 인자 중 마지막 인자값만 생략된 모습
profile
개발자가 되고 싶다

0개의 댓글