Swift에서 항상 [소유하지 않은 자체] 클로저를 사용해야합니까?


467

WWDC 2014 session 403 Intermediate Swift and transcript 에서 다음 슬라이드가있었습니다

여기에 이미지 설명을 입력하십시오

이 경우 스피커는 사용하지 않으면 [unowned self]메모리 누수가 발생 한다고 말했다 . 항상 [unowned self]내부 폐쇄를 사용해야한다는 의미 입니까?

스위프트 날씨 응용 프로그램의 ViewController.swift 라인 (64) , I는 사용하지 마십시오 [unowned self]. 하지만 일부 사용하여 UI를 업데이트 @IBOutlet처럼들 self.temperature하고 self.loadingIndicator. @IBOutlet내가 정의한 모든 것이이므로 괜찮을 수도 있습니다 weak. 그러나 안전을 위해 항상 사용해야 [unowned self]합니까?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}

이미지 링크가 끊어짐
Daniel Gomez Rico

@ DanielG.R. 고마워요 i.stack.imgur.com/Jd9Co.png
Jake Lin

2
내가 잘못 해요하지 않는 한, 슬라이드에 제시된 예는 incorrect-는 onChange해야 [weak self]다른 개체를 얻고 폐쇄를 저장할 수 있도록 주위 TempNotifier 개체를 유지, 재산 (아직 내부적으로 만) 공개 이후, 폐쇄 (무기한 경우 사용 목적은 가자하지 않았다 onChange가가보고 될 때까지 폐쇄 TempNotifier받는 자신의 약한 심판을 통해, 사라 TempNotifier) . 경우 var onChange …였다 private var onChange …다음 [unowned self]올바른 것입니다. 나는 이것에 대해 100 % 확신하지 못한다. 내가 틀렸다면 누군가 나를 고쳐주세요.
Slipp D. Thompson

@Jake Lin`var onChange : (Int)-> Void = {}`중괄호가 빈 닫힘을 나타 냅니까? []?를 사용 하여 빈 배열을 정의하는 것과 같습니다 . Apple 문서에서 설명을 찾을 수 없습니다.
bibscy

@bibscy yes, {}빈 클로저 (클로저 인스턴스)는 기본값 (아무것도하지 않음)이며 (Int) -> Void클로저 정의입니다.
Jake Lin

답변:


871

아니요, 사용하고 싶지 않은 시간이 있습니다 [unowned self]. 때로는 클로저가 호출 될 때까지 가까워 지도록 클로저가 자체 캡처를 원할 수도 있습니다.

예 : 비동기 네트워크 요청

비동기 네트워크 요청을 하는self 경우 요청이 완료 될 때 클로저를 유지하려고합니다 . 해당 객체가 다른 방식으로 할당 해제되었을 수 있지만 여전히 요청 마무리를 처리 할 수 ​​있기를 원합니다.

사용시기 unowned self또는weak self

당신이 정말로 사용하고 싶을 때 [unowned self]또는 [weak self]당신이 강한 참조주기를 만들 때 입니다. 강력한 참조주기는 객체가 서로를 소유하게되는 소유권 루프가있을 때 (타사를 통해 가능할 때) 서로를 고착시키기 때문에 할당 해제되지 않는 경우입니다.

클로저의 특정 경우, 그 안에 참조 된 모든 변수가 클로저에 의해 "소유"된다는 것을 인식하면됩니다. 폐쇄가 주위에있는 한, 그 물체는 주위에 있어야합니다. 해당 소유권을 중지하는 유일한 방법은 [unowned self]또는 을 수행하는 것 [weak self]입니다. 따라서 클래스가 클로저를 소유하고 해당 클로저가 해당 클래스에 대한 강력한 참조를 캡처하면 클로저와 클래스 사이에 강력한 참조주기가 있습니다. 클래스가 클로저를 소유 한 것을 소유 한 경우도 여기에 포함됩니다.

비디오의 예에서 구체적으로

슬라이드의 예에서 멤버 변수를 TempNotifier통해 클로저를 소유합니다 onChange. 그들이 선언하지 않은 경우 selfunowned, 폐쇄는 자신의 것이 self강한 참조주기를 생성.

차이 사이 unownedweak

