본 내용은 스위프트 프로그래밍 3판 (야곰 지음) 교재를 공부한 내용을 바탕으로 작성 하였습니다.
클로저는 클로저가 정의된 주변 문맥을 통해 상수 혹은 변수의 참조를 획득
할 수 있다. 다시 말해, 클로저는 값 획득
을 통해 획득한 상수나 변수가 더 이상 존재 하지 않더라도 내부에서 참조 및 수정이 가능하다는 것이다.
중첩 함수도 하나의 클로저로써 값 획득
에 대한 예시를 살펴 보자
func makeIncrementer(amount: Int) -> (() -> Int) {
var runningTotal = 1
func incrementer() -> Int {
//incrementer 함수는 주변 변수인
//amount와 runningTotal의 참조를 획득
runningTotal *= amount
return runningTotal
}
return incrementer
}
위의 makeIncrementer
함수의 반환 값이 () -> Int
인데, 이것은 함수 객체를 반환한다는 의미이다. 반환하는 함수는 반환 값이 Int 타입으로 호출 시 Int 타입의 값을 반환한다.
다시 값 획득 내용으로 돌아와 makeIncrementer
라는 함수는 incrementer
함수를 중첩 함수로 가진다.
이때 incrementer
함수는 makeIncrementer
함수의 지역 변수인 runningTotal
과 amount
변수의 참조를 획득한다.
이렇게 되면 makeIncrementer
함수가 종료 되더라도 내부의 incrementer
함수가 지역 변수의 참조를 획득하였으므로 메모리에서 사라지지 않고 계속해서 해당 변수를 참조 할 수 있다.
다음은 값 획득을 활용한 예시이다.
let incrementByTwo: (() -> Int) = makeIncrementer(amount: 2)
let first: Int = incrementByTwo() //incrementByTwo 상수에 저장된 클로저는 참조를 가지고
//있기때문에 호출시 runningTotal에 amount가 곱해진
//값이 반환됨
print(first)
let second: Int = incrementByTwo()
print(second)
let third: Int = incrementByTwo()
print(third)
위의 코드에서는 makeIncrementer
함수를 통해 incrementByTwo
상수에 incrementer
함수를 할당 해 주었다.
incrementer
함수에서 전달 인자를 runningTotal
변수에 곱해주도록 했기때문에 incrementByTwo
상수로 incrementer
함수를 호출 할때마다 runningTotal
의 값이 전달인자의 값 만큼 곱해지게 된다.
Swift는 함수형 프로그래밍 패러다임을 차용한 프로그래밍 언어이다.
Swift에서 함수(클로저)는 기본적으로 독립된 객체로 취급하고, 따라서 변수나 상수에 클로저를 대입한다는 것은 사실 클로저의 참조를 할당하는 것이다.
함수의 전달인자로서 클로저를 전달 하고 만약 함수가 끝난 후 클로저가 호출되어 사용될 필요가 있을때 클로저가 함수를 탈출(escape)
되었다고 한다. 이때 우리는 클로저가 탈출을 허용 한다는 의미로 매개변수 이름 뒤에 @escaping
키워드를 사용하여 탈출 가능한 클로저인 것을 알린다.
지금까지 함수의 전달인자로 클로저를 사용하면서 @escaping
키워드를 사용한적이 없는데 만약 @escaping
키워드를 명시 하지 않았다면 그 클로저는 기본적으로 비탈출 클로저
으로 컴파일러가 인식한다. 비탈출 클로저
는 함수 종료 후 더 이상 호출 될 일이 없는 클로저이다.
또한 비동기 작업
을 실행하는 함수들은 클로저를 컴플리션 핸들러(Complition handler)
전달인자로 받아온다. 이때 비동기 작업
완료 후 호출되는 클로저를 탈출 클로저
라고 한다.
위에서 비동기 작업이니 컴플리션 핸들러니 하는 어려운 용어들을 정리하고 넘어 가려고 한다.
비동기 작업:
데이터를 처리하는 방식에는 동기
방식과 비동기
방식이 있다. 동기
방식은 어떤 한 작업이 끝난 후에 다른 작업이 수행 되는 방식이고 (한번에 한가지의 작업이 수행되는 방식) 비동기
방식은 한 작업의 종료와 관계 없이 동시에 작업이 수행 되는 방식을 말한다.
컴프리션 핸들러:
어떠한 작업이 완료된 후 이후에 진행되는 작업을 담당하는 것
이제 탈출 클로저
를 구현한 코드를 살펴보자
typealias funcObject = () -> Void
let firstClosure: funcObject = {
print("You choice First Closure!")
}
let secondClosure: funcObject = {
print("You choice Second Closure!")
}
let noExistClosure: funcObject = {
print("error")
}
func chocieClosure(first: @escaping funcObject, second: @escaping funcObject, choice: String) -> funcObject {
if choice == "f" {
return first
}
else if choice == "s" {
return second
}
else {
return noExistClosure
}
}
//함수의 반환 값인 클로저를 외부 상수에 저장
let yourClosure: funcObject = chocieClosure(first: firstClosure, second: secondClosure, choice: "f")
yourClosure()
클로저가 탈출 클로저가 되기 위한 간단한 예시는 다음과 같다.
위의 코드에서는 chocieClosure
함수의 전달인자로 전달된 클로저가 반환되어 외부 상부에 저장 되어 탈출 클로저
가 사용되었다.
비탈출 클로저를 탈출 클로저 처럼 사용해야 되는 경우가 존재한다.
예를 들어 Array type에 존재하는 lazy
연산 프로퍼티는 LazySequence
타입을 반환하는 읽기 전용 연산 프로퍼티이다.
LazySequence
는 Array의 요소들을 베이스로 map
이나 filter
함수를 적용하여 지연 연산이 가능한 타입이다.
이처럼 지연 연산을 가능하게 하기 위해서는 map이나 filter에 탈출 클로저
로 클로저를 전달해야 하지만.. 아래와 같이 함수의 전달 인자로 비탈출 클로저를 전달 받아 map이나 filter에 탈출 클로저를 전달하지 못하는 경우 withoutActuallyEscaping
함수를 사용할 수 있다.
let numbers: [Int] = [1, 2, 3]
func addOne(in array: [Int], _ increment: (Int) -> Int) -> [Int] {
return withoutActuallyEscaping(increment, do: { escapablePredicate in
return array.lazy.map { escapablePredicate($0) }
//Array type의 lazy 프로퍼티는 지연연산
//하지만 전달인자로 들어오는 클로저는 비탈출 클로저
//비탈출 클로저를 탈출 클로저처럼 바꿔주는 withoutActuallyEscaping 함수
})
}
let addedNumbers: [Int] = addOne(in: numbers, {(number: Int) -> Int in
return number + 1
})
print(addedNumbers)
withoutActuallyEscaping
함수의 첫번째 매개변수는 탈출 클로저 역할을 해야하는 비탈출 클로저
가 전달되고, 두번째 do
매개변수에는 비탈출 클로저로 수행해야할 탈출 클로저
를 전달한다.
자동 클로저
는 함수의 매개변수로 전달되는 전달인자를 자동으로 클로저 형식으로 변환해주며, 매개변수 콜론 (:)
뒤에 @autoclosure
키워드를 사용하여 자동 클로저
임을 알린다.
자동 클로저
는 전달인자를 갖지 않으며 호출되었을때 자신이 수행할 수행문을 수행 후 결과 값을 반환한다.
또한 자동 클로저
는 호출되기 전까지는 내부 실행문이 실행되지 않는다.
다음은 자동 클로저를 사용한 예시이다.
var myDevice: [String] = ["iPhone", "iPad", "Macbook Pro"]
func introduceMyDevice(_ device: @autoclosure () -> String) {
print("This is my \(device())!")
}
introduceMyDevice(myDevice.removeFirst())
myDevice.removeFirst()
의 반환값은 String
타입이다. 또한 함수 introduceMyDevice
의 매개변수는 자동 클로저의 속성을 가진다.
따라서 String 타입의 값이 전달인자로 전달되면 자동 클로저의 속성을 가지는 매개변수는 String 타입의 전달인자를 전달인자는 없고 결과 값이 String 타입인 클로저
로 변환한다.