วิธีการจัดการ Weak Self ที่ถูกต้องในบล็อก Swift ด้วยอาร์กิวเมนต์อย่างถูกต้อง


151

ในตัวฉันTextViewTableViewCellฉันมีตัวแปรที่จะติดตามบล็อกและวิธีกำหนดค่าที่บล็อกถูกส่งผ่านและกำหนด
นี่คือTextViewTableViewCellชั้นเรียนของฉัน:

//
//  TextViewTableViewCell.swift
//

import UIKit

class TextViewTableViewCell: UITableViewCell, UITextViewDelegate {

    @IBOutlet var textView : UITextView

    var onTextViewEditClosure : ((text : String) -> Void)?

    func configure(#text: String?, onTextEdit : ((text : String) -> Void)) {
        onTextViewEditClosure = onTextEdit
        textView.delegate = self
        textView.text = text
    }

    // #pragma mark - Text View Delegate

    func textViewDidEndEditing(textView: UITextView!) {
        if onTextViewEditClosure {
            onTextViewEditClosure!(text: textView.text)
        }
    }
}

เมื่อฉันใช้วิธีกำหนดค่าในวิธีการของฉันฉันcellForRowAtIndexPathจะใช้ตัวเองที่อ่อนแอในบล็อกที่ฉันผ่านได้อย่างไร
นี่คือสิ่งที่ฉันมีโดยไม่มีตัวตนที่อ่อนแอ:

let myCell = tableView.dequeueReusableCellWithIdentifier(textViewCellIdenfitier) as TextViewTableViewCell
myCell.configure(text: body, onTextEdit: {(text: String) in
   // THIS SELF NEEDS TO BE WEAK  
   self.body = text
})
cell = bodyCell

อัปเดต : ฉันได้รับการทำงานดังต่อไปนี้โดยใช้[weak self]:

let myCell = tableView.dequeueReusableCellWithIdentifier(textViewCellIdenfitier) as TextViewTableViewCell
myCell.configure(text: body, onTextEdit: {[weak self] (text: String) in
        if let strongSelf = self {
             strongSelf.body = text
        }
})
cell = myCell

เมื่อฉันทำ[unowned self]แทน[weak self]และนำifคำสั่งออกมาแอปขัดข้อง ความคิดใด ๆ เกี่ยวกับวิธีการนี้ควรจะทำงานกับ[unowned self]?


คุณสามารถเลือกคำตอบด้านล่างเป็นคำตอบที่ถูกต้องได้หรือไม่? นอกจากนี้โปรดทราบว่าด้วยการไม่เป็นเจ้าของคุณไม่จำเป็นต้องสร้างความแข็งแกร่งให้ตนเองภายในการปิด ไม่มีใครเป็นเจ้าของดีกว่าอ่อนแอเนื่องจากวงจรชีวิตของเซลล์และตัวควบคุมมุมมองของคุณเชื่อมโยงกัน
ikuramedia

1
ฉันรู้ว่า [ตัวเองเป็นเจ้าของ] ถ้าเป็นตัวเลือกที่ดีกว่า แต่แอปของฉันขัดข้องเมื่อฉันใช้ ชอบที่จะเห็นตัวอย่างรหัสโดยใช้มันเพื่อปิดคำตอบ
NatashaTheRobot

1
จากเอกสาร: "เช่นเดียวกับการอ้างอิงที่อ่อนแอการอ้างอิงที่ไม่ได้อ้างอิงจะไม่ถูกเก็บไว้อย่างแน่นหนาในอินสแตนซ์ที่อ้างถึงอย่างไรก็ตามการอ้างอิงที่ไม่ได้อ้างอิงนั้นจะถือว่ามีค่าอยู่เสมอ" หากแอปของคุณขัดข้อง อาจเป็นเพราะมีการใช้งานที่ไม่เป็นเจ้าของกับค่าที่เป็นศูนย์ ณ รันไทม์
Bill Patterson

น่าจะดีกว่าที่จะโฆษณาคำสั่งป้องกันที่นี่กว่าถ้าให้ผูกพันกับ strongSelf เพียงแค่พูดว่านี่เป็นเหมือนผู้สมัครที่สมบูรณ์แบบ :-D
Daniel Galasko

@NatashaTheRobot, syntax คืออะไร [ตัวเองอ่อนแอ]? ดูเหมือนว่าข้อความจะผ่านไปในวัตถุประสงค์ C. คุณช่วยเพิ่มเล็กน้อยเกี่ยวกับไวยากรณ์ในคำถามได้ไหม
Vignesh

คำตอบ:


178

ถ้าตัวเองอาจจะเป็นศูนย์ในการใช้งานปิด[อ่อนแอตนเอง]

