Created
November 16, 2017 01:14
-
-
Save paulz/fc2993108e389eb85ae20c508b5ac22a to your computer and use it in GitHub Desktop.
Unit Test showing that you have to called removeObserver before deinit when using .addObserver(forName:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Quick | |
import Nimble | |
private let uniqueName = "unique name of notification" | |
private let testNotificationName = NSNotification.Name(uniqueName) | |
private var blockInvocationCounter = 0 | |
private class Counter { | |
var received: Int = 0 | |
var instances: Int = 0 | |
} | |
private class ObservationDeck { | |
var blockObserver: NSObjectProtocol! | |
let center: NotificationCenter | |
var counter: Counter | |
init(center: NotificationCenter, counter: Counter) { | |
self.center = center | |
self.counter = counter | |
self.counter.instances += 1 | |
} | |
func addBlockObserver() { | |
blockObserver = center.addObserver(forName: testNotificationName, object: nil, queue: nil) { [weak self] (note) in | |
blockInvocationCounter += 1 | |
self?.onNotification(notification: note) | |
} | |
} | |
func addSelectorObserver() { | |
center.addObserver(self, | |
selector: #selector(onNotification(notification:)), | |
name: testNotificationName, | |
object: nil) | |
} | |
@objc | |
func onNotification(notification: Notification) { | |
counter.received += 1 | |
} | |
deinit { | |
counter.instances -= 1 | |
} | |
} | |
class RemoveObserverSpec: QuickSpec { | |
override func spec() { | |
describe("NotificationCenter.removeObserver") { | |
var center: NotificationCenter! | |
var counter: Counter! | |
weak var toBeDeallocated: AnyObject? | |
beforeEach { | |
center = NotificationCenter() | |
counter = Counter() | |
blockInvocationCounter = 0 | |
} | |
context("block observer") { | |
it("does not remove observer by deinit and does not destroy observer") { | |
do { | |
let observer = ObservationDeck(center: center, counter: counter) | |
observer.addBlockObserver() | |
toBeDeallocated = observer.blockObserver | |
expect(center.debugDescription).to(contain(uniqueName)) | |
center.post(name: testNotificationName, object: nil) | |
expect(counter.received) == 1 | |
expect(blockInvocationCounter) == 1 | |
expect(observer).notTo(beNil()) | |
} | |
expect(toBeDeallocated).notTo(beNil()) | |
expect(counter.instances) == 0 | |
center.post(name: testNotificationName, object: nil) | |
expect(blockInvocationCounter) == 2 | |
expect(counter.received) == 1 | |
expect(center.debugDescription).to(contain(uniqueName)) | |
// really remove observer | |
center.removeObserver(toBeDeallocated!) | |
expect(center.debugDescription).notTo(contain(uniqueName)) | |
center.post(name: testNotificationName, object: nil) | |
expect(blockInvocationCounter) == 2 | |
expect(toBeDeallocated).notTo(beNil()) | |
} | |
context("remove observer should be called before deinit") { | |
it("should not receive notification after deinit") { | |
do { | |
let observer = ObservationDeck(center: center, counter: counter) | |
toBeDeallocated = observer | |
observer.addBlockObserver() | |
center.post(name: testNotificationName, object: nil) | |
expect(counter.received) == 1 | |
center.removeObserver(observer.blockObserver) | |
} | |
center.post(name: testNotificationName, object: nil) | |
expect(center.debugDescription).notTo(contain(uniqueName)) | |
expect(toBeDeallocated).to(beNil()) | |
expect(counter.instances) == 0 | |
expect(blockInvocationCounter) == 1 | |
} | |
} | |
} | |
context("deinit") { | |
it("should be called when exiting a block") { | |
do { | |
let deck = ObservationDeck(center: center, counter: counter) | |
toBeDeallocated = deck | |
expect(counter.instances) == 1 | |
expect(toBeDeallocated).notTo(beNil()) | |
} | |
expect(counter.instances) == 0 | |
expect(toBeDeallocated).to(beNil()) | |
} | |
} | |
context("selector observer") { | |
it("holds strong reference to observing object") { | |
do { | |
let observer = ObservationDeck(center: center, counter: counter) | |
toBeDeallocated = observer | |
observer.addSelectorObserver() | |
expect(center.debugDescription).to(contain(uniqueName)) | |
center.post(name: testNotificationName, object: nil) | |
expect(counter.received) == 1 | |
expect(observer).notTo(beNil()) | |
} | |
expect(toBeDeallocated).notTo(beNil()) | |
expect(counter.instances) == 1 | |
center.post(name: testNotificationName, object: nil) | |
expect(counter.received) == 2 | |
expect(center.debugDescription).to(contain(uniqueName)) | |
center.removeObserver(toBeDeallocated!) | |
center.post(name: testNotificationName, object: nil) | |
expect(counter.received) == 2 | |
expect(center.debugDescription).notTo(contain(uniqueName)) | |
} | |
context("remove observer") { | |
it("should be called by deinit") { | |
do { | |
let observer = ObservationDeck(center: center, counter: counter) | |
toBeDeallocated = observer | |
observer.addSelectorObserver() | |
center.post(name: testNotificationName, object: nil) | |
expect(counter.received) == 1 | |
} | |
center.post(name: testNotificationName, object: nil) | |
expect(toBeDeallocated).to(beNil()) | |
expect(counter.instances) == 0 | |
expect(counter.received) == 1 | |
expect(center.debugDescription).notTo(contain(uniqueName)) | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment