[Swift] 함수3

한음·2022년 3월 11일
0

함수의 중첩

함수 내의 함수 -> 중첩 함수 (Nested Function)

중첩 함수를 정의하면, 내부 함수는 외부 함수가 실행되는 순간 생성되고, 종료되는 순간 소멸.
외부 함수는 프로그램이 실행될 때 생성, 종료될 때 소멸.
일반적으로 함수는 자신을 참조하는 곳이 있으면 생성, 참조하는 곳이 사라지면 제거되는 생명 주기를 가짐.

func outer(base: Int) -> String {
    func inner(_ int: Int) -> String {
        return "\(int) 를 반환합니다."
    }
    let result = inner(base + 1)
    return result
}

print(outer(base: 5)) // 6 를 반환합니다.

내부 함수 반환 예제

func outer() -> (Int) -> String {
    func inner(_ base: Int) -> String {
        return "\(base) 반환"
    }
    return inner(_:)
}

let fn1 = outer()
print(fn1(30)) // 30 반환

위와 같은 방식을 통해 외부에서 내부 함수에 접근 가능.
fn1 에 inner(_:) 가 할당되었기 때문에, inner 는 소멸하지 않는다.
반면 outer()let fn1 = outer() 구문 이후 소멸된다. 더이상 자신을 참조하는 곳이 없기 때문.

func outer(base: Int) -> (Int) -> Int {
    func inner(_ param:Int) -> Int {
        return param + base
    }
    return inner
}

let result = outer(base: 30)
print(result(10)) // 40

내부 함수에서 외부 함수의 지역 변수 base 를 참조하는 예시.
inner(_:) 이 클로저를 갖기 때문에 base 를 참조해도 오류 없이 작동한다.

클로저
- 두 가지로 이루어진 객체. 하나는 내부 함수이며, 또 다른 하나는 내부 함수가 만들어진 주변 환경
- 클로저는 외부 함수 내에서 내부 함수를 반환하고, 내부 함수가 외부 함수의 지역 변수나 상수를 참조할 때 만들어짐.

=> 내부 함수, 내부 함수에 영향을 미치는 주변 환경(Context) 를 모두 포함한 객체.

outer 가 남아있는 것이 아니라, 값만 복사되어

func inner(_ param: Int) -> Int {
	return param + 30
}

과 같은 형태로 저장된다. ( 값이 캡쳐되었다 )

클로저

위의 클로저는 함수형 언어에서 공통으로 가지는 소프트웨어 아키텍처적인 개념이고, 이 장의 클로저는 익명 함수를 지칭한다. 전혀 다른 것은 아니고, 이 익명 함수가 위의 내용을 포함.

클로저는 자신이 정의되었던 문맥으로부터, 모든 상수와 변수의 값을 캡처하거나 레퍼런스를 저장하는 익명 함수.

스위프트에서 클로저라고 부르는 객체는 세 가지 경우 중 하나.

    1. 전역 함수: 이름이 있으며, 주변 환경에서 캡처할 값이 없는 클로저
    2. 중첩 함수: 이름이 있으며 자신을 둘러싼 함수로부터 값을 캡처할 수 있는 클로저
    3. 클로저 표현식: 이름이 없으며 주변 환경으로부터 값을 캡처할 수 있는 경량 문법으로 작성된 클로저
    

클로저 표현식

let f = { () -> () in 
	print("클로저 실행")
}
f() // 클로저 실행

중괄호 내에 타입을 선언한다. 반환값이 없을 때에도 빈 괄호로 표시해주어야 함.
할당 없이 바로 실행도 가능. 이때는 소괄호로 클로저를 감싸주어야 함.

({(_ base: Int) -> Void in
    print("\(base) 입력됨.")
})(4) // 4 입력됨.
예시
var a = [13,4,5,3,1]
a.sort(by: {(s1: Int, s2: Int) -> Bool in return s1 > s2})
print(a)

클로저는 반환값, 입력값 타입을 생략할 수 있음. 컴파일러가 추론해줌.

var a = [13,4,5,3,1]
a.sort(by:{s1, s2 in return s1 < s2})
print(a) // 1 3 4 5 13

매개변수도 생략 가능. 미리 할당된 내부 상수를 이용해, 순서대로 호출가능.

var a = [13,4,5,3,1]
a.sort(by:{return $0 < $1}) // $0 부터 첫 번째 인자
print(a) // 1 3 4 5 13

트레일링 클로저

클로저를 인자 값으로 전달하는 상황에서 문법을 변형할 수 있음.
마지막 인자 값이 클로저일 때, 이를 인자값 형식으로 작성하는 대신, 함수의 뒤에 꼬리처럼 붙일 수 있는 문법을 의미. 이때 레이블은 생략.

var a = [2,13,4,5,3,1]
a.sort(){(s1, s2) in return s1 > s2}

필요한 인자 값이 하나일때 아예 소괄호 생략도 가능

var a = [2,13,4,5,3,1]
a.sort{return $0 > $1}
> a

@escaping @autoclosure

클로저를 함수나 메소드의 인자값으로 사용할 때에는 용도에 따라 @escaping@autoclosure 속성을 부여할 수 있음.

@escaping

인자값으로 전달된 클로저를 저장해 두었다가 나중에 다른 곳에서도 실행할 수 있도록 함.

인자값으로 전달된 클로저는 기본적으로 "탈출불가" 의 성격을 가짐. 해당 클로저를 1. 함수 내에서 2. 직접 실행을 위해서만 사용해야 하는 것을 의미.

아래는 예시.

func callback(fn: () -> Void) {
    let f = fn // 오류
    f()
}

callback(fn: {() -> Void in print(123)})

인자로 전달된 클로저를 변수나 상수에 대입하려면 @escaping 을 이용해 탈출 가능 인자로 만들어줌.

func callback(fn: @escaping () -> Void) {
    let f = fn
    f()
}

callback(fn: {print(123)}) // 123

@autoclosure

인자값으로 전달된 일반 구문이나, 함수 등을 클로저로 래핑하는 역할. 일반 구문을 넣더라도 컴파일러가 알아서 클로저로 만들어 사용.

func condition(statement: () -> Bool ){
    if statement() {
        print("true")
    } else {
        print("false")
    }
}

condition(statement: {5 > 3}) 
condition{5 > 3}

// 위 두 구문은 같은 의미. 클로저 경량화 + 트레일링 클로저
// @autoclosure 를 사용하면 다음과 같다.

func condition(statement: @autoclosure () -> Bool) {
    if statement() {
        print("true")
    } else {
        print("false")
    }
}

condition(statement: (5 > 2)) // true

추가 예시

var arr : [String] = []
func f(_ function: @autoclosure () -> Void) {
    arr = Array(repeating: "", count: 3) // 배열 크기 3으로 확장
    function()
}

f(arr.insert("hello", at: 1)) // 평문을 넣어도 정상 작동한다.
print(arr) // ["", "hello", "", ""]
profile
https://github.com/0hhanum

0개의 댓글