의 차이 unownedweak그가있다 weak(선택 사양) 동안과 같이 선언 unowned하지 않습니다. 그것을 선언함으로써 weak어떤 시점에서 클로저 내부가 없을 수있는 경우를 처리하게됩니다. unownednil 인 변수 에 액세스하려고 하면 전체 프로그램이 중단됩니다. 따라서 unowned클로저가 주변에있는 동안 변수가 항상 주변에 있다는 긍정적 인 경우 에만 사용 하십시오.


1
안녕. 좋은 대답입니다. 나는 소유하지 않은 자아를 이해하려고 애 쓰고 있습니다. weakSelf를 사용하는 이유는 단순히 '자체가 선택이된다'는 것만으로는 충분하지 않습니다. 내가 왜 '소유하지 않은 자기'를 사용하고 싶습니까? stackoverflow.com/questions/32936264/…

19
@robdashnash, 소유하지 않은 자체 사용의 장점은 설계 상 확실하게 알 수 있다면 불필요한 코드가 될 수있는 옵션을 풀지 않아도된다는 것입니다. 궁극적으로, 소유되지 않은 자아는 간결함을 위해 그리고 아마도 미래 가치를 기대하지 않는 미래 개발자에게 힌트로 사용됩니다.
drewag

77
사용하는 경우 [weak self]비동기 네트워크 요청은 인 뷰 컨트롤러 그 요청을보기 위해서 사용된다. 사용자가 철회하면 더 이상 뷰를 채울 필요가 없으며 뷰 컨트롤러에 대한 참조가 필요하지 않습니다.
David James

1
weaknil객체의 할당이 해제 될 때 참조도 설정됩니다 . unowned참조가 아닙니다.
BergQuester

1
약간 혼란 스러워요. unowned사용되는 non-Optional동안 weak사용되는 Optional우리가 그래서 self이다 Optionalnon-optional?
Muhammad Nayab

193

2016 년 11 월 업데이트

이 답변을 확장 (ARC 가하는 일을 이해하기 위해 SIL을 보며)에 대한 기사를 썼습니다 . 여기 에서 확인 하십시오 .

원래 답변

위의 답변은 실제로 다른 것을 사용하는시기와 이유에 대한 간단한 규칙을 제공하지는 않으므로 몇 가지를 추가하겠습니다.

소유되지 않거나 약한 토론 은 변수 의 수명 과 변수를 참조하는 클로저의 문제로 귀결됩니다 .

빠른 약한 대 소유

시나리오

가능한 두 가지 시나리오가 있습니다.

  1. 클로저는 변수의 수명이 동일하므로 변수에 도달 할 때까지 클로저 에 도달 할 수 있습니다. 변수와 클로저의 수명은 동일합니다. 이 경우 참조를 소유하지 않은 것으로 선언해야합니다 . 일반적인 예는 [unowned self]부모의 맥락에서 무언가를하고 다른 곳에서 참조하지 않는 부모의 수명이 다하지 않는 작은 폐쇄의 많은 예 에서 사용됩니다.

  2. 클로저 수명은 변수 중 하나와 독립적이며 변수에 더 이상 도달 할 수없는 경우 클로저를 계속 참조 할 수 있습니다. 이 경우 참조를 약한 것으로 선언하고 사용하기 전에 참조 가 아닌지 확인해야합니다 (포장 풀기 금지). 이것의 일반적인 예는 [weak delegate]완전히 관련되지 않은 (평생 현명한) 델리게이트 객체를 참조하는 클로저의 예에서 볼 수 있는 것입니다.

실제 사용법

그렇다면 실제로 어떤 시간을 사용해야합니까?

트위터에서 Joe Groff 인용 :

비소 유가 더 빠르며 불변성과 비 선택성을 허용합니다.

약한 것이 필요하지 않으면 사용하지 마십시오.

소유하지 않은 *내부 작업 에 대한 자세한 내용은 여기를 참조하십시오 .

* 일반적으로 소유되지 않은 참조에 액세스하기 전에 런타임 검사 (유효하지 않은 참조로 인해 충돌이 발생 함)가 수행됨을 나타 내기 위해 unowned (safe)라고도합니다.


