Skip to content

Commit

Permalink
Added user defaults monitor (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Nyondo authored and eBardX committed May 20, 2018
1 parent 7537c60 commit ede9308
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 28 deletions.
1 change: 1 addition & 0 deletions .jazzy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ custom_categories:
- UbiquitousKeyValueStoreMonitor
- UbiquityIdentityMonitor
- UndoManagerMonitor
- UserDefaultsMonitor
- name: UIKit Accessibility Monitor Classes
children:
- AccessibilityAnnouncementMonitor
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ env:
- DESTINATION="OS=9.0,name=iPhone 6s Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" RUN_TESTS="YES"
- DESTINATION="OS=11.3,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME" RUN_TESTS="YES"
- DESTINATION="arch=x86_64" SCHEME="$OSX_FRAMEWORK_SCHEME" RUN_TESTS="YES"
- DESTINATION="OS=9.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" RUN_TESTS="YES"
- DESTINATION="OS=9.2,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" RUN_TESTS="YES"
- DESTINATION="OS=11.3,name=Apple TV 4K (at 1080p)" SCHEME="$TVOS_FRAMEWORK_SCHEME" RUN_TESTS="YES"
- DESTINATION="OS=2.0,name=Apple Watch - 38mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" RUN_TESTS="NO"
- DESTINATION="OS=4.3,name=Apple Watch Series 2 - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" RUN_TESTS="NO"
Expand Down
12 changes: 7 additions & 5 deletions Documents/wrapped-foundation-notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ Notification Name | Platform(s)
`NSWillBecomeMultiThreaded` | iOS, macOS, tvOS, watchOS | _Not yet implemented_
`Port.didBecomeInvalidNotification` | iOS, macOS, tvOS, watchOS | [PortMonitor][port_monitor]
`ProcessInfo.thermalStateDidChangeNotification` | iOS, macOS, tvOS, watchOS | [ProcessInfoThermalStateMonitor][process_info_thermal_state_monitor]
`UserDefaults.completedInitialCloudSyncNotification` | iOS, tvOS, watchOS | UserDefaultsMonitor (_see_ [#44](https://github.com/eBardX/XestiMonitors/issues/44))
`UserDefaults.didChangeCloudAccountsNotification` | iOS, tvOS, watchOS | UserDefaultsMonitor (_see_ [#44](https://github.com/eBardX/XestiMonitors/issues/44))
`UserDefaults.didChangeNotification` | iOS, macOS, tvOS, watchOS | UserDefaultsMonitor (_see_ [#44](https://github.com/eBardX/XestiMonitors/issues/44))
`UserDefaults.noCloudAccountNotification` | iOS, tvOS, watchOS | UserDefaultsMonitor (_see_ [#44](https://github.com/eBardX/XestiMonitors/issues/44))
`UserDefaults.sizeLimitExceededNotification` | iOS, tvOS, watchOS | UserDefaultsMonitor (_see_ [#44](https://github.com/eBardX/XestiMonitors/issues/44))
`UserDefaults.completedInitialCloudSyncNotification` | iOS, tvOS, watchOS | UbiquitousUserDefaultsMonitor (_see_ [#60](https://github.com/eBardX/XestiMonitors/issues/60))
`UserDefaults.didChangeCloudAccountsNotification` | iOS, tvOS, watchOS | UbiquitousUserDefaultsMonitor (_see_ [#60](https://github.com/eBardX/XestiMonitors/issues/60))
`UserDefaults.didChangeNotification` | iOS, macOS, tvOS, watchOS | [UserDefaultsMonitor][user_defaults_monitor]
`UserDefaults.noCloudAccountNotification` | iOS, tvOS, watchOS | UbiquitousUserDefaultsMonitor (_see_ [#60](https://github.com/eBardX/XestiMonitors/issues/60))
`UserDefaults.sizeLimitExceededNotification` | iOS, tvOS, watchOS | [UserDefaultsMonitor][user_defaults_monitor]

[metadata_query_monitor]: https://eBardX.github.io/XestiMonitors/Classes/MetadataQueryMonitor.html
[port_monitor]: https://eBardX.github.io/XestiMonitors/Classes/PortMonitor.html
Expand All @@ -56,3 +56,5 @@ Notification Name | Platform(s)
[ubiquitous_key_value_store_monitor]: https://eBardX.github.io/XestiMonitors/Classes/UbiquitousKeyValueStoreMonitor.html
[ubiquity_identity_monitor]: https://eBardX.github.io/XestiMonitors/Classes/UbiquityIdentityMonitor.html
[undo_manager_monitor]: https://eBardX.github.io/XestiMonitors/Classes/UndoManagerMonitor.html
[user_defaults_monitor]: https://eBardX.github.io/XestiMonitors/Classes/UserDefaultsMonitor.html

10 changes: 5 additions & 5 deletions Examples/XestiMonitorsDemo-iOS/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PODS:
- XestiMonitors (2.9.0):
- XestiMonitors/Core (= 2.9.0)
- XestiMonitors/Core (2.9.0)
- XestiMonitors (2.10.0):
- XestiMonitors/Core (= 2.10.0)
- XestiMonitors/Core (2.10.0)

DEPENDENCIES:
- XestiMonitors (from `../../`)
Expand All @@ -11,8 +11,8 @@ EXTERNAL SOURCES:
:path: "../../"

SPEC CHECKSUMS:
XestiMonitors: 0be4511e7510139d4810f6cf688df325e8008490
XestiMonitors: 2e15b417857e0e31c46651efc40d667b453587e3

PODFILE CHECKSUM: af967f85b3de0f1060583f21837d6381de340343

COCOAPODS: 1.5.0
COCOAPODS: 1.5.2
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,4 @@ public class OtherViewController: UITableViewController {

super.viewWillDisappear(animated)
}

// MARK: - UITextFieldDelegate

public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.text = ""

if textField.isFirstResponder {
textField.resignFirstResponder()
}

return false
}
}
10 changes: 5 additions & 5 deletions Examples/XestiMonitorsDemo-tvOS/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PODS:
- XestiMonitors (2.9.0):
- XestiMonitors/Core (= 2.9.0)
- XestiMonitors/Core (2.9.0)
- XestiMonitors (2.10.0):
- XestiMonitors/Core (= 2.10.0)
- XestiMonitors/Core (2.10.0)

DEPENDENCIES:
- XestiMonitors (from `../../`)
Expand All @@ -11,8 +11,8 @@ EXTERNAL SOURCES:
:path: "../../"

SPEC CHECKSUMS:
XestiMonitors: 0be4511e7510139d4810f6cf688df325e8008490
XestiMonitors: 2e15b417857e0e31c46651efc40d667b453587e3

PODFILE CHECKSUM: 1126d3f650bae5d09bc018cdd9097411c8d68f2e

COCOAPODS: 1.5.0
COCOAPODS: 1.5.2
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ XestiMonitors provides eight monitors wrapping `Foundation`
for changes to the iCloud (”ubiquity”) identity. *(iOS, macOS, tvOS, watchOS)*
* [UndoManagerMonitor][undo_manager_monitor] to monitor an undo manager for
changes to its recording of operations. *(iOS, macOS, tvOS, watchOS)*
* [UserDefaultsMonitor][user_defaults_monitor] to monitor a user defaults
object for changes to its data. *(iOS, macOS, tvOS, watchOS)*

### <a name="uikit_monitors">UIKit Monitors</a>

Expand Down Expand Up @@ -632,6 +634,7 @@ XestiMonitors is available under [the MIT license][license].
[pasteboard_monitor]: https://eBardX.github.io/XestiMonitors/Classes/PasteboardMonitor.html
[pedometer_monitor]: https://eBardX.github.io/XestiMonitors/Classes/PedometerMonitor.html
[port_monitor]: https://eBardX.github.io/XestiMonitors/Classes/PortMonitor.html
[process_info_power_state_monitor]: https://eBardX.github.io/XestiMonitors/Classes/ProcessInfoPowerStateMonitor.html
[process_info_thermal_state_monitor]: https://eBardX.github.io/XestiMonitors/Classes/ProcessInfoThermalStateMonitor.html
[protected_data_monitor]: https://eBardX.github.io/XestiMonitors/Classes/ProtectedDataMonitor.html
[proximity_monitor]: https://eBardX.github.io/XestiMonitors/Classes/ProximityMonitor.html
Expand All @@ -654,6 +657,7 @@ XestiMonitors is available under [the MIT license][license].
[ubiquitous_key_value_store_monitor]: https://eBardX.github.io/XestiMonitors/Classes/UbiquitousKeyValueStoreMonitor.html
[ubiquity_identity_monitor]: https://eBardX.github.io/XestiMonitors/Classes/UbiquityIdentityMonitor.html
[undo_manager_monitor]: https://eBardX.github.io/XestiMonitors/Classes/UndoManagerMonitor.html
[user_defaults_monitor]: https://eBardX.github.io/XestiMonitors/Classes/UserDefaultsMonitor.html
[view_controller_show_detail_target_monitor]: https://eBardX.github.io/XestiMonitors/Classes/ViewControllerShowDetailTargetMonitor.html
[visit_monitor]: https://eBardX.github.io/XestiMonitors/Classes/VisitMonitor.html
[window_monitor]: https://eBardX.github.io/XestiMonitors/Classes/WindowMonitor.html
Expand Down
152 changes: 152 additions & 0 deletions Sources/Core/Foundation/UserDefaultsMonitor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//
// UserDefaultsMonitor.swift
// XestiMonitors
//
// Created by Paul Nyondo on 2018-04-25.
//
// © 2018 J. G. Pusey (see LICENSE.md)
//

import Foundation

///
/// A `UserDefaultsMonitor` instance monitors a user defaults object for
/// changes to its data.
///
public class UserDefaultsMonitor: BaseNotificationMonitor {
///
/// Encapsulates changes to the user defaults object.
///
public enum Event {
///
/// The user defaults object has been changed within the current
/// process.
///
case didChange(UserDefaults)

#if os(iOS) || os(tvOS) || os(watchOS)
///
/// More data has been stored in the user defaults object than is
/// allowed.
///
case sizeLimitExceeded(UserDefaults)
#endif
}

#if os(iOS) || os(tvOS) || os(watchOS)
///
/// Specifies which events to monitor.
///
public struct Options: OptionSet {
///
/// Monitor `didChange` events.
///
public static let didChange = Options(rawValue: 1 << 0)

///
/// Monitor `sizeLimitExceeded` events.
///
public static let sizeLimitExceeded = Options(rawValue: 1 << 1)

///
/// Monitor `all` events.
///
public static let all: Options = [.didChange,
.sizeLimitExceeded]
/// :nodoc:
public init(rawValue: UInt) {
self.rawValue = rawValue
}

/// :nodoc:
public let rawValue: UInt
}
#endif

#if os(iOS) || os(tvOS) || os(watchOS)
///
/// Initializes a new `UserDefaultsMonitor`.
///
/// - Parameters:
/// - userDefaults: The user defaults object to monitor.
/// - options: The options that specify which events to monitor.
/// By default, all events are monitored.
/// - queue: The operation queue on which the handler executes.
/// By default, the main operation queue is used.
/// - handler: The handler to call when the user defaults object
/// is changed within the current process or more data
/// is stored in the object than is allowed.
///
public init(userDefaults: UserDefaults,
options: Options = .all,
queue: OperationQueue = .main,
handler: @escaping (Event) -> Void) {
self.options = options
self.handler = handler
self.userDefaults = userDefaults

super.init(queue: queue)
}
#else
///
/// Initializes a new `UserDefaultsMonitor`.
///
/// - Parameters:
/// - userDefaults: The user defaults object to monitor.
/// - queue: The operation queue on which the handler executes.
/// By default, the main operation queue is used.
/// - handler: The handler to call when the user defaults object
/// is changed within the current process.
///
public init(userDefaults: UserDefaults,
queue: OperationQueue = .main,
handler: @escaping (Event) -> Void) {
self.handler = handler
self.userDefaults = userDefaults

super.init(queue: queue)
}
#endif

///
/// The user defaults object being monitored.
///
public let userDefaults: UserDefaults

private let handler: (Event) -> Void
#if os(iOS) || os(tvOS) || os(watchOS)
private let options: Options
#endif

public override func addNotificationObservers() {
super.addNotificationObservers()

#if os(iOS) || os(tvOS) || os(watchOS)
if options.contains(.didChange) {
observe(UserDefaults.didChangeNotification,
object: userDefaults) { [unowned self] in
if let userDefaults = $0.object as? UserDefaults {
self.handler(.didChange(userDefaults))
}
}
}

if options.contains(.sizeLimitExceeded),
#available(iOS 9.3, *) {
observe(UserDefaults.sizeLimitExceededNotification,
object: userDefaults) { [unowned self] in
if let userDefaults = $0.object as? UserDefaults {
self.handler(.sizeLimitExceeded(userDefaults))
}
}
}
#else
observe(UserDefaults.didChangeNotification,
object: userDefaults) { [unowned self] in
if let userDefaults = $0.object as? UserDefaults {
self.handler(.didChange(userDefaults))
}
}
#endif
}
}
93 changes: 93 additions & 0 deletions Tests/Foundation/UserDefaultsMonitorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// UserDefaultsMonitorTests.swift
// XestiMonitors
//
// Created by Paul Nyondo on 2018-04-25.
//
// © 2018 J. G. Pusey (see LICENSE.md)
//

import XCTest
@testable import XestiMonitors

internal class UserDefaultsMonitorTests: XCTestCase {
let notificationCenter = MockNotificationCenter()
let userDefaults = UserDefaults()

override func setUp() {
super.setUp()

NotificationCenterInjector.inject = { return self.notificationCenter }
}

func testMonitor_didChange() {
let expectation = self.expectation(description: "Handler called")
var expectedEvent: UserDefaultsMonitor.Event?
#if os(iOS) || os(tvOS) || os(watchOS)
let monitor = UserDefaultsMonitor(userDefaults: userDefaults,
options: .didChange,
queue: .main) { event in
expectedEvent = event
expectation.fulfill()
}
#else
let monitor = UserDefaultsMonitor(userDefaults: userDefaults,
queue: .main) { event in
expectedEvent = event
expectation.fulfill()
}
#endif

monitor.startMonitoring()
simulateDidChange()
waitForExpectations(timeout: 1)
monitor.stopMonitoring()

if let event = expectedEvent,
case let .didChange(test) = event {
XCTAssertEqual(test, userDefaults)
} else {
XCTFail("Unexpected event")
}
}

#if os(iOS) || os(tvOS) || os(watchOS)
func testMonitor_sizeLimitExceeded() {
if #available(iOS 9.3, *) {
let expectation = self.expectation(description: "Handler called")
var expectedEvent: UserDefaultsMonitor.Event?
let monitor = UserDefaultsMonitor(userDefaults: userDefaults,
options: .sizeLimitExceeded,
queue: .main) { event in
expectedEvent = event
expectation.fulfill()
}

monitor.startMonitoring()
simulateSizeLimitExceeded()
waitForExpectations(timeout: 1)
monitor.stopMonitoring()

if let event = expectedEvent,
case let .sizeLimitExceeded(test) = event {
XCTAssertEqual(test, userDefaults)
} else {
XCTFail("Unexpected event")
}
}
}
#endif

private func simulateDidChange() {
notificationCenter.post(name: UserDefaults.didChangeNotification,
object: userDefaults)
}

#if os(iOS) || os(tvOS) || os(watchOS)
@available(iOS 9.3, *)
private func simulateSizeLimitExceeded() {
notificationCenter.post(name: UserDefaults.sizeLimitExceededNotification,
object: userDefaults)
}
#endif
}
Loading

0 comments on commit ede9308

Please sign in to comment.