2016-03-26 16 views
7

在這裏,我在玩漏洞,所以我有意識地製作了一個強大的參考週期,以查看儀器是否會檢測到某些東西,而我意想不到的結果。儀器中顯示的泄漏當然有意義,但隨機崩潰有點神祕(由於我將在後面提到的兩個事實)。在捕獲列表中使用無主模塊導致崩潰,即使模塊本身沒有執行

我這裏有一個名爲SomeClass類:

class SomeClass{ 

    //As you can guess, I will use this shady property to make a strong cycle :) 
    var closure:(()->())? 
    init(){} 
    func method(){} 
    deinit {print("SomeClass deinited")} 
} 

我也有兩個場景中,GameScene

class GameScene: SKScene { 

    override func didMoveToView(view: SKView) { 

     backgroundColor = .blackColor() 

     let someInstance = SomeClass() 

     let closure = {[unowned self] in 

      someInstance.method() //This causes the strong reference cycle... 
      self.method() 
     } 
     someInstance.closure = closure 
    } 

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { 

     if let nextScene = MenuScene(fileNamed: "MenuScene"){ 
      nextScene.scaleMode = .AspectFill 
      let transition = SKTransition.fadeWithDuration(1) 
      view?.presentScene(nextScene, transition: transition) 
     } 
    } 

    deinit {print("GameScene deinited")} 

    func method(){} 
} 

最後,MenuScene這等同於GameScene,只是用空的didMoveToView方法(它只有touchesBegan方法實現)。

再現崩潰

崩潰可以通過場景之間的轉換數次被重現。通過這樣做,泄漏將發生,因爲someInstanceclosure變量保留,並且closure變量由someInstance變量保留,所以我們有一個循環。但是,這仍然不會產生崩潰(它只會泄漏)。當我真正嘗試添加self.method()封閉,應用程序崩潰的內部,我得到這個:

error info

這:

error info

完全相同的崩潰,我可以,如果我產生嘗試訪問unowned引用時,它引用的對象被釋放,例如。當關閉超過捕獲的實例。這是有道理的,但情況並非如此(關閉從未執行)。

神祕的部分

神祕的部分是這個崩潰發生僅在iOS 9.1不是iOS9.3。另一個神祕的事實是,應用崩潰隨機,但大部分在前十個過渡。此外,怪異的部分是爲什麼它會崩潰,如果從不執行閉包,或者它捕獲的實例沒有被訪問(至少不是我)。

解決這個問題,但沒有答案的問題

當然墜毀可以在幾個方面打破循環來解決,而且我知道我應該使用unowned只有當我完全確保捕獲的實例永遠不會在初始化後變爲零。但是,由於事實上我還沒有執行過這個關閉,所以在它失效之後,這次崩潰對我來說非常尷尬。另外,值得一提的是,在每次轉換之後,場景都能成功地進行定義。

有趣

如果我使用weak self捕獲列表裏面,應用程序將不會崩潰(泄漏仍然存在,當然)。這是有道理的,因爲如果在解除分配塊之前場景變爲nil,通過可選鏈接訪問場景將防止崩潰。但有趣的是,即使我用forced unwrapping這樣的,它不會崩潰:

let closure = {[weak self] in 
     someInstance.method() 
     self!.method() 
} 

這讓我覺得...欣賞有關如何調試這或解釋什麼導致崩潰的任何提示.. 。

編輯:

這裏是Github上repo

+0