ถ้า ตัวเองไม่เคยจะเป็นศูนย์ในการใช้งานปิด[ไม่มีเจ้าของตัวเอง]

ถ้ามันพังเมื่อคุณใช้ [ตัวเองไม่มีกรรมสิทธิ์]ฉันจะเดาว่าตัวเองไม่มีอะไรเลยในช่วงเวลานั้นซึ่งเป็นเหตุผลว่าทำไมคุณต้องไปกับ[ตัวอ่อนแอ]แทน

ผมชอบส่วนทั้งจากคู่มือเกี่ยวกับการใช้ที่แข็งแกร่ง , อ่อนแอและไม่มีเจ้าของในการปิด:

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

หมายเหตุ: ฉันใช้คำว่าปิดแทนบล็อกซึ่งเป็นเทอมใหม่ของ Swift:

ความแตกต่างระหว่างบล็อก (Objective C) และการปิด (Swift) ใน ios


7
แอปเปิ้ลเรียกว่าบล็อก "ปิด" ในเอกสารแรกของพวกเขาสำหรับการขยายภาษา C (บล็อกหรือการปิดเป็นส่วนขยายของ C ในตอนแรกเพียง MM เท่านั้นที่เกี่ยวข้องกับ Objective-C) แม้ว่าฉันจะชอบคำว่า "การปิด" เช่นกันเพราะ "บล็อก" ใน C เกี่ยวข้องกับคำสั่งผสมบ่อยครั้งมาก เป็นสิ่งที่ผิดทั้งสองภาษาเพราะมันถูกเรียกว่าการปิดแม้ว่ามันจะไม่ได้ปิดวัตถุ (ตัวแปรหรือค่าคงที่)
Amin Negm-Awad

1
ตอบอย่างมาก :)
iDevAmit

1
unownedผมจะแนะนำไม่เคยใช้ ไม่คุ้มกับความเสี่ยงที่จะทำให้แอปของคุณเสีย
Kyle Redfearn

32

ใส่[unowned self]ก่อน(text: String)...ในการปิดของคุณ สิ่งนี้เรียกว่ารายการจับภาพและวางคำแนะนำการเป็นเจ้าของสัญลักษณ์ที่ถูกจับในการปิด


2
ขอบคุณที่ตั้งชื่อมันฉันอยากรู้ว่า!
rob5408

3
ฉันไม่คิดว่าคำตอบนี้มีประโยชน์ [ตัวเองที่ไม่มีชื่อ] จะพังถ้าตัวเองกลายเป็นศูนย์ในระหว่างการถูกประหารชีวิต
Yunus Nedim Mehel

3
มีอย่างไม่มีเหตุผลที่จะใช้ที่ไม่มีเจ้าของอื่น ๆ กว่า (1) ในมากสถานการณ์ที่ผิดปกติสำหรับการทำงาน (นี่คือที่ไม่เกี่ยวข้องอย่างเต็มที่ที่นี่และใน 99.999% ของการเขียนโปรแกรม) และ (2) เป็นเรื่องรูปแบบการบังคับใช้ ข้อความที่ว่า "คุณควรใช้คำว่าอ่อนแอไม่เป็นเจ้าของเสมอ" นั้นสมเหตุสมผลมาก
Fattie

29

** แก้ไขสำหรับ Swift 4.2:

ในฐานะที่ @Koen แสดงความคิดเห็น swift 4.2 ช่วยให้:

guard let self = self else {
   return // Could not get a strong reference for self :`(
}

// Now self is a strong reference
self.doSomething()

PS: ตั้งแต่ฉันมีบางอย่างขึ้นเสียงผมอยากจะขอแนะนำให้อ่านเกี่ยวกับการปิดการหลบหนี

แก้ไข: ตามที่ @ tim-vermeulen แสดงความคิดเห็น Chris Lattner กล่าวเมื่อวันศุกร์ที่ 22 มกราคม 19:51:29 CST 2016 เคล็ดลับนี้ไม่ควรใช้กับตัวเองดังนั้นโปรดอย่าใช้ ตรวจสอบข้อมูลการปิดไม่ใช่การหลบหนีและคำตอบรายการบันทึกภาพจาก @gbk. **

สำหรับผู้ที่ใช้ [อ่อนแอตัวเอง] ในรายการบันทึกโปรดทราบว่าตนเองอาจเป็นศูนย์ดังนั้นสิ่งแรกที่ฉันต้องทำคือตรวจสอบด้วยคำแถลงยาม

guard let `self` = self else {
   return
}
self.doSomething()

