7-2. 캡쳐값, 탈출 클로저(escaping closer)

🌈 devleeky16498·2022년 4월 13일
0

캡쳐값

  1. 클로저는 정의된 둘러싸인 컨텍스트에서 상수와 변수를 캡쳐할 수 있다. 그러면 클로저는 상수와 변수를 정의한 원래 범위가 더 이상 존재하지 않더라도 바디 안에서 해당 상수와 변수값을 참조하고 수정 가능하다. 스위프트에서 값을 캡처할 수 있는 가장 간단한 클로저는 중첩함수이다. 이는 바깥 함수의 어떠한 인자도 캡처 가능하며 바깥 함수 내에 정의된 상수와 변수의 캡처도 가능하다.
func makeIncrementer(forIncrement amount : Int) -> () -> Int {
	var runningTotal = 0
    func incrementer() -> Int {
    	runningTotal += amount
        return runningTotal
    }
    return incrementer
}
//위의 반환 타입은 함수타입이다. 단독으로 본다면 incrementer() 함수는 비정상적으로 보인다. 
//파라미터가 없음에도 바디내에서 두 개의 runningTotal과 amount를 참조하고 있기 때문이다.
//둘러싸인 함수에 있는 runningTotal과 amount에 대한 참조를 캡쳐해서 사용하기 떄문에 문제가 생기지 않는다.
//참조를 캡쳐하게 되면 바깥 함수의 호출이 종료될 때  두개의 참조값이 사라지지 않고 다음에 
//incrementer함수가 호출 시 runningTotal을 정상적으로 사용할 수 있다.

let incrementByTen = makeIncrementer(forIncrement : 10)
//호출 할 떄마다 runningTotal에 10을 더하는 함수를 참조하도록 상수를 설정한다.
incrementByTen()
incrementByTen()
incrementByTen()
//밸류는 30을 가지게 된다.

let incrementBySeven = makeIncrementer(forIncrement : 7)
//다음과 같이 새로운 상수를 생성하면 새로운 분리된 runningTotal변수에 참조된다.
//위의 함수와 전혀 상관이 없는 새로운 값이다.
//서로 다른 값을 참조하게 되며, 다른 값으로 캡처된 변수들은 영향을 주지 않는다.
  1. 클래스 인스턴스의 프로퍼티에 클로저를 할당하고 클로저가 인스턴스 또는 멤버를 참조하여 해당 인스턴스를 캡쳐하면 클로저와 인스턴스 사이에 '강한 참조' 사이클이 생성된다. 스위프트는 캡처 목록을 통해 이런 강한 참조 사이클을 깨뜨린다.

클로저는 참조타입

  1. 위에서 보듯 incrementBySeven과 incrementByTen은 상수이지만 이러한 상수가 참조하는 클로저는 캡쳐한 runningTotal을 계속 증가시킬 수 있다. 이는 함수와 클로저가 참조타입이기 떄문이다. 함수 또는 클로저를 상수 및 변수에 할당할 떄마다 실제로 해당 상수 및 변수를 함수 또는 클로저에 대한 참조로 설정다면 서로 다른 2개의 상수라 할지라도 같은 클로저를 참조한다는 의미이다.
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
incrementByTen()
//둘다 모두 runningTotal을 10씩 증가시키게 된다. 같은 클로저를 참조하기 때문이다.

탈출 클로저(escaping closer)

  1. 클로저를 파라미터로 갖는 함수를 선언 시 이 클로저는 탈출을 허락한다는 의미로 파라미터 타입전 @escaping을 작성할 수 있다.
  2. 클로저가 탈출이 가능한 한가지 방법은 함수 바깥에 정의된 변수에 저장되는 것이다. 다음 예시는 비동기작업을 시작하는 함수인데 대부분 완료 핸들러로 클로저를 사용한다. 이 함수는 시작한 후 반환되지만, 작업이 완료될때까지 클로저가 호출되지 않는다. 클로저는 나중에 호출하려면 탈출해야 한다. 함수가 종료되면 클로저 호출이 안되기 떄문이다.
var completionHandler: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler : @escaping () -> Void) {
	competionHandler.append(completionHandler)
}
//다음 함수는 인자로 클로저를 가지고 있고, 함수 바깥에 선언된 배열에 추가한다. 
//이 때 파라미터에 @escaping을 명시하지 않으면 오류가 난다.
//self를 참조하는 탈출 클로저는 self가 클래스의 인스턴스를 참조하는 경우 특별히 고려해야 한다.
//self를 참조할 떄는 명시적으로 self를 작성하거나 클로저 캡쳐 목록에 self를 포함해야 한다.

func someFunctionWithNoneEscapingClosure(closure() -> Void) {
	closure()
}

class someClass {
	var a  =10
    	func doSomething() {
        	someFunctionWithEscapingClosure {
            	[self].x = 100
            }
            someFunctionWithNoneEscapingClosure {
            	x = 200
            }
        }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
//print "200"을 출력한다.

completionHandlers.first?()
print(instance.x)
//"100" 을 출력한다.

struct SomeStruct {
	var x = 10
    mutating func doSomething() {
    	someFunctionWithNoneescapingClosure {x = 200} // ok
        someFunctionWithEscapingClosure {x = 100} // error
//다음과 같이 구조체와 열거형은 값 타입이므로 공뷰 및 변경을 허락하지 않는다.
//위의 탈출 클로저는 구조체 바깥의 x에 대한 값을 캡쳐하므로 문제가 없다.
//하지만 아래 클로저는 구조체 자체의 값을 캡처하여 변경을 시도하므로 오류가 난다.

자동 클로저

  1. 자동 클로저는 함수에 인자로 전달되는 표현식을 래핑하기 위해 자동으로 생성되는 클로저이다.인자를 별도로 가지지 않고, 호출될 때 내부에 래핑된 표현식의 값을 반환한다. 자동클로저를 가지는 함수를 호출하는 건 일반적이지만 이러한 함수를 구현하는 건 일반적이지 않다.
var customerInLine = ["Chris", "Alex", "Ewa"]
print(customerInLine.count)
// 3을 출력한다.

let customerProvider = { customerInLine.remove(at: 0) }
print(customerInLine.count)
//여기까지도 3을 출력한다.

print("Now! provide \(customerProvider())!")
//"Now provide Chris!"

print(customerProvider.count)
// 위에서 자동 클로저를 수행하고 2를 출력한다.
//클로저 내부 코드에 의해서 배열의 첫 요소는 삭제된다.
//하지만 실제로 클로저가 호출되기 전까지 삭제되지는 않는다.
  1. 자동클로저는 @autoclosure 속성을 표기하여 자동 클로저를 가진다고 나타낼 수 있다. 그렇게 되면 위의 예시는 다음과 같이 다시 나타낼 수 있다.
//customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
	print("now! \(customerProvider())!")
}
//prints("now! Ewa")
//이 후에 0번 인덱스 값을 지우게 된다.
  1. 자동클로저가 이스케이프를 허용하길 원하면 @autoclosure와 @escaping 클로저 속성을 둘다 사용하면 된다.
var customersInLine = ["Barry", "Daniella"]
var customerProvider: [() -> String] = []
func collectCustomerProvider(_ customerProvider : @autoclosure @escaping () -> String) {
	customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at:0))
collectCustomerProviders(customersInLine.remove(at:0))

print("collected \(customerProviders.count) closures.")
//print" 2"

for customerProvider in customerProviders {
	print(Now serving \(customerProvider())!")
}
//"now serving Barry"
//"now serving Daniella"
profile
Welcome to Growing iOS developer's Blog! Enjoy!🔥

0개의 댓글

관련 채용 정보