Swift,Singletons,代表函数的变量

时间:2016-03-23 10:12:28

标签: ios swift unit-testing singleton

有一件奇怪的事让我沮丧。感觉就像有一个简单的“In Swift 2我们总是(或从不)做这个”我错过了,但我看不到它。

我有一个Brain类,意在用作单身人士:

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

在我对Brain的单元测试中:

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)")
        }
    }
}

我需要将函数定义为“brain - &gt;() - &gt;()”的方式感觉很麻烦,但它似乎很有效。它让我看到通知何时到达,所以我可以通过测试验证我们的行为是否正确。大脑重置后调用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")
}

当我运行测试时,我得到了最奇怪的错误:当执行TranslatorTests.swift时,会调用BrainTests.swift中的闭包。这会导致expectation.fulfill()方法第二次被调用,导致崩溃。

顺便说一句,在闭包内部没有“自我”,但如果我在调用堆栈中上升一级,则“self”指的是前一个大脑实例。这让我相信大脑 - &gt; () - &gt; ()语法是问题。

令我难以置信。在每个闭包之前,'大脑'有一个不同的地址,这向我表明它是一个不同的实例。事实上,旧脑已被这一点所取代,那怎么可能被称为呢?

我原本以为为实例分配变量意味着我们给闭包一个新函数来执行这个实例。

任何人都可以向我解释这个吗?请使用小词,我感觉比编写此代码时的智能要差一些。

1 个答案:

答案 0 :(得分:0)

在我的研究中,我偶然发现了这两篇有帮助的文章:

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

这解释了为什么这个电话是“{brain - &gt;() - &gt;()in ...”。

我的代码中遇到的问题当然是在我的测试文件中声明了实例变量:

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()删除旧的单例并创建一个新的单例时,仍然有一个指向原始的指针。

通过删除实例变量,大脑按预期重置,然后回调起作用。我可以根据需要设置每个测试用例的回调以通过我的所有测试。

天哪,这是一个鬼鬼祟祟的事情!