簡體   English   中英

為什么在呼叫[無名的自我]時將自我釋放

[英]Why self is deallocated when calling [unowned self]

我正在嘗試編寫一個簡單的閉包作為完成處理程序,並在閉包內部設置文本框的文本值:

class ViewController: UIViewController {

    @IBOutlet var textArea : UITextView

    let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()

    let session:NSURLSession?


    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)

        session = NSURLSession(configuration: sessionConfig)
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    @IBAction func btnSendRequestTapped(sender : AnyObject) {

        let url:NSURL  = NSURL(string: "https://www.google.com")

        let sessionTask:NSURLSessionTask =
        session!.dataTaskWithURL(url, completionHandler: {
            [unowned self]
            (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
            let st:String = NSString(data: data,encoding: NSUTF8StringEncoding)

            println("\(st)")

            NSOperationQueue.mainQueue().addOperationWithBlock({
                () -> Void in
                self.textArea!.text = st
                })
            })

        sessionTask.resume()
    }
}

但是在我定義了[ EXC_BREAKPOINT(code=EXC_I386_BPT,subcode=0x0) self]的那一行上,我得到EXC_BREAKPOINT(code=EXC_I386_BPT,subcode=0x0) ,並且它顯示了一些匯編代碼,如下所示:

libswift_stdlib_core.dylib`_swift_abortRetainUnowned:
0x1001bb980:  pushq  %rbp
0x1001bb981:  movq   %rsp, %rbp
0x1001bb984:  leaq   0x176a7(%rip), %rax       ; "attempted to retain deallocated object"
0x1001bb98b:  movq   %rax, 0x792ce(%rip)       ; gCRAnnotations + 8
0x1001bb992:  int3   
0x1001bb993:  nopw   %cs:(%rax,%rax)

根據文檔,我不確定在這里做錯了什么。 我已經將問題更新為包含整個課程。 我也更新了問題以更新主線程上的TextView的text屬性

只是為了弄清這里的所有答案-如何解決此錯誤取決於您的需要。

原始問題中的代碼存在的問題是,從異步執行的NSOperation塊訪問了self (即UIViewController ,並且這是在異步 NSURLSessionTask的完成處理程序中完成的。

到運行庫到達self.textArea!.textself變為nil 在什么情況下會釋放UIViewController 當您將其從導航堆棧中彈出,被關閉時,等等。我猜測上面btnSendRequestTapped(:)的代碼不完整,並且在某個地方有popViewController()

解決方案是:

1:使用weak而不是unowned 兩者之間的區別在於, weak表示被捕獲的對象( self )可能為nil並將其轉換為可選對象, unowned意味着您確定self永遠不會為nil並且可以按原樣訪問self 使用weak意味着在使用self之前先解開self 如果為零,則在代碼中不執行任何操作。

{[weak self] in
    if let strongSelf = self {
        // ...
        strongSelf.textArea!.text = "123"
    }
}

2:另一種解決方案是取消NSURLSessionTask和其完成處理程序(其被分派到一個NSOperationQueue的屬性NSURLSession稱為delegateQueue如果) UIViewController被彈出導航堆棧的。

func btnSendRequestTapped() {
    // pop the view controller here...
    sessionTask.cancel()
    sessionTask.delegateQueue.cancelAllOperations()
}

3:繼續使用[unowned self] ,但是在完成操作塊之前不要彈出視圖控制器。 我個人更喜歡這種方法。

簡而言之,在真正完成self之前,不要釋放self

我不確定為什么,但是我認為使用weak而不是unownedunowned 這可能是一個錯誤。

session.dataTaskWithURL(url, completionHandler: {
        [weak self]
        (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
        let st:String = NSString(data: data,encoding: NSUTF8StringEncoding)

        self!.txtArea!.text = "123"
        }
    )

在完成區塊運行之前,您的self已被釋放。 不要忘了, unowned只是一樣unsafe_unretained並不會歸零了。 您可以改用[weak self]但您必須像這樣訪問它:

self?.txtArea!.text = "123"

在這種情況下,您最好使用weak self ,以防萬一您從導航堆棧中彈出視圖。

更新:Swift 3.0

let task:URLSessionTask = session.dataTask(with: url, completionHandler: {
        [weak self] (data:Data?, response: URLResponse?, error:Error?) in

        /// Cool tip instead of using *strongSelf*, use ` as:
            if let `self` = self {
               /// Run your code here, this will ensure if self was deallocated, it won't crash.
            }
    })

斯威夫特2.2

let sessionTask:NSURLSessionTask =
        session!.dataTaskWithURL(url, completionHandler: {
            [weak self] (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
                /// Cool tip instead of using *strongSelf*, use ` as:
                if let `self` = self {
                   /// Run your code here, this will ensure if self was deallocated, it won't crash.
                }
            })

我發現,當我在通知閉包內取消對變量的最后一個強引用時,這會發生在我身上,導致該對象在現場被取消初始化,但隨后相同的通知繼續被傳播到其他對象-觀察到相同通知的對象之一就是已取消初始化的對象-該對象將self稱為[unown self]。 顯然,盡管該對象已取消初始化,但它仍嘗試執行與通知關聯的代碼塊並崩潰。 我也看到過這種情況,當最后一個強引用被取消並且即將生成通知時,該通知將傳播到該對象。 即使deinit執行了NSNotificationCenter.defaultCenter()。removeObserver(self),它仍然崩潰。

在這些情況下,我成功使用的一種解決方案是在nil周圍包裹一個dispatch_async,如下所示:

dispatch_async(dispatch_get_main_queue(), { () -> Void in
   someVariable = nil
})

這將導致在通知完全傳播之后被淘汰的變量被銷毀。 盡管它曾經對我有用,但它似乎仍然很敏感。

最重要的是:對我來說,唯一可行的解​​決方案是消除閉包的使用,用較舊的對象/選擇器類型觀察器代替通知觀察器。

我認為這很可能是[無人認領的自我]運作方式的錯誤,而Apple僅需要對其進行修復。 我見過其他人談論他們在[無主的自我]時遇到的問題,需要使用[弱的自我]作為解決方法。 我認為這是同樣的問題。

我在運行Xcode 6.4時遇到了同樣的問題。 從擁有變為弱者似乎已經解決了這個問題。

兩件事情:

  1. _swift_abortRetainUnowned並非始終都發生。
  2. _swift_abortRetainUnowned發生在分配閉包時,而不是在執行閉包時發生,因此對於我來說,我確定self不會被釋放。

我發現如果“未擁有的對象”是“ Objective-C類”的對象,例如UIViewController,則程序將崩潰。 如果“無主對象”是“ Swift類”的對象,則可以。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM