swift 如何通过对象的deinit设置测试移除基于块的观察者?

9vw9lbht  于 11个月前  发布在  Swift
关注(0)|答案(1)|浏览(100)

假设我有这个代码:

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

字符串
我想写一个单元测试来确保这个观察者在类被释放时被移除。
Apple文档规定:
如果使用addObserver(forName:object:queue:using:)创建观察者,则应在系统释放addObserver(forName:object:queue:using:)指定的任何对象之前调用此方法或removeObserver(_: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”)
我如何修复这个问题,以Assert在解除分配我的观察对象时,观察者已从通知中心删除?

4ktjp1zp

4ktjp1zp1#

问题是在你的Foo类中有一个保留周期。在addObserver闭包中,你正在捕获Foo类示例本身。它应该是:

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

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

相关问题