如果它沒有在9.3崩潰,那麼它可能是一個錯誤。這是你的報告的方式? [SR-1006](https://bugs.swift.org/browse/SR-1006)看起來是同樣的問題。 – Sulthan

+0

@Sulthan Nope,那不是我的錯誤報告......那麼,我開始了一個賞金,看看有人能夠真正證明這是一個錯誤,並解釋在這種情況下應該是什麼樣的預期行爲。就我個人而言,我認爲它應該泄漏,但不應該崩潰,但我們會看到... – Whirlwind

+0

我很確定它不應該崩潰,如果你不實際執行該塊。 – Sulthan

回答

1

崩潰正在發生的事情,因爲GameScene對象已被釋放BEF動畫完成。

實現此目的的一種方法是將SomeClass對象返回到閉包中。

class SomeClass { 
    var closure: (SomeClass->Void)? 
} 

這將是這樣使用:

override func didMoveToView(view: SKView) { 
    let someInstance = SomeClass() 
    someInstance.closure = { [unowned self] someClass in 
     someClass.method() // no more retain cycle 
     self.method() 
    } 
} 

UPDATE

原來這是一個微妙的組合,方便init?(fileNamed:) +濫用[unowned self]造成你崩潰。

儘管官方文檔似乎沒有說明,但在blog post中簡要解釋過,便捷初始值設定器實際上會重用相同的對象。

文件引用

場景編輯器允許您引用不同.sks(場景)文件之間的內容,這意味着你可以放在一起一串精靈在一個場景文件,然後從另一個引用文件場景文件。

你可能不知道你爲什麼會需要超過一個場景,有幾個原因:

1)可以重新使用在多個不同場景的精靈一樣收集,這意味着你不必重新他們一遍又一遍。

2)如果您需要更改所有場景中的引用內容,您只需編輯原始場景,並在引用該場景的每個場景中自動更新內容。聰明,對吧?

添加日誌各地的創建,關閉和DEINIT的設置導致了一些有趣的輸出:

GameScene init: 0x00007fe51ed023d0 
GameScene setting closure: 0x00007fe51ed023d0 
MenuScene deinited 
GameScene deinited: 0x00007fe51ed023d0 
GameScene init: 0x00007fe51ed023d0 
GameScene setting closure: 0x00007fe51ed023d0 

通知相同的內存地址如何被使用了兩次。我假設在引擎蓋下蘋果正在做一些有趣的內存管理優化,這可能導致deinit後仍然存在陳舊的關閉。

SpriteKit的內部可以做更深入的挖掘,但現在我只是用[weak self]替換[unowned self]

+0

所以你認爲崩潰是合理的,並且在iOS9.1上看到的行爲正常工作,但iOS9.3的結果是錯誤的(因爲相同的代碼不會產生崩潰)? – Whirlwind

+0

另外,你可以請更詳細和詳細的* GameScene實例在動畫完成之前發佈*聲明?你的意思是在實際的'SKTransition'完成之前釋放嗎?我忘了提及這只是在從'MenuScene' - >'GameScene'過渡時才發生,反之亦然。 – Whirlwind

+0

查看更新的答案 – Casey

0

從我可以看到如果看起來像保留循環會引起,因爲你是在它自己的閉包中包含一個對象並將其保存到自身。看看以下工作:

class GameScene: SKScene { 

    let someInstance = SomeClass() 

    override func didMoveToView(view: SKView) { 

     backgroundColor = .blackColor() 

     let closure = {[weak self, weak someInstance] in 

      someInstance?.method() //This causes the strong reference cycle... 
      self?.method() 
     } 
     someInstance.closure = closure 
    } 

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { 

     if let nextScene = MenuScene(fileNamed: "MenuScene"){ 
      nextScene.scaleMode = .AspectFill 
      let transition = SKTransition.fadeWithDuration(1) 
      view?.presentScene(nextScene, transition: transition) 
     } 
    } 

    deinit {print("GameScene deinited")} 

    func method(){} 
} 

我搬到someInstance,因爲我與塊弱引用,沒有地方傳遞someInstance以外的功能非常確信類的屬性,someInstance將在年底DEINIT該功能。如果那就是你想要的,然後在函數內部保留someInstance。如果你願意,你也可以使用無主,但如你所知,我想我只是使用弱大聲笑的一個更大的粉絲。讓我知道如果這可以修復泄漏和崩潰

+0

好的,謝謝你的迴應和努力......只要我找到時間,我會盡快回復。 – Whirlwind

+0

沒問題。讓我知道,因爲從我在上面的答案中看到的使用相同的內存地址,我認爲強者保留內存泄漏可能是問題的原因 –