26
나는 앵무새 설명에 "피곤하다. 자아가 없을 수 있다면 일주일이 걸리고, 절대 못될 수 없을 때는 소유하지 말아라"라는 말에 귀를 기울인다. 알았어 우리는 그것을 백만 번 들었다! 이 답변은 실제로 영어 자체가 평범한 영어로 쓸 수없는 경우에 대해 더 깊이 파고있어 OP의 질문에 직접 대답합니다. 이 위대한 설명에 감사드립니다 !!
TruMan1

감사합니다 @ TruMan1, 나는 실제로 내 블로그에 곧 게시 될 링크에 대한 답변을 업데이트 할 게시물을 작성하고 있습니다.
움베르토 라이 몬디

1
좋은 답변, 매우 실용적입니다. 나는 성능에 민감한 약한 변수 중 일부를 지금 소유하지 않은 것으로 전환하도록 영감을 받았습니다.
original_username

"폐쇄 수명은 변수 중 하나와 독립적입니다."오타가 있습니까?
Honey

1
클로저가 항상 부모 개체와 수명이 동일하다면 개체가 파괴 될 때 참조 카운트가 처리되지 않습니까? 왜이 상황에서 '자기'를 소유하지 않거나 약한 사람을 귀찮게하는 대신 사용할 수 없는가?
LegendLength

105

뷰 컨트롤러에 대한 구체적인 예제를 추가 할 것이라고 생각했습니다. 여기에 스택 오버플로에 대한 설명뿐만 아니라 많은 설명이 실제로 훌륭하지만 실제 예제를 사용하여 더 잘 작동합니다 (@drewag는 이것에 대해 잘 시작했습니다).

  • 네트워크 요청에 대한 응답을 처리 할 수있는 클로저가있는 경우에는 weak수명이 길기 때문에 use를 사용 합니다. 요청이 완료되기 전에 뷰 컨트롤러가 닫힐 수 있으므로 self클로저가 호출 될 때 더 이상 유효한 객체를 가리 키지 않습니다.
  • 단추의 이벤트를 처리하는 클로저가있는 경우 이는 unowned뷰 컨트롤러가 사라지 자마자 버튼 및 참조하는 다른 항목 self이 동시에 사라 지기 때문일 수 있습니다 . 폐쇄 블록도 동시에 사라집니다.

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // closure must be held in this class. 
    
          override func viewDidLoad() {
              // use unowned here
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // won't happen after vc closes. 
              }
              // use weak here
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // could be called any time after vc closes
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // rest of class below.
     }

17
이것은 더 많은 투표를 필요로합니다. 버튼 누름 클로저가 뷰 컨트롤러의 수명 밖에 존재하지 않는 방법을 보여주는 두 가지 확실한 예는 소유하지 않지만 UI를 업데이트하는 대부분의 네트워크 호출은 약해야합니다.
Tim Fuqua

2
명확히하기 위해 클로저 블록에서 자신을 호출 할 때 항상 소유하지 않거나 약한 것을 사용합니까? 아니면 우리가 약하거나 소유하지 않은 전화를하지 않는 시간이 있습니까? 그렇다면 그에 대한 예도 제시해 주시겠습니까?
luke

정말 고맙습니다.
Shawn Baek

1
이로 인해 [약한 자아]와 [소유하지 않은 자아]에 대해 더 깊이 이해하게되었습니다. 감사합니다. @possen!
Tommy

대단하다. 사용자 상호 작용을 기반으로 하는 애니메이션 이 있지만 완료하는 데 시간이 걸리는 경우 그런 다음 사용자는 다른 viewController로 이동합니다. 이 경우에 나는 여전히 옳은 weak것이 아니라 사용해야한다고 생각 unowned합니까?
Honey


50

다음은 맛있는 세부 사항을 설명한 Apple 개발자 포럼의 훌륭한 인용문입니다 .

unownedvs unowned(safe)vsunowned(unsafe)

unowned(safe)객체가 여전히 살아 있다는 액세스를 주장하는 비 소유 참조입니다. x!액세스 할 때마다 암시 적으로 래핑되지 않는 약한 선택적 참조와 같습니다 . ARC unowned(unsafe)와 비슷 __unsafe_unretained하지만 소유하지 않은 참조이지만 객체가 여전히 액세스 상태에 있는지 런타임 검사가 없으므로 매달려있는 참조는 가비지 메모리에 도달합니다. unowned는 항상 unowned(safe)현재와 동의어 이지만 런타임 검사가 비활성화되면 빌드 unowned(unsafe)에서 최적화됩니다 -Ofast.

