@escaping
@escaping 속성은 인자값으로 전달된 클로저를 저장해 두었다가, 후에 다른 곳에서 실행할 수 있도록 허용해주는 속성이다. 예제를 보자.
func callback(fn: () -> Void) {
fn()
}
callback {
print("Closure가 실행되었습니다.")
}
/* 실행결과
Closure가 실행되었습니다.
정의된 함수 callback(fn:)은 매개변수를 통해 전달된 클로저를 함수 내부에서 실행하는 역할을 한다. 이번엔 코드를 다음과 같이 바꿔보자.
func callback(fn: () -> Void {
let f = fn // 클로저를 상수 f에 대입
f() // 대입된 클로저 실행
}
전달된 클로저를 다음과 같이 실행하면 'Non-escaping parameter 'fn' may only be called'라는 오류가 발생하게되는데 뜻은 Non-escaping 파라미터은 fn은 오직 직접 호출하는 것만 가능하다는 의미이다. 클로저를 변수에 대입할 수 없고 바로 호출만 할 수 있다니 이게 무슨 의미일까?
스위프트에서 함수의 인자값으로 전달된 클로저는 기본적으로 탈출불가(non-escape)의 성격을 가진다. 이는 해당 클로저를 함수내에서 직접 실행을 위해서만 사용해야한다는 것을 의미하며, 이 때문에 함수 내부라 할지라도 변수나 상수를 대입할 수 없다. 변수나 상수에 대입하는 것을 허용한다면 내부 함수를 통한 캡처(Capture) 기능을 이용해 클로저가 함수 바깥으로 탈출할 수 있기 때문이다.
또한 인자값으로 전달된 클로저는 중첩된 내부 함수에서 사용할 수도 없다. 내부 함수에서 사용할 수 있도록 허용할 경우, 이 역시 컨텍스트(Context)의 캡처를 통해 탈출될 수 있기 때문이다. 다음 예제를 실행하면 오류가 발생한다
func callback(fn: () -> Void) {
func innerCallback() {
fn()
}
}
하지만 코드를 작성하다 보면 클로저를 변수나 상수에 대입하거나 중첩함수 내부에서 사용해야할 경우가 생긴다. 이 때 사용되는 것이 바로 @escaping속성이다. 이 속성을 클로저에 붙여주면 해당 클로저는 탈출이 가능한 인자값으로 설정된다. 예제를 살펴보자.
func callback(fn: @escaping () -> Void) {
let f = fn
f()
}
callback {
print("Closure가 실행되었습니다.")
}
/* 실행결과
Closure가 실행되었습니다.
이제 입력된 클로저는 변수나 상수에 정상적으로 할당될 뿐만 아니라, 중첩된 내부 함수에 사용할 수 있으며 함수 바깥으로 전달할 수도 있다.
@autoclosure
@autoclosure 속성은 인자값으로 전달된 일반 구문이나 함수 등을 클로저로 래핑(Wrapping)하는 역할을 한다. 쉽게 말해 이 속성이 붙어 있을 경우, 일반 구문을 인자값으로 넣더라도 컴파일러가 알아서 클로저로 만들어 사용한다는 것이다.
이 속성을 적용하면 인자값을 '{ }' 형태가 아니라 '( )' 형태로 사용할 수 있다는 장점이있다. 인자값을 직접 클로저 형식으로 넣어줄 필요가 없기 때문이다. 이는 코드를 조금 더 이해하기 쉬운 형태로 만들어준다. 다음 예제를 보자.
// 함수 정의
func condition(stmt: () -> Bool) {
if stmt() == true {
print("결과가 참입니다")
} else {
print("결과가 거짓입니다")
}
}
함수 condition(stmt:)는 참 / 거짓을 반환하는 클로저를 인자값으로 전달받고 그 결과값을 문장으로 출력해 주는 역할을한다. 현재까지는 이 함수를 실행하고자 하는 경우, 다음 두 가지 방법을 사용할 수있다.
// 실행방법 1 : 일반구문
condition(stmt: {
4 > 2
})
// 실행방법 2 : 클로저 구문
condition {
4 > 2
}
위의 클로저 구문은 다음과 같이 경량화 과정이 진행된 구문이니 다시 한 번 확인해보자.
// STEP 1 : 경량화X
condition { () -> Bool in
return (4 > 2)
}
// STEP 2 : 클로저 타입 선언 생략
condition {
return (4 > 2)
}
// STEP 3 : 클로저 반환구문 생략
condition {
4 > 2
}
위 예제에서 볼 수 있듯 일반 실행 구문이나 트레일링 클로저 어느 것을 적용하더라도 원하는 구문을 '{ }'형태로 감싸 클로저 형태로 만든 다음에 인자값으로 전달해야한다. 하지만 @autoclosure 속성을 붙이면 이 같은 제약이 사라지고, 구문만 인자값으로 전달해 줄 수 있게된다.
func condition(stmt: @autoclosure () -> Bool {
if stmt() == true {
print("결과가 참입니다")
} else {
print("결과가 거짓입니다")
}
}
매개변수 @autoclosure 속성을 적용했다. 이렇게 속성을 적용하면 함수 condition(stmt:)는 다음과 같은 방식으로 호출할 수 있게 된다. 아니, 반드시 다음과 같이 호출해야만 한다. @autoclosure 속성의 영향으로, 더이상 일반 클로저를 인자값으로 사용할 수 없기 때문이다. 같은 이유로, 클로저일 때 사용할 수 있는 트레일링 클로저 구문도 @autoclosure 속성이 붙고나면 더이상 사용할 수 없다.
// 실행방법
condition(stmt: ( 4 > 2 ))
클로저가 아니라 그 안에 들어가는 내용만 인자값으로 넣어줄 뿐이다. 이렇게 전달된 인자값은 컴파일러가 자동으로 클로저 형태로 감싸 처리해주게 된다. 이속성에 대한 설명 중에서 인자값을 '{ }' 형태가 아니라 '( )'형태로 사용할 수 있도록 해준다는 것은 바로 이 같은 의미이다.
@autoclosure 속성과 관련해 알아두어야 할 개념이 하나있다. 바로 '지연된 실행'이다. 다음 구문을 보자.
// 빈 배열 정의
var arrs = [String]()
func addVars(fn: @autoclosure () -> Void) {
// 배열 요소를 3개까지 추가하여 초기화
arrs = Array(repeating: "", count: 3)
// 인자값으로 전달된 클로저 실행
fn()
}
// 구문 1 : 아래 구문은 오류 발생
arrs.insert("KR", at: 1)
문자열을 요소로 가지는 빈 배열 arrs를 정의하였다. 아직 초기화만 되어 있을 뿐 내용은 모두 비어있는 상태로, addVars(fn:) 함수 내부에서는 이 배열의 사이즈를 3으로 확장하고 빈 값들로 초기화한다. 즉, addVars(fn:) 함수가 실행되기 전까지 이 함수의 인덱스는 0까지 밖에 없다는 것이다.
이 때문에 맨 마지막에 작성된 arrs.insert(at:) 메소드는 오류가 발생하게 된다. 마지막 구문의 내용은 arrs 배열의 두번째 인덱스 위치에 "KR" 값을 입력하는 것인데, 아직 배열의 인덱스가 그만큼 확장되어 있지 않기 때문이다.
이제 아래의 다음과 같은 구문을 추가해보자. 동일한 구문이지만, 이를 함수 addVars(fn:)의 인자값으로 넣겠다.
// 구문 2 : 아래 구문은 오류가 발생하지 않음
addVars(fn: arrs.insert("KR", at: 1))
신기하게도, 이 구문은 오류가 발생하지 않는다. 이것이 바로 '지연된 실행'이다. 원래 구문은 작성하는 순간에 실행되는 것이 맞지만, 함수 내에 작성된 구문은 함수가 실행되기 전까지는 실행되지 않는다.
@autoclosure 속성이 부여된 인자값은 보기엔 일반 구문 형태이지만 컴파일러에 의해 클로저, 즉 함수로 감싸지기 때문에 위와 같이 작성해도 addVars(fn:) 함수 실행 전까지는 실행되지 않으며, 해당 구문이 실행될 때에는 이미 배열의 인덱스가 확장된 후이므로 오류도 발생하지 않는 것이다.
정리해보면 @autoclosure 속성이 인자값에 부여되면 해당 인자값은 컴파일러에 의해 클로저로 자동 래핑된다. 때문에 함수를 실행할 때에 '{ }'형식이 아니라 '( )' 형식의 일반값을 인자값으로 사용해야 한다. 또한 인자값은 코드에 작성된 시점이 아니라 해당 클로저가 실행되는 시점에 맞추어 실행된다. 이를 지연된 실행이라 부르며, @autoclosure 속성이 가지는 특징 중의 하나라고 할 수 있다.
출처 : 꼼꼼한 재은씨의 Swift 문법편
'Swift > 문법' 카테고리의 다른 글
스위프트(Swift) - 구조체와 클래스 Ⅱ. 프로퍼티 - 저장프로퍼티 (0) | 2022.06.01 |
---|---|
스위프트(Swift) - 구조체와 클래스 Ⅰ. 기본 개념 (0) | 2022.05.31 |
스위프트(Swift) - 함수(Function) Ⅸ. 트레일링 클로저(Trailing Closure) (0) | 2022.05.25 |
스위프트(Swift) - 함수(Function) Ⅷ. 클로저(Closure) (0) | 2022.05.24 |
스위프트(Swift) - 함수(Function) Ⅶ. 함수의 중첩 (0) | 2022.05.23 |