100 days of Swift - Day 27 (capture lists)

sun02·2021년 9월 17일
0

100 days of Swift 

목록 보기
23/40

캡쳐리스트는 클로져의 매개변수 목록 앞에 오고 값을 strong, weak, unowned 로 캡쳐한다.
강한 참조 주기를 피하기 위해 많이 사용한다.


class Singer {
	func playSong() {
    	print("Shake it off!")
    }
}

func sing() -> () -> Void {
	let taylor = Singer()
    
    let singing = {
    	taylor.playSong()
        return
    }
    
    return singing
}
  • 여기 간단한 클래스와 Singer 인스턴스를 만들고 singer의 playSong()메서드를 사용하는 클로져를 만들고 해당 클로져를 반환하는 함수가 있다.

let singFunction = sing()
singFunction()
  • 이것은 "Shake it off!"를 출력한다.

Strong capturing

특별한 것을 요구하지 않은 한 Swift는 강력한 캡쳐를 사용한다.

강력한 캡쳐란? 클로져가 클로져 내부에서 사용되는 모든 외부 값을 캡쳐하고 절대 소멸되지 않도록 하는 것.


func sing() -> () -> Void {
	let taylor = Singer()
    
    let singing = {
    	taylor.playSong()
        return
    }
    
    return singing
}
  • sing() 함수를 다시 보면, taylor 상수는 sing() 함수 안에서 생성되었기 때문에 일반적으로 함수가 끝날 때 소멸된다.
    • 그러나 클로져 내에서 사용되었기 때문에 클로져가 존재하는 한 함수가 return 되더라도 스위프트는 자동적으로 계속 존재하도록 보장한다.
  • 이것이 강한 캡쳐이다. 만약 스위프트가 taylor가 소멸되도록 허용했다면 클로져는 더 이상 호출하기에 안전하지 않다. taylor.playSong() 메서드는 더 이상 유효하지 않다.

Weak capturing

캡쳐 목록을 지정하여 클로저 내부에서 사용되는 값을 캡쳐하는 방법을 결정할 수 있다.
강력한 캡쳐에 대한 가장 일반적인 대안으로 약한 캡쳐가 있다.

  • 약하게 캡쳐된 값은 클로져에 의해 유지되지 않으므로 소멸되고 nil로 설정된다.
  • 위의 결과로 약하게 캡쳐된 값은 스위프트에서 항상 옵셔널이다.

func sing() -> () -> Void {
	let taylor = Singer()
    
    let singing = { [weak taylor] in
    	taylor?.playSong()
        return
    }
    
    return singing
}
  • [weak taylor] : 캡쳐 목록, 값을 캡쳐하는 방법에 대한 특정 지침을 제공한다.
    • 약하게 캡쳐되기 때문에 taylor는 옵셔널이다. 언제든 nil로 설정될 수 있기 때문이다.

let singFunction = sing()
singFunction()
  • 지금 실행한다면 singFunction()은 아무것도 출력하지 않는다.
    • taylor가 sing()안에서만 존재하기 때문이다.

func sing() -> () -> Void {
	let taylor = Singer()
    
    let singing = { [weak taylor] in
    	taylor!.playSong()
        return
    }
    
    return singing
}
  • sing()을 다음과 같이 바꿔서 실행해보자.
    • taylor가 nil이 되기 때문에 클로져 내의 강제 언래핑은 충돌을 발생시킨다.

Unowned capturing

weak에 대한 대안으로 암시적으로 옵셔널을 언래핑한 것처럼 작동한다.
약한 캡쳐와 같이 미래의 어느 시점에서 nil이 될 수 있지만 항상 존재하는 것처럼 작업할 수 있다. - 옵셔널을 언래핑 하지 않아도 됨.


func sing() -> () -> Void {
	let taylor = Singer()
    
    let singing = { [unowned taylor] in
    	taylor.playSong()
        return
    }
    
    return singing
}
  • 앞의 강제 언래핑된 예제와 유사한 방식으로 충돌한다: unowned taylor 는 taylor가 클로져의 수명동안 존재한다는 것을 알고있어서 메모리를 유지할 필요가 없다고 하지만 실제로 taylor는 거의 즉시 소멸되고 코드는 충돌한다.

