假设我有这样的代码:

class Foo {

    private let nc: NotificationCenter
    
    init(nc: NotificationCenter = .default) {
        self.nc = nc
        
        nc.addObserver(forName: Notification.Name.Foo, object: nil, queue: .main) { _ in
            Task { [weak self] in
                self?.doSomething()
            }
        }
    }

    deinit {
        nc.removeObserver(self, name: Notification.Name.Foo, object: nil)
    }

    @objc
    private func doSomething() async {
        // triggers some async code
    }
}

我想要编写一个单元测试,以确保在释放类时删除该观察器.

苹果公司的文件规定:

如果您使用addWatch(forName:Object:Queue:Using:)来创建您的观察器,则应在系统释放addWatch(forName:Object:Queue:Using:)指定的任何对象之前调用此方法或移除该方法或删除该方法(_:name:Object:).

我曾嘲笑过一个通知中心:

class MockNotificationCenter: NotificationCenter {

    var notifications: [NSNotification.Name?] = []

    override func addObserver(
        forName name: NSNotification.Name?,
        object obj: Any?,
        queue: OperationQueue?,
        using block: @escaping (Notification) -> Void) -> NSObjectProtocol
    {
        notifications.append(name)
        return super.addObserver(forName: name, object: obj, queue: queue, using: block)
    }

    override func removeObserver(
        _ observer: Any, 
        name aName: NSNotification.Name?, 
        object anObject: Any?
    ) {
        notifications = notifications.filter { $0 != aName }
        super.removeObserver(observer, name: aName, object: anObject)
    }

下面是我的单元测试:

    func testDeinit_DoesRemoveObserver() {
        // Given
        var sut: Foo?
        let mockNC = MockNotificationCenter()
        // When
        sut = Foo(notificationCenter: mockNC)
        // Then
        XCTAssertEqual(mockNC.notifications.count, 1) // Succeeds
        // When
        sut = nil
        // Then
        XCTAssertEqual(mockNC.notifications.count, 0) // Fails
    }

XCTAssertEqual失败:("1")不等于("0")

我如何解决这个问题,以断言观察者在我的观察对象解除分配后已从通知中心移除?

推荐答案

问题是在你的Foo个班级里有一个保留周期.在addObserver闭包中,捕获Foo类实例本身.它应该是:

nc.addObserver(forName: Notification.Name.Foo, object: nil, queue: .main) { [weak self] _ in
    Task {
        self?.doSomething()
    }
}

sut被赋值为nil时,deinitremoveObserver没有被调用,通知数组仍然是一个元素.

Ios相关问答推荐

无法在fastlane和github操作中从react-native 应用程序构建ios

什么是最好的方式使用DateFormatters在可扩展视图cellForRowAt方法

如何仅在TextField中的文本发生更改时才执行代码?

如何在本地iOS没有插件的情况下处理平台线程?

带有故事板的Xcode 15 UIKit视图可以与#Preview宏一起使用吗?

withTaskGroup不适用于CLGeocoder

Swift Vision库识别文本后如何将其赋值给 struct 体实例属性以供显示?

错误地提取问题的答案选项

SwiftUI 每秒从 Timer 更新单个 @State 属性,每秒也重绘整个视图

为什么 Swift 不在启动的同一线程上恢复异步函数?

占位符文本未显示在 TextEditor 上

如何从 Locale Swift 获取货币符号

如何在不支持并发的自动关闭中修复'async'调用?

在向我的 struct 添加属性后获取线程 10:EXC_BAD_ACCESS (code=EXC_I386_GPFLT)

从远程通知启动时,iOS 应用程序加载错误

如何使用 sendAsynchronousRequest:queue:completionHandler:

使用 convertPoint 获取父 UIView 内的相对位置

单击 GoogleSignIn 按钮时应用程序崩溃

使用 swift 将本地 html 加载到 UIWebView

iOS 7 应用程序图标、启动图像和命名约定,同时保留 iOS 6 图标