unowned vs weak

unowned실제로보다 간단한 구현을 사용합니다 weak. Native Swift 객체는 두 개의 참조 카운트를 가지며 unowned 참조 는 강한 참조 카운트 대신 소유되지 않은 참조 카운트 를 충돌시킵니다 . 강한 참조 카운트 가 0에 도달하면 객체가 초기화 되지 않지만, 소유되지 않은 참조 카운트 도 0에 도달 할 때까지 실제로 할당이 해제되지 않습니다 . 이로 인해 소유되지 않은 참조가있을 때 메모리가 약간 더 길게 유지되지만 일반적으로 문제가되지 않습니다.unowned 관련 개체의 수명이 거의 같아야하므로 약한 참조를 0으로 만드는 데 사용되는 보조 테이블 기반 구현보다 훨씬 간단하고 오버 헤드가 높습니다.

업데이트 : 현대 스위프트에서는 weak내부적으로 동일한 메커니즘 사용 unowned하지를 . 따라서이 비교는 Objective-C weak와 Swift를 비교하기 때문에 올바르지 않습니다 unonwed.

원인

참조를 소유 한 후 메모리를 활성 상태로 유지하는 목적은 무엇입니까? 코드가 초기화되지 않은 후 소유되지 않은 참조를 사용하여 객체로 무언가를 시도하면 어떻게됩니까?

보유 횟수를 계속 사용할 수 있도록 메모리가 활성 상태로 유지됩니다. 이렇게하면 누군가 소유하지 않은 객체에 대한 강력한 참조를 유지하려고 할 때 런타임에서 객체를 안전하게 유지하기 위해 강한 참조 횟수가 0보다 큰지 확인할 수 있습니다.

개체가 소유하거나 소유하지 않은 참조는 어떻게됩니까? 초기화되지 않은 객체의 수명이 객체에서 분리 되었습니까? 아니면 마지막 소유하지 않은 참조가 해제 된 후 객체가 할당 해제 될 때까지 메모리도 유지됩니까?

객체의 마지막 강력한 참조가 해제되고 객체의 초기화가 해제 되 자마자 객체가 소유 한 모든 리소스가 해제됩니다. 소유하지 않은 참조는 메모리를 유지합니다. 참조 횟수가있는 헤더 외에는 내용이 잘못되었습니다.

흥분 되나요?


38

여기에 좋은 답변이 있습니다. 그러나 Swift가 약한 참조를 구현하는 방법에 대한 최근의 변화는 모든 사람의 약한 자체 대 소유되지 않은 자체 사용 결정을 바꿔야합니다. 이전에는 소유하지 않은 자아에 액세스하는 것이 약한 자아에 액세스하는 것보다 훨씬 빠르기 때문에 소유하지 않은 자아를 사용하여 최상의 성능이 필요한 경우, 자아가 절대 없을 수 있다고 확신 할 수있는 한, 자존심이 약한 자보다 우수했습니다.

그러나 Mike Ash는 Swift가 사이드 테이블을 사용하기 위해 약한 변수의 구현을 업데이트 한 방법과 약한 자체 성능을 향상시키는 방법을 문서화했습니다.

https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html

약한 자아에 대한 상당한 성능 저하가 없으므로 앞으로는 기본적으로 사용하는 것이 좋습니다. 약한 자아의 장점은 선택 사항이므로 더 정확한 코드를 작성하는 것이 훨씬 쉬워지며 기본적으로 Swift가 훌륭한 언어입니다. 소유하지 않은 자아를 사용하는 것이 안전한 상황을 알고 있다고 생각할 수도 있지만 다른 개발자 코드를 많이 검토 한 경험은 대부분 그렇지 않습니다. 나는 일반적으로 컨트롤러가 할당 해제 된 후 백그라운드 스레드가 완료되는 상황에서 소유되지 않은 자체 할당이 취소되는 많은 충돌을 수정했습니다.

