2016-03-23 2 views
0

У меня странная вещь, которая меня отключает. Чувствуется, что есть простой «В Swift 2 мы всегда (или никогда) делаем это», что мне не хватает, но я не вижу его.Swift, Singletons, переменные, представляющие функции

У меня есть класс мозга, предназначен для использования в качестве одноэлементного:

class Brain: NSObject { 

    static var sharedInstance : Brain? 

    var languageLoadedAndReadyFunction = languageLoadedAndReadyImplementation 

    init() { 
     NSNotificationCenter.defaultCenter().addObserver(self, 
      selector: "languageLoadedAndReady", 
      name: Notifications.LanguageLoadedAndReady, 
      object: nil) 
    } 

    func languageLoadedAndReadyImplementation() { 

     print("Got language Ready notification") 
    } 

    func languageLoadedAndReady() { 

     self.languageLoadedAndReadyFunction(self)() 
    } 

    class func get() -> Brain! { 

     return sharedInstance 
    } 

    //... 

    class func reset() { 

     sharedInstance = Brain()   
    } 

    deinit() { 

     NSNotificationCenter.defaultCenter().removeObserver(self) 
    } 
} 

В моем модульном тесте для мозга:

func testBrainRegisterForNotificationWhenWakingUp() { 

    let expectation = expectationWithDescription("Function should be called when the notification is received") 

    var brain = Brain.get() 

    brain.languageLoadedAndReadyFunction = {brain ->() ->() in 
     { 

      expectation.fulfill() 

      Brain.reset() // <-- this sets sharedInstance to a new Brain 
     } 
    } 

    brain.startUp() // <-- this causes the languageLoadedAndReady event to arrive at the brain 

    waitForExpectationsWithTimeout(5) { 
     error in 
     if let error = error { 
      print("Error: \(error.localizedDescription)") 
     } 
    } 
} 

Пути мне нужно определить функцию, как «мозг ->() ->() «чувствует себя громоздким, но, похоже, это работает. Это позволило мне увидеть, когда поступило уведомление, поэтому я могу проверить, по результатам теста, что мы ведем себя правильно. Метод deinit() вызывается после сброса мозга, указывая на то, что старый мозг удаляется из памяти.

Сегодня я пишу новые тесты для TranslatorTests.swift:

func testTranslatorCanTranslate() { 

    let expectation = expectationWithDescription("Function should be called when the notification is received2") 

    var brain = Brain.get() 

    brain.languageLoadedAndReadyFunction = {brain ->() ->() in 
     { 

      expectation.fulfill() 

      Brain.reset() 

      print("first TranslatorTests") 
     } 
    } 

    brain.startUp() // <-- 'brain' is different here than in BrainTests.swift, causes 
        // the closure in BrainTests.swift to be called. 

    waitForExpectationsWithTimeout(5) { 
     error in 
     if let error = error { 
      print("Error: \(error.localizedDescription)") 
     } 
    } 

    translator.setActiveLanguage("en") 

    let resultString = translator.translate("about_title") 

    XCTAssertEqual(resultString, "About") 
} 

func testTranslatorCanTranslateSecondWord() { 

    let expectation = expectationWithDescription("Function should be called when the notification is received3") 

    let brain = Brain.get() 

    brain.languageLoadedAndReadyFunction = {brain ->() ->() in 
     { 

      expectation.fulfill() 

      Brain.reset() 

      print("second TranslatorTests") 
     } 
    } 

    brain.startUp() // <-- 'brain' is different here than in BrainTests.swift, causes 
        // the closure in BrainTests.swift to be called. 

    waitForExpectationsWithTimeout(5) { 
     error in 
     if let error = error { 
      print("Error: \(error.localizedDescription)") 
     } 
    } 

    translator.setActiveLanguage("en") 

    let resultString = translator.translate("actmon_ready") 

    XCTAssertEqual(resultString, "Ready to upload") 
} 

Когда я запускаю тесты, я получаю самую странную ошибку: замыкание в BrainTests.swift вызывается, когда TranslatorTests. выполняется swift. Это приводит к тому, что метод expectation.fulfill() будет вызван во второй раз, что приведет к сбою.

Кстати, внутри замыкания нет «я», но если я поднимаюсь на один уровень в стеке вызовов, «я» относится к предыдущему экземпляру мозга. Это приводит меня к убеждению, что синтаксис мозга ->() ->() является проблемой.

Это ошеломило меня. У «мозга» есть другой адрес перед каждым закрытием, что указывает на то, что это другой экземпляр. Фактически, старый мозг был деинирован этим пунктом, так как его можно было назвать?

Я бы подумал, что назначение переменной для экземпляра означает, что мы даем закрытию новую функцию для выполнения для этого экземпляра.

Может кто-нибудь объяснить это мне? Пожалуйста, используйте небольшие слова, я чувствую себя немного менее умным, чем при написании этого кода.

ответ

0

В моем исследовании, я наткнулся на эти две статьи, которые помогли:

http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/ https://devforums.apple.com/message/1008188#1008188

Это объясняет мне, почему вызов «{мозг ->() ->() в .. . ".

Проблема у меня в коде было, конечно, что переменная экземпляра была объявлена ​​как это в моих тестовых файлов:

class BrainTests: XCTestCase { 

    var brain: Brain! 

    override func setUp() { 

     super.setUp() 
     brain = Brain.get() 
    } 

    override func tearDown() { 

     super.tearDown() 
     Brain.reset() 
    } 

    func testBrainExists() { 

     XCTAssertNotNil(brain) 
    } 

Так что, когда я звоню Brain.reset() уронить старый синглтон и создать новый, все еще был указатель на оригинал.

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

Доброта, это была подлая вещь!