Common problems

  1. 클로저가 매개변수를 수락할 때 캡쳐 목록을 어디에서 사용해야하는지 확신하지 못한다.
  2. 강한 참조 주기를 만들어 메모리를 소모한다.
  3. 다중 캡쳐를 사용할 때 실수로 강력한 참조를 사용한다.
  4. 클러져의 복사본을 만들고 캡쳐된 데이터를 공유한다.

1. Capture lists alongside parameters

capture list와 클로저 매개변수를 함께 사용할 때 캡쳐 리스트는 항상 먼저 와야하고 클로저의 본문의 시작을 나타내는 단어 in 이 와야한다.


writeToLog { [weak self] user, message in
	self?.addToLog("\(user) triggered event: \(message)")
}

2. Strong reference cycles

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!")
    }
}
  • 한 개의 프로퍼티, 한 개의 메서드 그리고 하나의 디이니셜라이져를 가지는 House 클래스이다.

class Owner {
	var houseDetails: (() -> Void)?
    
    func printDetails() {
    	print("I own a house.")
    }
    
    deinit {
    	print("I'm dying!")
    }
}
  • 클로져가 저장하는 세부 정보를 제외하고 동일한 Owner 클래스가 있다.

print("Creating a house and an owner")

do {
	let house = House()
    let owner = Owner()
}

print("Done")
  • do 블럭 안에서 위 클래스들의 인스턴스를 만든다.

    • 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")
  • 강한 참조 사이클
    • 이제 "Creaing a house and an owner", "Done"만 출력하고 deinitializer는 호출되지 않는다.
  • house의 프로퍼티는 owner의 메서드를 가르키고, owner의 프로퍼티는 house의 메서드를 가르키기 때문에 어느 것도 안전하게 소멸할 수 없다.
    • 이것은 메모리 누수로 알려진 해제될 수 없는 메모리를 유발하여 시스템 성능을 저하시키고 앱이 종료될 수 있다.

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")
  • 위와 같은 경우를 막기위해 약한 캡쳐를 사용한다.

  • 두 값 모두 약하게 캡쳐될 필요는 없다 - 최소 하나이상이어야 스위프트가 필요한 경우 두 값을 모두 파괴할 수 있다.

  • 실제 프로젝트 코드에서 명백학 강한 참조 주기는 자주 사용되지 않는다. 문제를 완전히 피하기 위해서는 약한 캡쳐를 사용하는 것이 더 중요하다.

3. Accidental string references

스위프트에서는 강한 캡쳐가 디폴트기 때문에 의도하지 않은 문제가 발생할 수 있다.


func sing() -> () -> Void {
	let taylor = Singer()
    let adele = Singer()
    
    let singing = { [unowned taylor, adele] in
    	taylor.playSong()
        adele.playSong()
        return
    }
    
    return singing
}
  • taylor, adele은 클로져 안에서 캡쳐되고 같은 방식으로 사용되었지만 taylor만 unowned로 캡쳐되고 adele은 강한 캡쳐가 된다.
  • unowned 키워드는 캡쳐 값 리스트에서 각각의 값 앞에 사용되어야하기 때문이다.

[unowned taylor, unowned adele]
  • 따라서 만약 둘 다 unowned로 캡쳐하고 싶다면 다음과 같이 작성해야한다.
  • self를 이용해서 의도하지 않은 캡쳐를 막을 수도 있다.
    • 클로져에서 self를 사용할 때, self.또는 self?.를 추가하여 의도를 명확히 할 수 있다.

4. Copies of closures

클로저가 복사되는 방식은 사람들을 혼란스럽게 한다. 캡쳐된 데이터가 복사본 간에 공유되기 때문이다.


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를 가르키기 떄문이다.

0개의 댓글