버그와 충돌은 프로그래밍에서 가장 시간이 많이 걸리고 고통스럽고 비싼 부분입니다. 올바른 코드를 작성하고 피하기 위해 최선을 다하십시오. 랩핑 해제 옵션을 강요하지 않고 약한 자아 대신 소유하지 않은 자아를 사용하지 않는 것이 좋습니다. 랩핑을 풀고 소유하지 않은 자아는 실제로 안전합니다. 그러나 충돌 및 버그를 찾아서 디버그하기 어려운 것을 제거하면 많은 이점을 얻을 수 있습니다.


마지막 단락의 업데이트와 아멘에 감사드립니다.
모토

1
따라서 새로운 변경 후 ? weak대신에 사용할 수없는 시간 이 unowned있습니까?
Honey

4

Apple-doc 에 따르면

  • 약한 참조는 항상 선택적인 유형이며 참조하는 인스턴스가 할당 해제되면 자동으로 nil이됩니다.

  • 캡처 된 참조가 절대로 0이되지 않으면 약한 참조가 아닌 항상 소유되지 않은 참조로 캡처해야합니다.

예 -

    // if my response can nil use  [weak self]
      resource.request().onComplete { [weak self] response in
      guard let strongSelf = self else {
        return
      }
      let model = strongSelf.updateModel(response)
      strongSelf.updateUI(model)
     }

    // Only use [unowned self] unowned if guarantees that response never nil  
      resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
     }

0

위의 어느 것도 의미가 없다면 :

tl; dr

마찬가지로 , 사용 시점에서 참조가 무효가되지 않도록 보장implicitly unwrapped optional 할 수 있는 경우, 소유하지 않은 것을 사용하십시오. 그렇지 않은 경우 약한 것을 사용해야합니다.

설명:

아래에서 다음을 검색했습니다 : weak unowned link . 내가 수집 한 것에서, 소유하지 않은 자아 무질서 할 수 없지만 약한 자아는 될 수 없으며, 소유하지 않은 자아는 매달려있는 포인터로 이어질 수 있습니다 ... Objective-C에서 악명 높은 것. 그것이 도움이되기를 바랍니다.

"사용하지 않는 약한 참조 및 소유되지 않은 참조는 비슷하게 동작하지만 동일하지는 않습니다."

약한 참조 와 같이 소유되지 않은 참조는 참조 되는 개체의 유지 횟수를 늘리지 않습니다 . 그러나 Swift에서 소유하지 않은 참조는 Optional이 아닌 추가 이점이 있습니다. 따라서 선택적 바인딩을 사용하지 않고 관리 하기가 더 쉽습니다 . 이것은 Implicitly Unwrapped Optionals와 다르지 않습니다. 또한 소유되지 않은 참조는 0아닙니다 . 이것은 객체가 할당 해제 될 때 포인터를 제로화하지 않음을 의미합니다. 즉, 소유하지 않은 참조를 사용하면 포인터가 매달려 있을 수 있습니다.. 저처럼 Objective-C 시절을 기억하는 괴상한 사람들에게는 소유되지 않은 참조가 unsafe_unretained 참조에 매핑됩니다.

여기가 약간 혼란스러워집니다.

약하고 소유되지 않은 참조는 모두 보유 횟수를 증가시키지 않습니다.

둘 다 유지주기를 중단하는 데 사용할 수 있습니다. 그래서 언제 사용합니까?!

애플의 문서 에 따르면 :

그 때마다 "약한 참조를 사용하여 유효한 해당 참조의 수명 동안 어떤 시점에서 nil이 될 수 있도록. 반대로 초기화 중에 참조가 설정되면 참조가 절대로 0이 아님을 알 때 소유하지 않은 참조를 사용하십시오.”


0
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        self.navigationController?.pushViewController(controller, animated: true)

    }

}



import UIKit
class AnotherViewController: UIViewController {

    var name : String!

    deinit {
        print("Deint AnotherViewController")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print(CFGetRetainCount(self))

        /*
            When you test please comment out or vice versa

         */

//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//


//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//


//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }


        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)

        clouser(string: "") {  (boolValue)  in
            print("some")
            print(CFGetRetainCount(self))

        }

    }


    func clouser(string: String, completion: @escaping (Bool) -> ()) {
        // some heavy task
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            completion(true)
        }

    }

}

확실하지 않으면 [unowned self] 다음을 사용하십시오. [weak self]

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.