Skip to content

Instantly share code, notes, and snippets.

@paulz
Created November 16, 2017 01:14
Show Gist options
  • Save paulz/fc2993108e389eb85ae20c508b5ac22a to your computer and use it in GitHub Desktop.
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:
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