안녕하세요, UX를 고려하는 개발자 유자🍋입니다.
오늘은 Swift 클로저 마지막 포스팅입니다!
오늘 포스팅은 클로저의 캡처와 밀접한 관련이 있기 때문에
반드시 이전 포스팅을 보고 오시는 걸 추천드립니다!
1. 클로저는 참조 타입
func outer(base: Int) -> () -> Int {
var value = 20 + base
func inner() -> Int {
value += 1
return value
}
return inner
}
let result = outer(base: 5)
let alsoResult = result
클로저를 변수에 할당하고
let first = result() //26
let second = alsoResult() //27
실행해 보면, 동일한 클로저가 동작하는 것을 확인할 수 있습니다.
이는 클로저가 참조 타입이기 때문입니다.
클로저가 참조 타입이기 때문에
클로저를 상수나 변수에 할당할 때마다
실제 클로저의 내용이 아닌
클로저가 저장된 메모리 주소를 참조하게됩니다.
따라서 새로운 클로저 참조를 상수에 할당해 준다면
let newResult = outer(base: 5)
let new = newResult()
print(first, second, new) //26 27 26
다른 참조를 가지기 때문에
출력되는 값이 다른 것을 확인할 수 있습니다.
2. 탈출 클로저 (Escaping Closures)
함수의 전달 인자로 전달된 클로저가
함수가 반환된 후에 호출되는 경우
함수를 ‘탈출’했다고 표현합니다.
우리가 지금까지 사용한 클로저는
탈출이 불가한 non-escaping의 성격을 가집니다.
클로저가 탈출 불가하다는 것은 함수가 반환된 후 호출되지 못하도록
함수 내부에서 직접 실행을 위해서만 사용해야 하는 것을 의미하며,
그렇기 때문에 다음과 같은 제약사항이 있습니다.
1.함수 내부라 할지라도 변수나 상수에 대입 불가
2. 중첩함수 내부에서 사용 불가
3. 함수가 종료되기 전에 실행
코드를 보며 하나씩 살펴보겠습니다.
2-1. 변수나 상수에 대입
가장 먼저 상수에 클로저를 대입하면 오류가 발생합니다.

2-2. 중첩함수 내부에서 매개 변수로 받은 클로저를 사용
또 중첩함수 내부에서 매개변수로 받은 클로저를 사용하는 경우 역시 오류가 발생합니다.

2-3. 함수가 종료되고 나서 클로저를 실행
함수가 종료된 뒤 클로저를 실행해 보겠습니다.
다음 코드는 2초 뒤 클로저를 실행하는 코드이며,

함수가 끝나고 클로저가 실행되기 때문에 오류가 발생합니다.
2-4. @escaping
하지만 코드를 작성하다 보면
탈출하는 클로저를 사용해야 할 경우도 있는데요.
이럴 경우, 다음과 같이 @escaping 을 타입 앞에 표기해 주면
func someFunction(c1: @escaping () -> Void) {
let f: () -> Void = c1
}
해당 클로저는 탈출을 허락한다는 의미가 됩니다.
3. 자동 클로저 (Autoclosures)
자동 클로저는 함수의 전달 인자로 전달되는 표현식을
래핑(Wrapping)하는 역할을 합니다.
쉽게 이해하기 위해 예시를 통해 알아보겠습니다.
func someFunction(closure: () -> Bool) {
}
위와 같은 함수를 실행하고자 하는 경우,
지금까지는 다음 두 가지 방법을 사용해 주었습니다.
someFunction { 4 > 2 }
//or
someFunction(closure: { 4 > 2 })
하지만 만약에 함수를 정의할 때 매개 변수 타입 앞에 @autoclosure를 표기해 주면
func someFunction(closure: @autoclosure () -> Bool) {
}
전달된 값은 컴파일러가 자동으로
클로저 형태로 감싸서 처리해 주기 때문에
someFunction(closure: (4 > 2))
매개 변수 주위의 중괄호를 생략할 수 있습니다.
@autoclosure와 관련해서 알아두어야 할 개념이 하나 있습니다.
바로 지연된 실행입니다.
아래 예시 코드는 names 변수를 정의하고,
두 차례에 걸쳐서 출력하는 코드입니다.
var names = ["홍길동", "철수", "영희"]
func enter(closure: () -> String) {
print("\(closure())님이 입장하셨습니다")
}
print(names) //["홍길동", "철수", "영희"]
enter { names.removeFirst() }
print(names) //["철수", "영희"]
enter 함수의 전달 인자로 클로저를 전달해줬기 때문에
enter 함수가 호출되기 전에는 names.reomveFirst 기능이
실행되지 않은 것을 확인할 수 있습니다.
그런데 만약 enter 함수를 호출하는 부분을
다음과 같이 변경하면 어떤 일이 일어날까요?
enter(closure: names.removeFirst())
위 코드를 실행해 보면 오류가 발생하는 것을 알 수 있습니다.

그 이유는 names.removeFirst() 코드가 즉시 실행되기 되어
enter 함수를 호출하기 전에 이미 names.removeFirst()가 실행되기 때문입니다.
즉, enter함수에
names.removeFirst() 코드 실행 결과인 String을 전달하려고 하니
타입이 맞지 않는다는 오류가 발생하는 것이죠.
이런 경우 활용이 가능한 것이 @autoclosure 속성입니다.
var names = ["홍길동", "철수", "영희"]
func enter(closure: @autoclosure () -> String) {
print("\(closure())님이 입장하셨습니다")
}
print(names) //"홍길동", "철수", "영희"
enter(closure: names.removeFirst())
print(names) //"철수", "영희"
@autoclosure 속성을 사용하면 일반 구문 형태 같아 보여도 클로저로 감싸기 때문에
enter함수 실행 전까지는 names.removeFirst()가 실행되지 않아
오류가 발생하지 않는 것을 확인할 수 있습니다.
자동클로저를 남용하면 코드를 이해하기 어렵게 만듭니다.
따라서 너무 남용하지 않도록 항상 주의해야 합니다!
이렇게 Swift 클로저 포스팅이 마무리되었습니다!
잘못된 내용이나 피드백이 있다면 언제든 환영합니다.
'iOS > Swift' 카테고리의 다른 글
Swift) 프로퍼티(2/5) - 연산 프로퍼티/get/set/Read-Only (0) | 2024.08.14 |
---|---|
Swift) 프로퍼티(1/5) - 저장 프로퍼티/lazy (0) | 2024.07.11 |
Swift 클로저(2/3) - 클로저의 캡처 (0) | 2024.03.28 |
Swift 클로저(1/3) - 클로저/클로저 표현식/클로저 표현식 실행/경량 문법/$0/짧은 인수 이름/후행 클로저(Trailing Closures) (0) | 2024.03.21 |
Swift 함수③ - 함수 표기 방법/일급 객체 함수/함수 타입/중첩 함수 (0) | 2024.03.12 |