How to Correctly handle Weak Self in Swift Blocks with Arguments

这一生的挚爱 提交于 2019-12-27 12:10:06

问题


In my TextViewTableViewCell, I have a variable to keep track of a block and a configure method where the block is passed in and assigned.
Here is my TextViewTableViewCell class:

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

When I use the configure method in my cellForRowAtIndexPath method, how do I properly use weak self in the block that I pass in.
Here is what I have without the weak self:

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

UPDATE: I got the following to work using [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

When I do [unowned self] instead of [weak self] and take out the if statement, the app crashes. Any ideas on how this should work with [unowned self]?


回答1:


If self could be nil in the closure use [weak self].

If self will never be nil in the closure use [unowned self].

If it's crashing when you use [unowned self] I would guess that self is nil at some point in that closure, which is why you had to go with [weak self] instead.

I really liked the whole section from the manual on using strong, weak, and unowned in closures:

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

Note: I used the term closure instead of block which is the newer Swift term:

Difference between block (Objective C) and closure (Swift) in ios




回答2:


Put [unowned self] before (text: String)... in your closure. This is called a capture list and places ownership instructions on symbols captured in the closure.




回答3:


**EDITED for Swift 4.2:

As @Koen commented, swift 4.2 allows:

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

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

P.S.: Since I am having some up-votes, I would like to recommend the reading about escaping closures.

EDITED: As @tim-vermeulen has commented, Chris Lattner said on Fri Jan 22 19:51:29 CST 2016, this trick should not be used on self, so please don't use it. Check the non escaping closures info and the capture list answer from @gbk.**

For those who use [weak self] in capture list, note that self could be nil, so the first thing I do is check that with a guard statement

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

If you are wondering what the quote marks are around self is a pro trick to use self inside the closure without needing to change the name to this, weakSelf or whatever.




回答4:


Use Capture list

Defining a Capture List

Each item in a capture list is a pairing of the weak or unowned keyword with a reference to a class instance (such as self) or a variable initialized with some value (such as delegate = self.delegate!). These pairings are written within a pair of square braces, separated by commas.

Place the capture list before a closure’s parameter list and return type if they are provided:

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

If a closure does not specify a parameter list or return type because they can be inferred from context, place the capture list at the very start of the closure, followed by the in keyword:

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

additional explanations




回答5:


EDIT: Reference to an updated solution by LightMan

See LightMan's solution. Until now I was using:

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

Or:

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

Usually you don't need to specify the parameter type if it's inferred.

You can omit the parameter altogether if there is none or if you refer to it as $0 in the closure:

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

Just for completeness; if you're passing the closure to a function and the parameter is not @escaping, you don't need a weak self:

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



回答6:


Swift 4.2

let closure = { [weak self] (_ parameter:Int) in
    guard let self = self else { return }

    self.method(parameter)
}

https://github.com/apple/swift-evolution/blob/master/proposals/0079-upgrade-self-from-weak-to-strong.md




回答7:


As of swift 4.2 🔸 we can do:

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



回答8:


You can use [weak self] or [unowned self] in the capture list prior to your parameters of the block. The capture list is optional syntax.

[unowned self] works good here because the cell will never be nil. Otherwise you can use [weak self]




回答9:


If you are crashing than you probably need [weak self]

My guess is that the block you are creating is somehow still wired up.

Create a prepareForReuse and try clearing the onTextViewEditClosure block inside that.

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

See if that prevents the crash. (It's just a guess).



来源:https://stackoverflow.com/questions/24468336/how-to-correctly-handle-weak-self-in-swift-blocks-with-arguments

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!