diff --git a/Sources/SCFNotification/Observation.swift b/Sources/SCFNotification/Observation.swift index 491f2ba..9426177 100644 --- a/Sources/SCFNotification/Observation.swift +++ b/Sources/SCFNotification/Observation.swift @@ -21,7 +21,7 @@ struct Observation { weak var observer: AnyObject? weak var object: AnyObject? - + var observerPtr: UnsafeMutableRawPointer? { guard let observer = self.observer else { return nil diff --git a/Sources/SCFNotification/ObservationStore.swift b/Sources/SCFNotification/ObservationStore.swift index b51e345..a7de3f8 100644 --- a/Sources/SCFNotification/ObservationStore.swift +++ b/Sources/SCFNotification/ObservationStore.swift @@ -78,6 +78,10 @@ class ObservationStore { localObservations = localObservations.cleanUpped() darwinNotifyObservations = darwinNotifyObservations.cleanUpped() + +#if os(macOS) + distributedObservations = distributedObservations.cleanUpped() +#endif } func notifyIfNeeded(center: SCFNotificationCenter.CenterType, observer: UnsafeMutableRawPointer?, name: CFNotificationName?, object: UnsafeRawPointer?, userInfo: CFDictionary?) { @@ -103,3 +107,18 @@ class ObservationStore { } } + +extension ObservationStore { + func observations(for center: SCFNotificationCenter.CenterType) -> [Observation] { + switch center { + case .local: + return localObservations + case .darwinNotify: + return darwinNotifyObservations +#if os(macOS) + case .distributed: + return distributedObservations +#endif + } + } +} diff --git a/Sources/SCFNotification/SCFNotificationCenter.swift b/Sources/SCFNotification/SCFNotificationCenter.swift index e0d8511..4dcffec 100644 --- a/Sources/SCFNotification/SCFNotificationCenter.swift +++ b/Sources/SCFNotification/SCFNotificationCenter.swift @@ -13,12 +13,12 @@ public class SCFNotificationCenter { private let center: CenterType - private init(center: CenterType) { + init(center: CenterType) { self.center = center } public func addObserver(observer: Observer, - name: CFNotificationName, + name: CFNotificationName?, object: AnyObject? = nil, suspensionBehavior: CFNotificationSuspensionBehavior, callback: @escaping SCFNotificationCallback) { @@ -31,7 +31,7 @@ public class SCFNotificationCenter { } public func removeObserver(observer: Observer, - name: CFNotificationName, + name: CFNotificationName?, object: AnyObject? = nil) { Self.removeObserver(center: center, observer: observer, diff --git a/Tests/SCFNotificationTests/CFNotificationTests.swift b/Tests/SCFNotificationTests/CFNotificationTests.swift deleted file mode 100644 index d2ea58e..0000000 --- a/Tests/SCFNotificationTests/CFNotificationTests.swift +++ /dev/null @@ -1,4 +0,0 @@ -import XCTest -@testable import SCFNotification - -final class SCFNotificationTests: XCTestCase {} diff --git a/Tests/SCFNotificationTests/SCFDarwinNotificationTests.swift b/Tests/SCFNotificationTests/SCFDarwinNotificationTests.swift new file mode 100644 index 0000000..c952517 --- /dev/null +++ b/Tests/SCFNotificationTests/SCFDarwinNotificationTests.swift @@ -0,0 +1,105 @@ +import XCTest +@testable import SCFNotification + +class SCFDarwinNotificationTests: SCFNotificationTests { + override var centerType: SCFNotificationCenter.CenterType { + .darwinNotify + } + + // Customized + // object is ignored + override func testObserveNamedWithObject() { + let exp = expectation(description: #function) + + notificationCenter + .addObserver(observer: self, + name: .init(#function as CFString), + object: "hello" as CFString, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + XCTAssertEqual(observer, self) + XCTAssertEqual(center?.centerType, self.centerType) + XCTAssertEqual(name?.rawValue, #function as CFString) + XCTAssertNil(object) + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + object: "hello" as CFString, + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + removeEveryObserver() + } + + override func testObserveNamedWithObjectShouldNotCalled() { + let exp = expectation(description: #function) + + notificationCenter + .addObserver(observer: self, + name: .init("\(#function)" as CFString), + object: "hello-aaa" as CFString, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + object: "hello" as CFString, + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } + + // Custimized + // If center is a Darwin notification center, this value must not be NULL. + override func testObserveNilNamed() { + let exp = expectation(description: #function) + exp.isInverted = true + + notificationCenter + .addObserver(observer: self, + name: nil, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } + + // Custimized + // If center is a Darwin notification center, this value must not be NULL. + override func testObserveNilNamedWithObject() { + let exp = expectation(description: #function) + exp.isInverted = true + + notificationCenter + .addObserver(observer: self, + name: nil, + object: "hello" as CFString, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + object: "hello" as CFString, + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } +} diff --git a/Tests/SCFNotificationTests/SCFDistributedNotificationTests.swift b/Tests/SCFNotificationTests/SCFDistributedNotificationTests.swift new file mode 100644 index 0000000..5fa8f7c --- /dev/null +++ b/Tests/SCFNotificationTests/SCFDistributedNotificationTests.swift @@ -0,0 +1,30 @@ +import XCTest +@testable import SCFNotification + +class SCFDistributedNotificationTests: SCFNotificationTests { + override var centerType: SCFNotificationCenter.CenterType { + .distributed + } + + // Custimized + // For distributed notifications, object must be a CFString object ? + override func testObserveNilNamed() { + let exp = expectation(description: #function) + exp.isInverted = true + + notificationCenter + .addObserver(observer: self, + name: nil, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + exp.fulfill() + } + + notificationCenter.postNotification(name: .init(#function as CFString), + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } +} diff --git a/Tests/SCFNotificationTests/SCFNotificationTests.swift b/Tests/SCFNotificationTests/SCFNotificationTests.swift new file mode 100644 index 0000000..139180a --- /dev/null +++ b/Tests/SCFNotificationTests/SCFNotificationTests.swift @@ -0,0 +1,195 @@ +import XCTest +@testable import SCFNotification + +/// NOTE: +/// When I check the pointer of self in the setUp method, it changes every time. +/// Perhaps each time one test method is called, Xcode makes a copy and continues with the rest of the tests. +/// Therefore, each time one method is called, we call the `removeEveryObserver` method at the end of it. + +class SCFNotificationTests: XCTestCase { + var centerType: SCFNotificationCenter.CenterType { .local } + lazy var notificationCenter: SCFNotificationCenter = .init(center: centerType) + var observationStore: ObservationStore = .shared + + var timeout: TimeInterval { 1 } + + override func setUp() { + super.setUp() + + removeEveryObserver() + } + + func removeEveryObserver() { + observationStore.cleanUp() + + notificationCenter + .removeEveryObserver(observer: self) + + + XCTAssertTrue(observationStore.observations(for: centerType).isEmpty) + } + + func testObserveNamed() { + let exp = expectation(description: #function) + + notificationCenter + .addObserver(observer: self, + name: .init(#function as CFString), + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + XCTAssertEqual(observer, self) + XCTAssertEqual(center?.centerType, self.centerType) + XCTAssertEqual(name?.rawValue, #function as CFString) + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } + + func testObserveNamedShouldNotCalled() { + let exp = expectation(description: #function) + exp.isInverted = true + + notificationCenter + .addObserver(observer: self, + name: .init("\(#function)-aaa" as CFString), + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } + + func testObserveNamedWithObject() { + let exp = expectation(description: #function) + + notificationCenter + .addObserver(observer: self, + name: .init(#function as CFString), + object: "hello" as CFString, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + XCTAssertEqual(observer, self) + XCTAssertEqual(center?.centerType, self.centerType) + XCTAssertEqual(name?.rawValue, #function as CFString) + XCTAssertEqual(object as! CFString, "hello" as CFString) + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + object: "hello" as CFString, + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + removeEveryObserver() + } + + func testObserveNamedWithObjectShouldNotCalled() { + let exp = expectation(description: #function) + exp.isInverted = true + + notificationCenter + .addObserver(observer: self, + name: .init("\(#function)" as CFString), + object: "hello-aaa" as CFString, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + object: "hello" as CFString, + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } + + func testObserveNilNamed() { + let exp = expectation(description: #function) + + notificationCenter + .addObserver(observer: self, + name: nil, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + XCTAssertEqual(observer, self) + XCTAssertEqual(center?.centerType, self.centerType) + XCTAssertEqual(name?.rawValue, #function as CFString) + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } + + func testObserveNilNamedWithObject() { + let exp = expectation(description: #function) + + notificationCenter + .addObserver(observer: self, + name: nil, + object: "hello" as CFString, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + XCTAssertEqual(observer, self) + XCTAssertEqual(center?.centerType, self.centerType) + XCTAssertEqual(name?.rawValue, #function as CFString) + XCTAssertEqual(object as! CFString, "hello" as CFString) + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + object: "hello" as CFString, + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } + + func testObserveNilNamedWithObjectShouldNotCalled() { + let exp = expectation(description: #function) + exp.isInverted = true + + notificationCenter + .addObserver(observer: self, + name: nil, + object: "hello-aaa" as CFString, + suspensionBehavior: .deliverImmediately) { center, observer, name, object, userInfo in + exp.fulfill() + } + + + notificationCenter.postNotification(name: .init(#function as CFString), + object: "hello" as CFString, + userInfo: [:] as CFDictionary, + deliverImmediately: true) + + wait(for: [exp], timeout: timeout) + + removeEveryObserver() + } +}