2016-03-26 7 views
7

Qui, stavo giocando con perdite, quindi ho fatto un ciclo di riferimento forte intenzionalmente per vedere se gli strumenti rileveranno qualcosa, e io ha ottenuto risultati inaspettati. La perdita mostrata in Strumenti ha sicuramente senso, ma il crash casuale è un po 'misterioso (a causa di due fatti che menzionerò più avanti).L'utilizzo di un interno sconosciuto di una lista di cattura causa un crash anche il blocco stesso non viene eseguito

Quello che abbiamo qui è una classe chiamata 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")} 
} 

Anche io ho due scene, il 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(){} 
} 

E, infine, la MenuScene, che è identico al GameScene, basta con un metodo vuoto didMoveToView (ha implementato solo il metodo touchesBegan).

riproduzione dei Crash

L'incidente può essere riproducono per la transizione tra le scene per un paio di volte. In questo modo, la perdita si verificherà perché la variabile closure viene trattenuta dalla variabile closure e la variabile someInstance viene mantenuta, quindi abbiamo un ciclo. Ma ancora, questo non produrrà il crash (sarà solo una perdita). Quando sono in realtà provo ad aggiungere self.method() all'interno di una chiusura, l'applicazione si blocca e ottengo questo:

error info

e questo:

error info

Lo stesso incidente esatto posso produrre se io prova ad accedere ad un riferimento unowned quando l'oggetto a cui si fa riferimento è deallocato, ad es. quando la chiusura sopravvive all'istanza catturata. Questo ha senso, ma non è questo il caso (la chiusura non viene mai eseguita).

The Mysterious Parte

La parte misteriosa è che questo incidente accade solo su iOS 9.1 e non su iOS9.3. Un'altra cosa misteriosa è il fatto che l'app si arresta in modo anomalo a in modo casuale, ma per lo più entro le prime dieci transizioni. Inoltre, la parte strana è il motivo per cui si blocca se la chiusura non viene mai eseguita, o l'istanza che cattura non è accessibile (almeno non da me).

soluzione al problema, ma non è la risposta alla domanda

Naturalmente l'incidente può essere risolto in un paio di modi per rompere il ciclo, e mi rendo conto che dovrei usare unowned solo quando sono completamente sicuro che l'istanza catturata non diventerà mai nulla dopo l'inizializzazione. Ma ancora, a causa del fatto che non ho eseguito questa chiusura, dopo che è sopravvissuta alla scena, questo incidente è piuttosto imbarazzante per me. Inoltre, potrebbe valere la pena menzionare che le scene sono deinite con successo dopo ogni transizione.

Interessante

Se uso weak self all'interno di una lista di acquisizione, l'applicazione non vada in crash (la perdita esiste ancora, ovviamente). Il che ha senso, perché se la scena diventa nil prima che il blocco venga deallocato, l'accesso alla scena attraverso il concatenamento opzionale preverrà l'arresto anomalo. Ma la parte interessante è che, anche se io uso forced unwrapping come questo, non vada in crash:

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

Il che mi fa pensare ... Apprezziamo qualsiasi suggerimenti su come eseguire il debug di questo o spiegazione su ciò che causa l'incidente .. .

EDIT:

Ecco la Github repo

+0

Se non si arresta in modo anomalo su 9.3, probabilmente è un bug. A proposito, questo è il tuo rapporto? [SR-1006] (https://bugs.swift.org/browse/SR-1006) Sembra lo stesso problema. – Sulthan

+0

@Sulthan No, non è il mio bug report ... Beh, ho iniziato una taglia per vedere se qualcuno può davvero dimostrare che si tratta di un bug o no, e spiegare quale dovrebbe essere il comportamento previsto in questa situazione. Personalmente, penso che dovrebbe perdere, ma non dovrebbe andare in crash, ma vedremo ... – Whirlwind

+0

Sono abbastanza sicuro che non dovrebbe bloccarsi se non si esegue il blocco. – Sulthan

risposta

1

l'incidente accade perché l'oggetto GameScene è stato rilasciato bef l'animazione finisce.

un modo per implementare questo sarebbe quello di passare l'oggetto SomeClass indietro nella chiusura.

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

che sarebbe utilizzato come questo:

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

UPDATE

risulta questa è una combinazione di una sfumatura nella comodità init?(fileNamed:) + abuso di [unowned self] causando il crash.

sebbene la documentazione ufficiale non sembri affermarlo, come spiegato brevemente in questo blog post, l'inizializzatore di convenienza riutilizzerà effettivamente lo stesso oggetto.

File Riferimenti

L'editor di scena permette di fare riferimento a contenuti tra diversi .sks file (di scena), il che significa in grado di mettere insieme un gruppo di folletti in un singolo file di scena e quindi fare riferimento al file da un altro file di scena.

Si potrebbe chiedere perché si avrebbe bisogno di più di una scena, e c'è un paio di motivi:

1) È possibile riutilizzare la stessa collezione di sprite in più scene diverse, il che significa che non c'è bisogno di ricreare loro più e più volte.

2) Se è necessario modificare il contenuto di riferimento in tutte le scene, è sufficiente modificare la scena originale e il contenuto si aggiorna automaticamente in ogni scena che lo fa riferimento. Intelligente, giusto?

aggiungendo registrazione attorno alla creazione, l'impostazione della chiusura e deinit porta a qualche uscita interessante:

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

notare come lo stesso indirizzo di memoria viene utilizzato due volte.Direi che sotto la cappa Apple sta facendo un po 'di gestione della memoria interessante per l'ottimizzazione che sta potenzialmente portando alla chiusura stantia ancora esistente dopo il deinit.

scavo più profondo potrebbe essere fatto all'interno del SpriteKit, ma per ora mi piacerebbe solo sostituire [unowned self] con [weak self].

+0

Quindi pensi che il crash sia ragionevole, e che il comportamento visto su iOS9.1 funzioni correttamente, ma i risultati di iOS9.3 sono buggy (perché lo stesso codice non produce l'arresto anomalo)? – Whirlwind

+0

Inoltre, puoi essere più specifico e dettagliato sull'istanza * di GameScene che viene rilasciata prima che l'animazione termini l'istruzione *? Vuoi dire rilasciato prima che l'effettivo 'SKTransition' finisca o? Ho dimenticato di dire che questo sta accadendo solo quando si passa da 'MenuScene' ->' GameScene', non viceversa. – Whirlwind

+0

vedi risposta aggiornata – Casey

0

Da quello che posso vedere se sembra che il ciclo di conservazione potrebbe essere causato perché si include un oggetto nella sua chiusura e salvandolo su sé stesso. Vedere se le seguenti opere:

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(){} 
} 

mi sono trasferito someInstance a una proprietà della classe perché io sono abbastanza sicuro che con un riferimento debole nel blocco e senza passare someInstance da qualche parte fuori della funzione, someInstance sarà deinit alla fine del quella funzione. Se questo è ciò che vuoi, tienilo un po 'di sostanza all'interno della funzione. Puoi anche usare unowned se vuoi, ma come sai, credo di essere solo un fan più grande dell'uso del lol debole. Fatemi sapere se questo risolve la perdita e l'arresto

+0

Ok, grazie per la risposta e lo sforzo ... Esaminerò appena trovo il tempo. – Whirlwind

+0

Nessun problema. Fammi sapere perché da quello che ho visto nella risposta sopra con gli stessi indirizzi di memoria utilizzati, penso che il forte trattiene la perdita di memoria potrebbe essere una causa del problema –

Problemi correlati