หากคุณกำลังสงสัยว่าเครื่องหมายคำพูดที่มีรอบselfเป็นเคล็ดลับที่จะใช้โปรตนเองภายในปิดโดยไม่จำเป็นต้องเปลี่ยนชื่อนี้ , weakSelfหรืออะไรก็ตาม


2
`self 'เป็นตัวอย่างของshadowing
Cullen SUN

2
ฉันมักจะเรียกว่า "ตนเอง" "strongSelf" ในท้องถิ่นเพื่อให้แน่ใจว่ามันจะไม่สับสนกับตัวเองเริ่มต้นและง่ายต่อการตรวจสอบว่าคุณได้รับการปกป้องสำหรับการอ้างอิงด้วยตนเองที่แข็งแกร่ง
Justin Stanley

1
ไม่ควรใช้สิ่งนี้เนื่องจากเป็นจุดบกพร่องของคอมไพเลอร์: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/ …
Tim Vermeulen

1
ฉันคิดว่าความคิดเห็นของ Chris Lattner ในลิงค์ด้านบนเป็นเพียงการไม่ตั้งชื่อตัวแปรเป็นself(ใน backticks) ตั้งชื่อมันเป็นอย่างอื่นเช่น nonOptionalSelf และมันก็ใช้ได้
OutOnAWeekend

1
ทุกวันนี้ (รวดเร็ว 4.2) { [weak self] in guard let self = self else { return }สามารถใช้ได้โดยไม่ทำการ backticks และรองรับจริง: github.com/apple/swift-evolution/blob/master/proposals/?hl=th
Koen

26

ใช้รายการจับภาพ

การกำหนดรายการจับภาพ

แต่ละรายการในรายการจับภาพคือการจับคู่ของคำหลักที่อ่อนแอหรือไม่มีเจ้าของที่มีการอ้างอิงถึงอินสแตนซ์ของชั้นเรียน (เช่นตนเอง) หรือตัวแปรเริ่มต้นด้วยค่าบางอย่าง (เช่นมอบหมาย = self.delegate!) การจับคู่เหล่านี้เขียนขึ้นภายในวงเล็บปีกกาสองอันคั่นด้วยเครื่องหมายจุลภาค

วางรายการการดักจับไว้หน้ารายการพารามิเตอร์ของการปิดและประเภทการส่งคืนหากมีการจัดเตรียมไว้ให้:

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here 
} 

หากการปิดไม่ได้ระบุรายการพารามิเตอร์หรือชนิดส่งคืนเนื่องจากสามารถสรุปได้จากบริบทให้วางรายการจับภาพที่จุดเริ่มต้นของการปิดตามด้วยคำหลักใน:

lazy var someClosure: Void -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // closure body goes here
}

คำอธิบายเพิ่มเติม


3
คุณใช้ "ตนเอง" ที่ไม่มีชื่อสำหรับซึ่งหมายความว่าคุณรู้แน่นอนว่า "ตนเอง" จะไม่เป็นศูนย์เมื่อคุณเข้าถึง จากนั้นคุณใช้บังคับให้แกะ "self.delegate" (ซึ่งหมายความว่าคุณรู้ว่ามันจะไม่เป็นศูนย์) เพื่อกำหนดให้กับ var ที่อ่อนแอ หากคุณรู้แน่ ๆ ว่า "self.delegate" จะไม่เป็นศูนย์ทำไมไม่ลองใช้ "ผู้แทน" ที่ไม่ได้เป็นเจ้าของแทนการอ่อนแอ
Roni Leshes

26

แก้ไข: อ้างอิงถึงโซลูชันที่อัปเดตโดย LightMan

ดูวิธีการแก้ปัญหาของ Lightman จนถึงตอนนี้ฉันกำลังใช้:

input.action = { [weak self] value in
    guard let this = self else { return }
    this.someCall(value) // 'this' isn't nil
}

หรือ:

input.action = { [weak self] value in
    self?.someCall(value) // call is done if self isn't nil
}

โดยปกติคุณไม่จำเป็นต้องระบุประเภทพารามิเตอร์หากอนุมาน

คุณสามารถละเว้นพารามิเตอร์ทั้งหมดได้หากไม่มีหรืออ้างถึงในขณะ$0ที่ปิด:

input.action = { [weak self] in
    self?.someCall($0) // call is done if self isn't nil
}

เพียงเพื่อความสมบูรณ์ หากคุณผ่านการปิดฟังก์ชันและพารามิเตอร์ไม่ใช่@escapingคุณไม่จำเป็นต้องweak self:

[1,2,3,4,5].forEach { self.someCall($0) }

9

จากความรวดเร็ว 4.2 🔸เราทำได้:

_ = { [weak self] value in
    guard let self = self else { return }
    print(self) //👈 will never be nil
}()

อื่น ๆ มีวิธีแก้ไขปัญหาคล้ายกัน แต่ "นี่" คือ C ++ IMHO "strongSelf" เป็นแบบแผนของ Apple และใครก็ตามที่จ้องมองโค้ดของคุณจะรู้ว่าเกิดอะไรขึ้น
David H

1
@ David H IMO วลีนี้strongSelfอธิบายความหมายของตัวแปร / ด้านข้างอย่างชัดเจนซึ่งเป็นสิ่งที่ดีหากรหัสนั้นมีความยาวมากกว่า ขอบคุณสำหรับความคิดเห็นของคุณ แต่ไม่รู้ว่า c ++ ใช้ประโยคดังกล่าว
eonist

3
ตั้งแต่ Swift 4.2 คุณสามารถใช้guard let self = self else { return }เพื่อแกะ[weak self]: github.com/apple/swift-evolution/blob/master/proposals/?hl=th
Amer Hukic

@AmerHukic 👌
eonist


3

คุณสามารถใช้ [ตัวเองอ่อนแอ] หรือ [ตัวเองไม่มีกรรมสิทธิ์] ในรายการจับภาพก่อนพารามิเตอร์ของบล็อก รายการจับภาพเป็นไวยากรณ์ตัวเลือก

[unowned self]ทำงานได้ดีที่นี่เพราะเซลล์จะไม่มีศูนย์ มิฉะนั้นคุณสามารถใช้[weak self]


1
เซลล์ไม่ใช่ตัวเองเขาไม่ได้อยู่ในชั้นเซลล์เขาน่าจะเป็นผู้ควบคุม ...
Juan Boero

0

หากคุณล้มเหลวกว่าที่คุณต้องการ [ตัวอ่อนแอ]

ฉันเดาว่าบล็อกที่คุณกำลังสร้างยังคงมีสายอยู่บ้าง

สร้าง PreparForReuse และลองล้างบล็อค onTextViewEditClosure ข้างในนั้น

func prepareForResuse() {
   onTextViewEditClosure = nil
   textView.delegate = nil
}

ดูว่าช่วยป้องกันความผิดพลาดหรือไม่ (มันเป็นเพียงการคาดเดา)


0

การปิดและรอบการอ้างอิงที่แข็งแกร่ง[เกี่ยวกับ]

ดังที่คุณทราบการปิดตัวของ Swift นั้นสามารถจับภาพได้ หมายความว่าคุณสามารถใช้งานได้selfในการปิด โดยเฉพาะescaping closure[เกี่ยวกับ]สามารถสร้างสิ่งstrong reference cycleที่ โดยวิธีการที่คุณต้องใช้อย่างชัดเจนภายในselfescaping closure

การปิดสวิฟท์มีCapture Listคุณสมบัติที่ช่วยให้คุณสามารถหลีกเลี่ยงสถานการณ์ดังกล่าวและทำลายวงจรการอ้างอิงได้เนื่องจากไม่มีการอ้างอิงที่แข็งแกร่งสำหรับอินสแตนซ์ที่ถูกจับ องค์ประกอบรายการบันทึกภาพเป็นคู่ของweak/unownedและการอ้างอิงถึงคลาสหรือตัวแปร

ตัวอย่างเช่น

class A {
    private var completionHandler: (() -> Void)!
    private var completionHandler2: ((String) -> Bool)!

    func nonescapingClosure(completionHandler: () -> Void) {
        print("Hello World")
    }

    func escapingClosure(completionHandler: @escaping () -> Void) {
        self.completionHandler = completionHandler
    }

    func escapingClosureWithPArameter(completionHandler: @escaping (String) -> Bool) {
        self.completionHandler2 = completionHandler
    }
}

class B {
    var variable = "Var"

    func foo() {
        let a = A()

        //nonescapingClosure
        a.nonescapingClosure {
            variable = "nonescapingClosure"
        }

        //escapingClosure
        //strong reference cycle
        a.escapingClosure {
            self.variable = "escapingClosure"
        }

        //Capture List - [weak self]
        a.escapingClosure {[weak self] in
            self?.variable = "escapingClosure"
        }

        //Capture List - [unowned self]
        a.escapingClosure {[unowned self] in
            self.variable = "escapingClosure"
        }

        //escapingClosureWithPArameter
        a.escapingClosureWithPArameter { [weak self] (str) -> Bool in
            self?.variable = "escapingClosureWithPArameter"
            return true
        }
    }
}
  • weak- เป็นที่นิยมมากกว่าใช้เมื่อเป็นไปได้
  • unowned - ใช้เมื่อคุณแน่ใจว่าอายุการใช้งานของเจ้าของอินสแตนซ์นั้นใหญ่กว่าการปิด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.