캡쳐리스트는 클로져의 매개변수 목록 앞에 오고 값을 strong, weak, unowned 로 캡쳐한다.
강한 참조 주기를 피하기 위해 많이 사용한다.
class Singer {
func playSong() {
print("Shake it off!")
}
}
func sing() -> () -> Void {
let taylor = Singer()
let singing = {
taylor.playSong()
return
}
return singing
}
let singFunction = sing()
singFunction()
특별한 것을 요구하지 않은 한 Swift는 강력한 캡쳐를 사용한다.
강력한 캡쳐란? 클로져가 클로져 내부에서 사용되는 모든 외부 값을 캡쳐하고 절대 소멸되지 않도록 하는 것.
func sing() -> () -> Void {
let taylor = Singer()
let singing = {
taylor.playSong()
return
}
return singing
}
캡쳐 목록을 지정하여 클로저 내부에서 사용되는 값을 캡쳐하는 방법을 결정할 수 있다.
강력한 캡쳐에 대한 가장 일반적인 대안으로 약한 캡쳐가 있다.
func sing() -> () -> Void {
let taylor = Singer()
let singing = { [weak taylor] in
taylor?.playSong()
return
}
return singing
}
let singFunction = sing()
singFunction()
func sing() -> () -> Void {
let taylor = Singer()
let singing = { [weak taylor] in
taylor!.playSong()
return
}
return singing
}
weak에 대한 대안으로 암시적으로 옵셔널을 언래핑한 것처럼 작동한다.
약한 캡쳐와 같이 미래의 어느 시점에서 nil이 될 수 있지만 항상 존재하는 것처럼 작업할 수 있다. - 옵셔널을 언래핑 하지 않아도 됨.
func sing() -> () -> Void {
let taylor = Singer()
let singing = { [unowned taylor] in
taylor.playSong()
return
}
return singing
}
capture list와 클로저 매개변수를 함께 사용할 때 캡쳐 리스트는 항상 먼저 와야하고 클로저의 본문의 시작을 나타내는 단어 in 이 와야한다.
writeToLog { [weak self] user, message in
self?.addToLog("\(user) triggered event: \(message)")
}
A가 B를 가지고 B가 A를 가질 대, 강한 참조 주기를 가진다고 한다. 또는 그냥 retain cycle이라고도 한다.
class House {
var ownerDetails: (() -> Void)?
func printDetails() {
print("This is a great house.")
}
deinit {
print("I'm being demolished!")
}
}
class Owner {
var houseDetails: (() -> Void)?
func printDetails() {
print("I own a house.")
}
deinit {
print("I'm dying!")
}
}
print("Creating a house and an owner")
do {
let house = House()
let owner = Owner()
}
print("Done")
do 블럭 안에서 위 클래스들의 인스턴스를 만든다.
"Creating a house and and owener", "I'm dying!", "I'm being demoolished", "Done"을 출력할 것이다.
print("Creating a house and an owner")
do {
let house = House()
let owner = Owner()
house.ownerDetails = owner.printDetails
owner.houseDetails = house.printDetails
}
print("Done")
print("Creating a house and an owner")
do {
let house = House()
let owner = Owner()
house.ownerDetails = { [weak owner] in owner?.printDetails() }
owner.houseDetails = { [weak house] in house?.printDetails() }
}
print("Done")
위와 같은 경우를 막기위해 약한 캡쳐를 사용한다.
두 값 모두 약하게 캡쳐될 필요는 없다 - 최소 하나이상이어야 스위프트가 필요한 경우 두 값을 모두 파괴할 수 있다.
실제 프로젝트 코드에서 명백학 강한 참조 주기는 자주 사용되지 않는다. 문제를 완전히 피하기 위해서는 약한 캡쳐를 사용하는 것이 더 중요하다.
스위프트에서는 강한 캡쳐가 디폴트기 때문에 의도하지 않은 문제가 발생할 수 있다.
func sing() -> () -> Void {
let taylor = Singer()
let adele = Singer()
let singing = { [unowned taylor, adele] in
taylor.playSong()
adele.playSong()
return
}
return singing
}
[unowned taylor, unowned adele]
클로저가 복사되는 방식은 사람들을 혼란스럽게 한다. 캡쳐된 데이터가 복사본 간에 공유되기 때문이다.
var numberOfLinesLogged = 0
let logger1 = {
numberOfLinesLogged += 1
print("Lines logged: \(numberOfLinesLogged)")
}
logger1()
"Lines logged:1" 이 출력될 것이다.
let logger2 = logger1
logger2()
logger1()
logger2()
"Lines logged:2"
"Lines logged:3"
"Lines logged:4" 가 출력된다.
logger1과 logger2가 똑같이 캡쳐된 numberOfLinesLogged를 가르키기 떄문이다.