27일차 - 21.07.04

수킴·2021년 7월 4일
0

100DaysOfSwift

목록 보기
28/37
post-thumbnail

학습키워드

  • capture list

1. Capture lists in Swift: what’s the difference between weak, strong, and unowned references?

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!
  1. Strong capturing

    특별한 것을 요청하지 않는 한 스위프트는 Strong캡처를 사용합니다. (클로저가 클로저 내부에서 사용되는 모든 외부 값을 캡처하고 절대로 파괴되지 않도록 한다는 것)

    코드를 보면, taylor 상수는 sing() 함수 안에서 생성되므로 일반적으로 함수가 끝날 때 파괴됩니다. 그러나 클로저 안에서 사용되는 경우, 클로저가 존재한다면 활성상태(alive)로 유지합니다.

    만약 taylor 를 파괴하는 것을 허용한다면 클로저를 호출하는 것은 안전하지 않을 것입니다.

  2. 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 을 설정할 수 있습니다.

      출력결과는 아무것도 출력되지 않습니다. 왜냐하면 taylorsing() 안에서만 존재하기 때문입니다.

      클로저내부에 taylor 를 강제언래핑을 하면, taylornil 이 되고 앱은 충돌이 나게됩니다.

  3. 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을 사용할 때 문제점

  1. 클로저가 매개변수를 허락할때 캡처리스트를 어디에 사용할지 잘 모릅니다.

    Capture lists alongside parameters

    • 캡처리스트를 사용할 때 클로저매개변수와 함께 사용하는 경우 항상 캡처리스트가 먼저와야 합니다. 클로저매개변수 뒤에 삽입하면 컴파일이 중지됩니다.
    writeToLog { [weak self] user, message in 
        self?.addToLog("\(user) triggered event: \(message)")
    }
  2. 강한 참조사이클을 만들어서 메모리가 소모되게 합니다.

    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으로 변경해야합니다.

  3. 여러 캡처들을 사용할 때 강한 참조를 실수로 사용합니다.

    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 모두 강한캡처를 하지 않기를 원한다면 모든 값 앞에 캡처리스트를 명시해야합니다.
  4. 클로저의 복사본을 만들고 캡처된 데이터를 공유합니다.

    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를 사용해야 할까?

  1. 클로저가 호출될 가능성이 있는 동안 캡처된값이 사라지지 않는다는 것을 확실히 알고 있다면 unowned 를 사용할 수 있습니다.
  2. 강한 참조사이클 상황이 된다면 weak 를 사용할 수 있습니다. 여러곳에 사용해도 되고 한 곳에만 사용해도 됩니다.
  3. 강한 참조사이클이 없다면 strong 을 사용할 수 있습니다.

💡 잘 모르겠다면 weak 부터 사용해서 변경해 값니다.

2. Setting up

  • 개요 : 테이블뷰에 단어를 저장하는 프로젝트

  • Tableviews

  • Loading text from files

  • Asking for user input

3. Reading from disk: contentsOfFile

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")
	}
}

4. Pick a word, any word: UIAlertController

이 프로젝트는 사용자에게 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 (현재 뷰컨트롤러)

링크

100 Days of Swift - Day 27 - Hacking with Swift

profile
iOS 공부 중 🧑🏻‍💻

0개의 댓글