ios - Swift、单例、代表函数的变量

标签 ios swift unit-testing singleton

有一件奇怪的事情让我绊倒。感觉好像有一个简单的“在 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 -> () -> ()”的方式感觉很麻烦,但似乎很有效。它让我知道通知何时到达,这样我就可以通过测试验证我们的行为是否正确。 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' ,但是如果我在调用堆栈中上升一级,'self' 指的是大脑的前一个实例。这让我相信 Brain -> () -> () 语法就是问题所在。

这让我很困惑。 “大脑”在每次关闭之前都有不同的地址,这向我表明它是一个不同的实例。其实到这里,老大脑就已经被定义了,那还怎么调用呢?

我本以为为实例分配变量意味着我们为闭包提供了一个新函数来为此实例执行。

谁能给我解释一下吗?请使用小词,我感觉自己比编写这段代码时有点不聪明。

最佳答案

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

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

这向我解释了为什么调用是“{brain -> () -> () 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() 删除旧的单例并创建一个新的单例时,仍然有一个指向原始单例的指针。

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

天哪,这是一件偷偷摸摸的事情!

关于ios - Swift、单例、代表函数的变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36175246/

相关文章:

ios - Uint8 不可转换为 Int(节省游戏最佳时间)

c# - 在 .NET Core 中运行 NUnit 测试

java - 如何在 Gradle Android 插件中启用 processfork 进行单元测试

ios - 在 uitableview, IOS 中突出显示第一个单元格时遇到问题

ios - 获取 iOS 系统正常运行时间, sleep 时不会暂停

ios - 使用 GL_POINTS 通过 OpenGL 改变线宽 (iOS)

ios - swift 3 (SpriteKit) : Reseting GameScene doesn't deallocate

swift - 快速将 2 位数年份转换为 4 位数年份

ios - 如何查看iOS11录屏是开还是关?

python - 为什么我的 python unittest 脚本在导入时调用我的脚本?