[Swift 프로그래밍] 클로저 심화

이정훈·2022년 6월 1일
0

Swift 기본

목록 보기
12/22
post-thumbnail

본 내용은 스위프트 프로그래밍 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 함수의 지역 변수인 runningTotalamount 변수의 참조를 획득한다.

이렇게 되면 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 함수의 전달인자로 전달된 클로저가 반환되어 외부 상부에 저장 되어 탈출 클로저가 사용되었다.

withoutActuallyEscaping


비탈출 클로저를 탈출 클로저 처럼 사용해야 되는 경우가 존재한다.

예를 들어 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 타입인 클로저로 변환한다.

profile
새롭게 알게된 것을 기록하는 공간

0개의 댓글