Weak vs Strong vs Unowned - 클로저에서의 차이
캡처리스트는 코드에서 클로저 매개변수 목록전에 옵니다. 그리고 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()
// 출력결과
Shake it off!
Strong capturing
특별한 것을 요청하지 않는 한 스위프트는 Strong캡처를 사용합니다. (클로저가 클로저 내부에서 사용되는 모든 외부 값을 캡처하고 절대로 파괴되지 않도록 한다는 것)
코드를 보면, taylor
상수는 sing()
함수 안에서 생성되므로 일반적으로 함수가 끝날 때 파괴됩니다. 그러나 클로저 안에서 사용되는 경우, 클로저가 존재한다면 활성상태(alive)로 유지합니다.
만약 taylor
를 파괴하는 것을 허용한다면 클로저를 호출하는 것은 안전하지 않을 것입니다.
Weak capturing
스위프트에서 캡처리스트를 지정하면 클로저내에서 사용되는 값을 캡처하는 방법을 결정할 수 있습니다.
weak캡처된 값은 클로저에서 활성상태가(alive)가 유지되지 않으므로, 파괴되거나 nil값을 가질 수 있습니다.
1의 결과로서, weak캡처된값은 항상 스위프트에서 옵셔널입니다. 만약 값이 없다고 생각 될때 존재한다고 추측하는 것을 멈추게 할 수 있습니다.
Weak capturing으로 변경 코드
func sing() -> () -> Void {
let taylor = Singer()
let singing = { [weak taylor] in
taylor?.playSong()
return
}
return singing
}
// 출력결과
nil
[weak taylor]
부분이 캡처리스트 → 값을 캡처하는 방법에 대해 특정 지침을 제공하는 클로저의 특정 부분입니다. taylor
를 weak캡처하여 사용하라고 지정해주었습니다. 언제든지, nil
을 설정할 수 있습니다.
출력결과는 아무것도 출력되지 않습니다. 왜냐하면 taylor
는 sing()
안에서만 존재하기 때문입니다.
클로저내부에 taylor
를 강제언래핑을 하면, taylor
는 nil
이 되고 앱은 충돌이 나게됩니다.
Unowned capturing
weak
의 대안책으로 unowned
가 있습니다. 암묵적옵셔널언래핑과 동작방식이 유사합니다.
미래에 어느 시점에 값이 nil이 될수 있다는 것을 허용하지만, 옵셔널언래핑을 하지 않고 사용할 수 있습니다.
Unowned capturing으로 변경 코드
func sing() -> () -> Void {
let taylor = Singer()
let singing = { [unowned taylor] in
taylor.playSong()
return
}
return singing
}
closure capturing을 사용할 때 문제점
클로저가 매개변수를 허락할때 캡처리스트를 어디에 사용할지 잘 모릅니다.
Capture lists alongside parameters
writeToLog { [weak self] user, message in
self?.addToLog("\(user) triggered event: \(message)")
}
강한 참조사이클을 만들어서 메모리가 소모되게 합니다.
Strong reference cycles
A가 B를 소유하고, B가 A를 소유하는 경우 강한 참조사이클(strong reference cycle), (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()
house.ownerDetails = owner.printDetails
owner.houseDetails = house.printDetails
}
print("Done")
// 출력결과
Creating a house and an owner
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")
이 문제를 해결하려면 새로운 클로저를 만들거나 한 개 또는 여러개 값을 weak capturing으로 변경해야합니다.
여러 캡처들을 사용할 때 강한 참조를 실수로 사용합니다.
Accidental strong references
func sing() -> () -> Void {
let taylor = Singer()
let adele = Singer()
let singing = { [unowned taylor, unowned adele] in
taylor.playSong()
adele.playSong()
return
}
return singing
}
taylor
, adele
모두 강한캡처를 하지 않기를 원한다면 모든 값 앞에 캡처리스트를 명시해야합니다.클로저의 복사본을 만들고 캡처된 데이터를 공유합니다.
Copies of closures
var numberOfLinesLogged = 0
let logger1 = {
numberOfLinesLogged += 1
print("Lines logged: \(numberOfLinesLogged)")
}
logger1()
let logger2 = logger1
logger2()
logger1()
logger2()
// 출력 결과
Lines logged: 1
Lines logged: 2
Lines logged: 3
Lines logged: 4
언제 Strong, Weak, Unowned를 사용해야 할까?
unowned
를 사용할 수 있습니다.weak
를 사용할 수 있습니다. 여러곳에 사용해도 되고 한 곳에만 사용해도 됩니다.strong
을 사용할 수 있습니다.💡 잘 모르겠다면 weak
부터 사용해서 변경해 값니다.
개요 : 테이블뷰에 단어를 저장하는 프로젝트
Tableviews
Loading text from files
Asking for user input
txt파일로부터 String값 가져오기
일반적으로 줄 바꿈으로 할 경우 \n
을 사용합니다. 따라서 txt파일이 단어가 줄바꿈으로 구분되어 있다면 \n
으로 구분합니다.
파일명은 알고있어도 파일시스템에서 경로를 알 수 없는 경우
Bundle을 사용하여 path(forResource:)
를 사용하면 파일 이름과 확장자를 매개변수로 취하고 경로를 찾거나 nil
을 반환합니다.
String: components(separatedBy:)
: 구분 기호로 사용할 문자열을 지정하면 배열이 반환됩니다.
if let startWordsURL = Bundle.main.url(forResource: "start", withExtension: "txt") {
if let startWords = try? String(contentsOf: startWordsURL) {
allWords = startWords.components(separatedBy: "\n")
}
}
이 프로젝트는 사용자에게 8글자 프롬프트 단어로 만들 수 있는 단어를 입력하라는 메시지를 표시합니다.
만약 클로저를 사용하는 경우에 클로저가 뷰컨트롤러를 참조한다면 강한참조사이클이 발생할 수 있습니다.
@objc func promptForAnswer() {
let ac = UIAlertController(title: "Enter answer", message: nil, preferredStyle: .alert)
ac.addTextField()
let submitAction = UIAlertAction(title: "Submit", style: .default) { [weak self, weak ac] action in
guard let answer = ac?.textFields?[0].text else { return }
self?.submit(answer)
}
ac.addAction(submitAction)
present(ac, animated: true)
}
addTextField()
: UIAletController
에 편집 가능한 텍스트 입력필드를 추가합니다.self
(현재 뷰컨트롤러)