2016-03-26 2 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).

Воспроизводя Крушение

Катастрофа может воспроизвести путем перехода между сценами в несколько раз. Таким образом, утечка произойдет потому, что someInstance сохраняется переменной closure, а переменная closure сохраняется переменной someInstance, поэтому у нас есть цикл. Но тем не менее, это не приведет к сбою (он просто протекает). Когда я на самом деле пытаюсь добавить self.method() внутри закрытия, аварии приложения и я получаю это:

error info

и это:

error info

Точно такие же аварии я могу произвести, если я попытайтесь получить доступ к ссылке unowned, когда ссылающийся на нее объект освобождается, например. когда закрытие переживает захваченный экземпляр. Это имеет смысл, но это не так (закрытие никогда не выполняется).

Таинственной часть

Таинственная часть является то, что эта авария происходит только на прошивке 9.1 и не на iOS9.3. И еще одна загадочная вещь заключается в том, что приложение неожиданно разбивает , но в основном в течение первых десяти переходов. Кроме того, странная часть - это то, почему она падает, если закрытие никогда не выполняется, или экземпляр, который он захватывает, не доступен (по крайней мере, не мной).

Решение проблемы, но не ответ на вопрос

Конечно аварии можно решить несколькими способами, нарушая цикл, и я знаю, что я должен использовать unowned только тогда, когда я полностью уверены, что захваченный экземпляр никогда не станет нулевым после инициализации. Но все же, из-за того, что я не выполнил это закрытие вообще, после того, как он пережил сцену, эта авария для меня довольно неудобна. Кроме того, возможно, стоит упомянуть, что сцены успешно деинируются после каждого перехода.

Интересный

Если я использую weak self внутри списка захвата, приложение не будет врезаться (утечка все еще существует, конечно). Это имеет смысл, потому что, если сцена становится nil до того, как блок освобожден, доступ к сцене с помощью дополнительной цепочки предотвратит сбой. Но самое интересное в том, что даже если я использую forced unwrapping как это, он не будет врезаться:

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

Что заставляет меня думать ... Цените никаких намеков о том, как отладить это или объяснение о том, что причиной аварии .. .

EDIT:

Вот 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, но на данный момент я просто заменил [unowned self] на [weak 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 в конце эта функция. Если это то, что вы хотите, сохраните его внутри функции. Вы также можете использовать unowned, если хотите, но, как вы знаете, я думаю, что я просто больший поклонник использования слабого lol. Дайте мне знать, если это устраняет утечку и сбои.

+0

Хорошо спасибо за ответ и усилие ... Посмотрите, как только я найду время. – Whirlwind

+0

Нет проблем. Дайте мне знать, потому что из того, что я увидел в ответе выше, с теми же адресами памяти, которые я использую, я думаю, что сильная память с утечкой памяти может стать причиной проблемы –

 Смежные вопросы

  • Нет связанных вопросов^_^