diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 2a22e9e..6461b89 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -25,7 +25,6 @@ jobs:
run: |
brew update
brew outdated xctool || brew upgrade xctool
- brew install swiftlint
- name: Test iOS
run: |
xcodebuild clean build test -project $PROJECT -scheme $SCHEME -destination "$DESTINATION" | XCPRETTY_JSON_FILE_OUTPUT="xcodebuild-ios.json" xcpretty -f `xcpretty-json-formatter`
@@ -57,15 +56,6 @@ jobs:
SCHEME: UserDefaultsStore
DESTINATION: name=Apple Watch Series 5 - 40mm
- Swiftlint:
- runs-on: [macos]
- name: SwiftLint
- steps:
- - uses: actions/checkout@v1
- - name: SwiftLint
- run: |
- brew install swiftlint
- swiftlint
CocoaPods:
name: CocoaPods
runs-on: macos-latest
diff --git a/Package.swift b/Package.swift
index 8c66568..88572d6 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,16 +1,44 @@
-// swift-tools-version:5.0
-// The swift-tools-version declares the minimum version of Swift required to build this package.
+// swift-tools-version:5.1
+//
+// SingleUserDefaultsStore
+//
+// Copyright (c) 2018-Present Omar Albeik - https://github.com/omaralbeik
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
import PackageDescription
let package = Package(
name: "UserDefaultsStore",
+ platforms: [
+ .iOS(.v13),
+ .macOS(.v10_15),
+ .tvOS(.v13),
+ .watchOS(.v6)
+ ],
products: [
.library(name: "UserDefaultsStore", targets: ["UserDefaultsStore"])
],
dependencies: [],
targets: [
.target(name: "UserDefaultsStore", dependencies: [], path: "Sources"),
- .testTarget(name: "UserDefaultsStoreTests", dependencies: ["UserDefaultsStore"], path: "Tests"),
- ]
+ .testTarget(name: "UserDefaultsStoreTests", dependencies: ["UserDefaultsStore"], path: "Tests")
+ ],
+ swiftLanguageVersions: [.v5]
)
diff --git a/README.md b/README.md
index c5ab789..5425473 100644
--- a/README.md
+++ b/README.md
@@ -17,27 +17,22 @@
# tl;dr
You love Swift's `Codable` protocol and use it everywhere, who doesn't! Here is an easy and very light way to store and retrieve -**reasonable amount 😅**- of `Codable` objects, in a couple lines of code!
+---
-## Installation
+## Introducing v2.0
-
-CocoaPods (Recommended)
-
-To integrate UserDefaultsStore into your Xcode project using CocoaPods, specify it in your Podfile
:
-pod 'UserDefaultsStore'
-
+- Removed the `Identifiable` protocol in favor of Swift's `Identifiable`.
+- Increased deployment targets to iOS `13.0`, `tvOS 13.0`, `macOS 10.15`, and `watchOS 6.0`.
+- Objects defined as non-final classes can now be used as well.
+- Added new `generateSnapshot()` and `restoreSnapshot(_:)` methods to generate and restore a `Snapshot` object that can be saved (e.g. to iCloud) and restored later.
+- Fixed a bug where `objectsCount` might run out of sync with the actual count of objects in store.
-
-Carthage
-
-To integrate UserDefaultsStore into your Xcode project using Carthage, specify it in your Cartfile
:
+---
-github "omaralbeik/UserDefaultsStore" ~> 1.5.0
-
-
+## Installation
-Swift Package Manager
+Swift Package Manager (Recommended)
You can use The Swift Package Manager to install UserDefaultsStore
by adding the proper description to your Package.swift
file:
@@ -47,7 +42,7 @@ let package = Package(
name: "YOUR_PROJECT_NAME",
targets: [],
dependencies: [
- .package(url: "https://github.com/omaralbeik/UserDefaultsStore.git", from: "1.5.0")
+ .package(url: "https://github.com/omaralbeik/UserDefaultsStore.git", from: "2.0.0")
]
)
@@ -62,6 +57,23 @@ let package = Package(
Then run swift package update
.
+
+
+CocoaPods
+
+To integrate UserDefaultsStore into your Xcode project using CocoaPods, specify it in your Podfile
:
+pod 'UserDefaultsStore'
+
+
+
+Carthage
+
+To integrate UserDefaultsStore into your Xcode project using Carthage, specify it in your Cartfile
:
+
+github "omaralbeik/UserDefaultsStore" ~> 2.0.0
+
+
+
Manually
@@ -92,39 +104,35 @@ struct Laptop: Codable {
Here is how you store them in **UserDefaultsStore**:
-### 1. Conform to the `Identifiable` protocol and set the `idKey` property
+### 1. Conform to the `Identifiable` protocol and set the `id` property
The `Identifiable` protocol lets UserDefaultsStore knows what is the unique id for each object.
```swift
struct User: Codable, Identifiable {
- static let idKey = \User.id
...
}
```
```swift
struct Laptop: Codable, Identifiable {
- static let idKey = \Laptop.model
+ var id: String { model }
...
}
```
-> Notice how `User` uses `Int` for its id, while `Laptop` uses `String`, in fact the id can be any `Hashable` type. UserDefaults uses Swift keypaths to refer to properties without actually invoking them. Swift rocks 🤘
-
-
### 2. Create UserDefaults Stores
```swift
-let usersStore = UserDefaultsStore(uniqueIdentifier: "users")!
-let laptopsStore = UserDefaultsStore(uniqueIdentifier: "laptops")!
+let usersStore = UserDefaultsStore(uniqueIdentifier: "users")
+let laptopsStore = UserDefaultsStore(uniqueIdentifier: "laptops")
```
### 3. Voilà , you're all set!
```swift
let macbook = Laptop(model: "A1278", name: "MacBook Pro")
-let john = User(userId: 1, firstName: "John", lastName: "Appleseed", laptop: macbook)
+let john = User(id: 1, firstName: "John", lastName: "Appleseed", laptop: macbook)
// Save an object to a store
try! usersStore.save(john)
@@ -156,21 +164,21 @@ laptops.deleteAll()
// Know how many objects are stored in a store
let usersCount = usersStore.objectsCount
-```
+// Create a snapshot
+let snapshot = usersStore.generateSnapshot()
+// Restore a pre-generated snapshot
+try? usersStore.restoreSnapshot(snapshot)
+```
## Looking to store a single item only?
Use [`SingleUserDefaultsStore`](https://github.com/omaralbeik/UserDefaultsStore/blob/master/Sources/SingleUserDefaultsStore.swift), it enables storing and retrieving a single value of `Int`, `Double`, `String`, or any `Codable` type.
-## Note about using `class` instead of `struct`
-At the moment, only `final` classes are supported, please take this into consideration before using the library.
-
## Requirements
-- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
-- Xcode 10.0+
-- Swift 4.2+
+- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
+- Swift 5.0+
## Thanks
diff --git a/Sources/Identifiable.swift b/Sources/Identifiable.swift
deleted file mode 100644
index 985c442..0000000
--- a/Sources/Identifiable.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// UserDefaultsStore
-//
-// Copyright (c) 2018-Present Omar Albeik - https://github.com/omaralbeik
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-/// Conform to `Identifiable` protocol in uniquely identified objects you want to store in a `UserDefaultsStore`.
-public protocol Identifiable {
-
- /// ID type.
- associatedtype ID
-
- /// Id Key.
- static var idKey: WritableKeyPath { get }
-
-}
diff --git a/Sources/SingleUserDefaultsStore.swift b/Sources/SingleUserDefaultsStore.swift
index 01d58d1..7c17fb3 100644
--- a/Sources/SingleUserDefaultsStore.swift
+++ b/Sources/SingleUserDefaultsStore.swift
@@ -24,7 +24,24 @@
import Foundation
/// `SingleUserDefaultsStore` offers a convenient way to store a single `Codable` object in `UserDefaults`.
-open class SingleUserDefaultsStore {
+open class SingleUserDefaultsStore {
+ /// Used to backup and restore content to store.
+ public struct Snapshot: Codable {
+ /// Object.
+ public let object: Object?
+
+ /// Date when the snapshot was created.
+ public let dateCreated: Date
+
+ /// Create a new `Snapshot`.
+ /// - Parameters:
+ /// - object: Object to include in the snapshot.
+ /// - dateCreated: Date when the snapshot was created.
+ public init(object: Object?, dateCreated: Date) {
+ self.object = object
+ self.dateCreated = dateCreated
+ }
+ }
/// Store's unique identifier.
///
@@ -38,21 +55,23 @@ open class SingleUserDefaultsStore {
open var decoder: JSONDecoder
/// UserDefaults store.
- private var store: UserDefaults
+ private let store: UserDefaults
- /// Initialize store with given identifier.
+ /// Initialize store with given identifier. _O(1)_
///
/// **Warning**: Never use the same identifier for two -or more- different stores.
///
/// - Parameter uniqueIdentifier: store's unique identifier.
/// - Parameter encoder: JSON encoder to be used for encoding object to be stored. _default is `JSONEncoder()`_
/// - Parameter decoder: JSON decoder to be used to decode the stored object. _default is `JSONDecoder()`_
- required public init?(
+ required public init(
uniqueIdentifier: String,
encoder: JSONEncoder = .init(),
decoder: JSONDecoder = .init()
) {
- guard let store = UserDefaults(suiteName: uniqueIdentifier) else { return nil }
+ guard let store = UserDefaults(suiteName: uniqueIdentifier) else {
+ fatalError("Can not create a store with identifier: '\(uniqueIdentifier)'.")
+ }
self.uniqueIdentifier = uniqueIdentifier
self.encoder = encoder
self.decoder = decoder
@@ -63,34 +82,85 @@ open class SingleUserDefaultsStore {
///
/// - Parameter object: object to save.
/// - Throws: JSON encoding error.
- public func save(_ object: T) throws {
+ public func save(_ object: Object) throws {
let data = try encoder.encode(generateDict(for: object))
store.set(data, forKey: key)
}
/// Get object from store. _O(1)_
- public var object: T? {
+ public var object: Object? {
guard let data = store.data(forKey: key) else { return nil }
- guard let dict = try? decoder.decode([String: T].self, from: data) else { return nil }
+ guard let dict = try? decoder.decode([String: Object].self, from: data) else { return nil }
return extractObject(from: dict)
}
/// Delete object from store. _O(1)_
public func delete() {
- store.set(nil, forKey: key)
+ store.removePersistentDomain(forName: uniqueIdentifier)
store.removeSuite(named: uniqueIdentifier)
}
+ /// Generate a snapshot that can be saved and restored later. _O(1)_
+ /// - Returns: `Snapshot` object representing current contents of the store.
+ public func generateSnapshot() -> Snapshot {
+ let now = Date()
+ store.setValue(now, forKey: lastSnapshotDateKey)
+ return Snapshot(object: object, dateCreated: now)
+ }
+
+ /// Restore a pre-generated `Snapshot`. _O(1)_
+ /// - Parameter snapshot: `Snapshot` to restore.
+ /// - Throws: JSON encoding/decoding error.
+ public func restoreSnapshot(_ snapshot: Snapshot) throws {
+ let now = Date()
+ guard let object = snapshot.object else {
+ delete()
+ store.setValue(now, forKey: lastRestoreDateKey)
+ return
+ }
+
+ let current = self.object
+ delete()
+ do {
+ try save(object)
+ store.setValue(now, forKey: lastRestoreDateKey)
+ } catch {
+ if let current = current {
+ try save(current)
+ }
+ throw error
+ }
+ }
+
+ /// Date when the last `Snapshot` was generated.
+ public var lastSnapshotDate: Date? {
+ return store.value(forKey: lastSnapshotDateKey) as? Date
+ }
+
+ /// Date when the last `Snapshot` was successfully restored.
+ public var lastRestoreDate: Date? {
+ return store.value(forKey: lastRestoreDateKey) as? Date
+ }
+}
+
+extension SingleUserDefaultsStore.Snapshot: Equatable where Object: Equatable {
+ /// Returns a Boolean value indicating whether two snapshots are equal.
+ ///
+ /// - Parameters:
+ /// - lhs: A `Snapshot` object to compare.
+ /// - rhs: Another `Snapshot` object to compare.
+ public static func == (lhs: Self, rhs: Self) -> Bool {
+ return lhs.object == rhs.object && lhs.dateCreated == rhs.dateCreated
+ }
}
// MARK: - Helpers
private extension SingleUserDefaultsStore {
-
/// Enclose the object in a dictionary to enable single object storing.
///
/// - Parameter object: object.
/// - Returns: dictionary enclosing object.
- func generateDict(for object: T) -> [String: T] {
+ func generateDict(for object: Object) -> [String: Object] {
return ["object": object]
}
@@ -98,7 +168,7 @@ private extension SingleUserDefaultsStore {
///
/// - Parameter dict: dictionary.
/// - Returns: object.
- func extractObject(from dict: [String: T]) -> T? {
+ func extractObject(from dict: [String: Object]) -> Object? {
return dict["object"]
}
@@ -107,4 +177,13 @@ private extension SingleUserDefaultsStore {
return "\(uniqueIdentifier)-single-object"
}
+ /// last snapshot date key.
+ var lastSnapshotDateKey: String {
+ return "\(uniqueIdentifier)-single-object-last-snapshot-date"
+ }
+
+ /// last restore date key.
+ var lastRestoreDateKey: String {
+ return "\(uniqueIdentifier)-single-object-last-restore-date"
+ }
}
diff --git a/Sources/UserDefaultsStore.h b/Sources/UserDefaultsStore.h
deleted file mode 100644
index 41c9806..0000000
--- a/Sources/UserDefaultsStore.h
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// UserDefaultsStore
-//
-// Copyright (c) 2018-Present Omar Albeik - https://github.com/omaralbeik
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-#import
-
-FOUNDATION_EXPORT double UserDefaultsStoreVersionNumber;
-FOUNDATION_EXPORT const unsigned char UserDefaultsStoreVersionString[];
diff --git a/Sources/UserDefaultsStore.swift b/Sources/UserDefaultsStore.swift
index dac0aff..95bb23b 100644
--- a/Sources/UserDefaultsStore.swift
+++ b/Sources/UserDefaultsStore.swift
@@ -24,7 +24,24 @@
import Foundation
/// `UserDefaultsStore` offers a convenient way to store a collection of `Codable` objects in `UserDefaults`.
-open class UserDefaultsStore {
+open class UserDefaultsStore {
+ /// Used to backup and restore content to store.
+ public struct Snapshot: Codable {
+ /// Array of objects.
+ public let objects: [Object]
+
+ /// Date when the snapshot was created.
+ public let dateCreated: Date
+
+ /// Create a new `Snapshot`.
+ /// - Parameters:
+ /// - object: Array of objects to include in the snapshot.
+ /// - dateCreated: Date when the snapshot was created.
+ public init(objects: [Object], dateCreated: Date) {
+ self.objects = objects
+ self.dateCreated = dateCreated
+ }
+ }
/// Store's unique identifier.
///
@@ -38,21 +55,23 @@ open class UserDefaultsStore {
open var decoder: JSONDecoder
/// UserDefaults store.
- private var store: UserDefaults
+ private let store: UserDefaults
- /// Initialize store with given identifier.
+ /// Initialize store with given identifier. _O(1)_
///
/// **Warning**: Never use the same identifier for two -or more- different stores.
///
/// - Parameter uniqueIdentifier: store's unique identifier.
/// - Parameter encoder: JSON encoder to be used for encoding objects to be stored. _default is `JSONEncoder()`_
/// - Parameter decoder: JSON decoder to be used to decode stored objects. _default is `JSONDecoder()`_
- required public init?(
+ required public init(
uniqueIdentifier: String,
encoder: JSONEncoder = .init(),
decoder: JSONDecoder = .init()
) {
- guard let store = UserDefaults(suiteName: uniqueIdentifier) else { return nil }
+ guard let store = UserDefaults(suiteName: uniqueIdentifier) else {
+ fatalError("Can not create a store with identifier: '\(uniqueIdentifier)'.")
+ }
self.uniqueIdentifier = uniqueIdentifier
self.encoder = encoder
self.decoder = decoder
@@ -63,7 +82,7 @@ open class UserDefaultsStore {
///
/// - Parameter object: object to save.
/// - Throws: JSON encoding error.
- public func save(_ object: T) throws {
+ public func save(_ object: Object) throws {
let data = try encoder.encode(object)
store.set(data, forKey: key(for: object))
increaseCounter()
@@ -73,7 +92,7 @@ open class UserDefaultsStore {
///
/// - Parameter optionalObject: optional object to save.
/// - Throws: JSON encoding error.
- public func save(_ optionalObject: T?) throws {
+ public func save(_ optionalObject: Object?) throws {
guard let object = optionalObject else { return }
try save(object)
}
@@ -82,48 +101,48 @@ open class UserDefaultsStore {
///
/// - Parameter objects: object to save.
/// - Throws: JSON encoding error.
- public func save(_ objects: [T]) throws {
+ public func save(_ objects: [Object]) throws {
let pairs = try objects.map({ (key: key(for: $0), data: try encoder.encode($0)) })
- for pair in pairs {
+ pairs.forEach { pair in
store.set(pair.data, forKey: pair.key)
+ increaseCounter()
}
- increaseCounter(by: pairs.count)
}
/// Get object from store by its id. _O(1)_
///
/// - Parameter id: object id.
/// - Returns: optional object.
- public func object(withId id: T.ID) -> T? {
+ public func object(withId id: Object.ID) -> Object? {
guard let data = store.data(forKey: key(for: id)) else { return nil }
- return try? decoder.decode(T.self, from: data)
+ return try? decoder.decode(Object.self, from: data)
}
/// Get array of objects from store for array of m id values. _O(m)_
///
/// - Parameter ids: array of ids.
/// - Returns: array of objects with the given ids.
- public func objects(withIds ids: [T.ID]) -> [T] {
+ public func objects(withIds ids: [Object.ID]) -> [Object] {
return ids.compactMap { object(withId: $0) }
}
/// Get all objects from store. _O(n)_
///
/// - Returns: array of all objects in store.
- public func allObjects() -> [T] {
+ public func allObjects() -> [Object] {
guard objectsCount > 0 else { return [] }
- return store.dictionaryRepresentation().keys.compactMap { key -> T? in
+ return store.dictionaryRepresentation().keys.compactMap { key -> Object? in
guard isObjectKey(key) else { return nil }
guard let data = store.data(forKey: key) else { return nil }
- return try? decoder.decode(T.self, from: data)
+ return try? decoder.decode(Object.self, from: data)
}
}
/// Delete object by its id from store. _O(1)_
///
/// - Parameter id: object id.
- public func delete(withId id: T.ID) {
+ public func delete(withId id: Object.ID) {
guard hasObject(withId: id) else { return }
store.removeObject(forKey: key(for: id))
decreaseCounter()
@@ -132,13 +151,14 @@ open class UserDefaultsStore {
/// Delete objects with ids from given m ids array. _O(m)_
///
/// - Parameter ids: array of ids.
- public func delete(withIds ids: [T.ID]) {
+ public func delete(withIds ids: [Object.ID]) {
ids.forEach { delete(withId: $0) }
}
/// Delete all objects in store. _O(1)_
public func deleteAll() {
store.removePersistentDomain(forName: uniqueIdentifier)
+ store.removeSuite(named: uniqueIdentifier)
}
/// Count of all objects in store. _O(1)_
@@ -150,59 +170,116 @@ open class UserDefaultsStore {
///
/// - Parameter id: object id to check for.
/// - Returns: true if the store has an object with the given id.
- public func hasObject(withId id: T.ID) -> Bool {
+ public func hasObject(withId id: Object.ID) -> Bool {
return object(withId: id) != nil
}
/// Iterate over all objects in store. _O(n)_
///
/// - Parameter object: iteration block.
- public func forEach(_ object: (T) -> Void) {
+ public func forEach(_ object: (Object) -> Void) {
allObjects().forEach { object($0) }
}
+ /// Generate a snapshot that can be saved and restored later. _O(n)_
+ /// - Returns: `Snapshot` object representing current contents of the store.
+ public func generateSnapshot() -> Snapshot {
+ let now = Date()
+ store.setValue(now, forKey: lastSnapshotDateKey)
+ return Snapshot(objects: allObjects(), dateCreated: now)
+ }
+
+ /// Restore a pre-generated `Snapshot`. _O(n)_
+ /// - Parameter snapshot: `Snapshot` to restore.
+ /// - Throws: JSON encoding/decoding error.
+ public func restoreSnapshot(_ snapshot: Snapshot) throws {
+ let now = Date()
+ guard !snapshot.objects.isEmpty else {
+ deleteAll()
+ store.setValue(now, forKey: lastRestoreDateKey)
+ return
+ }
+
+ let current = allObjects()
+ deleteAll()
+ do {
+ try save(snapshot.objects)
+ store.setValue(now, forKey: lastRestoreDateKey)
+ } catch {
+ try save(current)
+ throw error
+ }
+ }
+
+ /// Date when the last `Snapshot` was generated.
+ public var lastSnapshotDate: Date? {
+ return store.value(forKey: lastSnapshotDateKey) as? Date
+ }
+
+ /// Date when the last `Snapshot` was successfully restored.
+ public var lastRestoreDate: Date? {
+ return store.value(forKey: lastRestoreDateKey) as? Date
+ }
+}
+
+extension UserDefaultsStore.Snapshot: Equatable where Object: Equatable {
+ /// Returns a Boolean value indicating whether two snapshots are equal.
+ ///
+ /// - Parameters:
+ /// - lhs: A `Snapshot` object to compare.
+ /// - rhs: Another `Snapshot` object to compare.
+ public static func == (lhs: Self, rhs: Self) -> Bool {
+ return lhs.objects == rhs.objects && lhs.dateCreated == rhs.dateCreated
+ }
}
// MARK: - Helpers
private extension UserDefaultsStore {
-
/// Increase objects count counter.
- func increaseCounter(by count: Int = 1) {
+ func increaseCounter() {
let currentCount = store.integer(forKey: counterKey)
- store.set(currentCount + count, forKey: counterKey)
+ store.set(currentCount + 1, forKey: counterKey)
}
/// Decrease objects count counter.
- func decreaseCounter(by count: Int = 1) {
+ func decreaseCounter() {
let currentCount = store.integer(forKey: counterKey)
guard currentCount > 0 else { return }
- guard currentCount - count >= 0 else { return }
- store.set(currentCount - count, forKey: counterKey)
+ guard currentCount - 1 >= 0 else { return }
+ store.set(currentCount - 1, forKey: counterKey)
}
-
}
// MARK: - Keys
private extension UserDefaultsStore {
-
/// counter key.
var counterKey: String {
return "\(uniqueIdentifier)-count"
}
+ /// last snapshot date key.
+ var lastSnapshotDateKey: String {
+ return "\(uniqueIdentifier)-last-snapshot-date"
+ }
+
+ /// last restore date key.
+ var lastRestoreDateKey: String {
+ return "\(uniqueIdentifier)-last-restore-date"
+ }
+
/// store key for object.
///
/// - Parameter object: object.
/// - Returns: UserDefaults key for given object.
- func key(for object: T) -> String {
- return "\(uniqueIdentifier)-\(object[keyPath: T.idKey])"
+ func key(for object: Object) -> String {
+ return "\(uniqueIdentifier)-\(object.id)"
}
/// store key for object by its id.
///
/// - Parameter id: object id.
/// - Returns: UserDefaults key for given id.
- func key(for id: T.ID) -> String {
+ func key(for id: Object.ID) -> String {
return "\(uniqueIdentifier)-\(id)"
}
@@ -213,5 +290,4 @@ private extension UserDefaultsStore {
func isObjectKey(_ key: String) -> Bool {
return key.starts(with: "\(uniqueIdentifier)-")
}
-
}
diff --git a/Tests/SingleStoreTests.swift b/Tests/SingleStoreTests.swift
index 9782129..9b065af 100644
--- a/Tests/SingleStoreTests.swift
+++ b/Tests/SingleStoreTests.swift
@@ -25,29 +25,20 @@ import XCTest
@testable import UserDefaultsStore
final class SingleStoreTests: XCTestCase {
-
- func testCreateStore() {
- let store = createFreshUsersStore()
- XCTAssertNotNil(store)
- }
+ private typealias Snapshot = SingleUserDefaultsStore.Snapshot
func testCreateStoreWithCustomEncoderAndDecoder() {
let encoder = JSONEncoder()
let decoder = JSONDecoder()
- let store = createFreshUsersStore(encoder: encoder, decoder: decoder)
+ let store = createFreshUserStore(encoder: encoder, decoder: decoder)
XCTAssertNotNil(store)
- XCTAssert(store?.encoder === encoder)
- XCTAssert(store?.decoder === decoder)
- }
-
- func testCreateInvalidStore() {
- let invalidStore = SingleUserDefaultsStore(uniqueIdentifier: UserDefaults.globalDomain)
- XCTAssertNil(invalidStore)
+ XCTAssert(store.encoder === encoder)
+ XCTAssert(store.decoder === decoder)
}
func testSaveObject() {
- let store = createFreshUsersStore()!
+ let store = createFreshUserStore()
XCTAssertNoThrow(try store.save(TestUser.john))
XCTAssertNotNil(store.object)
@@ -55,32 +46,96 @@ final class SingleStoreTests: XCTestCase {
}
func testSaveInvalidObject() {
- let store = createFreshUsersStore()!
+ let store = createFreshUserStore()
XCTAssertThrowsError(try store.save(TestUser.invalid))
}
func testObject() {
- let store = createFreshUsersStore()!
+ let store = createFreshUserStore()
XCTAssertNoThrow(try store.save(TestUser.johnson))
XCTAssertNotNil(store.object)
}
+ func testGenerateSnapshot() {
+ let store = createFreshUserStore()
+
+ XCTAssertNoThrow(try store.save(TestUser.john))
+
+ XCTAssertNil(store.lastSnapshotDate)
+
+ let snapshot = store.generateSnapshot()
+
+ XCTAssertEqual(snapshot.object, TestUser.john)
+ XCTAssertNotNil(store.lastSnapshotDate)
+ XCTAssertEqual(store.lastSnapshotDate, snapshot.dateCreated)
+ }
+
+ func testRestoreSnapshot() {
+ var store = createFreshUserStore()
+
+ XCTAssertNoThrow(try store.save(TestUser.john))
+
+ let snapshot = store.generateSnapshot()
+
+ store = createFreshUserStore()
+
+ XCTAssertNil(store.lastRestoreDate)
+ XCTAssertNoThrow(try store.restoreSnapshot(snapshot))
+ XCTAssertNotNil(store.lastRestoreDate)
+ XCTAssertEqual(store.object, TestUser.john)
+ }
+
+ func testRestoreEmptySnapshot() {
+ let store = createFreshUserStore()
+
+ XCTAssertNoThrow(try store.save(TestUser.john))
+
+ let snapshot = SingleUserDefaultsStore.Snapshot(object: nil, dateCreated: Date())
+
+ XCTAssertNoThrow(try store.restoreSnapshot(snapshot))
+
+ XCTAssertNotNil(store.lastRestoreDate)
+ XCTAssertNil(store.object)
+ }
+
+ func testRestoreSnapshotWithInvalidObjects() {
+ let store = createFreshUserStore()
+
+ XCTAssertNoThrow(try store.save(TestUser.john))
+
+ let snapshot = Snapshot(object: TestUser.invalid, dateCreated: Date())
+
+ XCTAssertThrowsError(try store.restoreSnapshot(snapshot))
+ XCTAssertNil(store.lastRestoreDate)
+ XCTAssertEqual(store.object, TestUser.john)
+ }
+
+ func testSnapshotEquality() {
+ let now = Date()
+ let snapshot1 = Snapshot(object: TestUser.john, dateCreated: now)
+ let snapshot2 = Snapshot(object: TestUser.john, dateCreated: now)
+ XCTAssertEqual(snapshot1, snapshot2)
+
+ let snapshot3 = Snapshot(object: TestUser.james, dateCreated: Date())
+ let snapshot4 = Snapshot(object: TestUser.john, dateCreated: Date())
+ XCTAssertNotEqual(snapshot3, snapshot4)
+ }
}
// MARK: - Helpers
private extension SingleStoreTests {
- func createFreshUsersStore(
+ func createFreshUserStore(
encoder: JSONEncoder = .init(),
decoder: JSONDecoder = .init()
- ) -> SingleUserDefaultsStore? {
+ ) -> SingleUserDefaultsStore {
let store = SingleUserDefaultsStore(
uniqueIdentifier: "single-user",
encoder: encoder,
decoder: decoder
)
- store?.delete()
+ store.delete()
return store
}
diff --git a/Tests/StoreTests.swift b/Tests/StoreTests.swift
index 162c63a..238e8a0 100644
--- a/Tests/StoreTests.swift
+++ b/Tests/StoreTests.swift
@@ -25,11 +25,7 @@ import XCTest
@testable import UserDefaultsStore
final class StoreTests: XCTestCase {
-
- func testCreateStore() {
- let store = createFreshUsersStore()
- XCTAssertNotNil(store)
- }
+ private typealias Snapshot = UserDefaultsStore.Snapshot
func testCreateStoreWithCustomEncoderAndDecoder() {
let encoder = JSONEncoder()
@@ -37,8 +33,8 @@ final class StoreTests: XCTestCase {
let store = createFreshUsersStore(encoder: encoder, decoder: decoder)
XCTAssertNotNil(store)
- XCTAssert(store?.encoder === encoder)
- XCTAssert(store?.decoder === decoder)
+ XCTAssert(store.encoder === encoder)
+ XCTAssert(store.decoder === decoder)
}
func testCreateStoreWithCustomDecoder() {
@@ -51,17 +47,12 @@ final class StoreTests: XCTestCase {
let store = createFreshUsersStore(encoder: encoder, decoder: decoder)
XCTAssertNotNil(store)
- XCTAssertNoThrow(try store?.save(TestUser.john))
- XCTAssertEqual(store?.object(withId: TestUser.john.userId), TestUser.john)
- }
-
- func testCreateInvalidStore() {
- let invalidStore = UserDefaultsStore(uniqueIdentifier: UserDefaults.globalDomain)
- XCTAssertNil(invalidStore)
+ XCTAssertNoThrow(try store.save(TestUser.john))
+ XCTAssertEqual(store.object(withId: TestUser.john.id), TestUser.john)
}
func testSaveObject() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save(TestUser.john))
XCTAssertEqual(store.objectsCount, 1)
@@ -69,7 +60,7 @@ final class StoreTests: XCTestCase {
}
func testSaveOptional() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save(nil))
XCTAssertEqual(store.objectsCount, 0)
@@ -80,7 +71,7 @@ final class StoreTests: XCTestCase {
}
func testSaveInvalidObject() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
let optionalUser: TestUser? = TestUser.invalid
XCTAssertThrowsError(try store.save(TestUser.invalid))
@@ -88,7 +79,7 @@ final class StoreTests: XCTestCase {
}
func testSaveObjects() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save([TestUser.john, TestUser.johnson, TestUser.james]))
XCTAssertEqual(store.objectsCount, 3)
@@ -98,7 +89,7 @@ final class StoreTests: XCTestCase {
}
func testSaveInvalidObjects() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertThrowsError(try store.save([TestUser.james, TestUser.john, TestUser.invalid]))
XCTAssertEqual(store.objectsCount, 0)
@@ -106,7 +97,7 @@ final class StoreTests: XCTestCase {
}
func testObject() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save(TestUser.johnson))
let user = store.object(withId: 2)
@@ -117,19 +108,19 @@ final class StoreTests: XCTestCase {
}
func testObjects() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save(TestUser.james))
XCTAssertNoThrow(try store.save(TestUser.johnson))
- let users = store.objects(withIds: [TestUser.james.userId, TestUser.johnson.userId, 5])
+ let users = store.objects(withIds: [TestUser.james.id, TestUser.johnson.id, 5])
XCTAssertEqual(users.count, 2)
XCTAssert(users.contains(TestUser.james))
XCTAssert(users.contains(TestUser.johnson))
}
func testDeleteObject() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save(TestUser.james))
@@ -140,19 +131,19 @@ final class StoreTests: XCTestCase {
}
func testDeleteObjects() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save(TestUser.james))
XCTAssertNoThrow(try store.save(TestUser.johnson))
XCTAssertEqual(store.objectsCount, 2)
- store.delete(withIds: [TestUser.james.userId, 5, 6, 8])
+ store.delete(withIds: [TestUser.james.id, 5, 6, 8])
XCTAssertEqual(store.objectsCount, 1)
}
func testGetAll() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save(TestUser.john))
XCTAssertEqual(store.allObjects(), [TestUser.john])
@@ -168,7 +159,7 @@ final class StoreTests: XCTestCase {
}
func testDeleteAll() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertNoThrow(try store.save(TestUser.john))
XCTAssertNoThrow(try store.save(TestUser.johnson))
@@ -179,15 +170,15 @@ final class StoreTests: XCTestCase {
}
func testHasObject() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
XCTAssertFalse(store.hasObject(withId: 10))
XCTAssertNoThrow(try store.save(TestUser.john))
- XCTAssert(store.hasObject(withId: TestUser.john.userId))
+ XCTAssert(store.hasObject(withId: TestUser.john.id))
}
func testForEach() {
- let store = createFreshUsersStore()!
+ let store = createFreshUsersStore()
let users = [TestUser.john, TestUser.johnson, TestUser.james]
XCTAssertNoThrow(try store.save(users))
@@ -200,22 +191,90 @@ final class StoreTests: XCTestCase {
XCTAssertEqual(counter, 3)
}
+ func testGenerateSnapshot() {
+ let store = createFreshUsersStore()
+
+ let users = [TestUser.john, TestUser.johnson].sorted()
+ XCTAssertNoThrow(try store.save(users))
+
+ XCTAssertNil(store.lastSnapshotDate)
+
+ let snapshot = store.generateSnapshot()
+
+ XCTAssertEqual(snapshot.objects.sorted(), users)
+ XCTAssertNotNil(store.lastSnapshotDate)
+ XCTAssertEqual(store.lastSnapshotDate, snapshot.dateCreated)
+ }
+
+ func testRestoreSnapshot() {
+ var store = createFreshUsersStore()
+
+ let users = [TestUser.john, TestUser.johnson].sorted()
+ XCTAssertNoThrow(try store.save(users))
+
+ let snapshot = store.generateSnapshot()
+
+ store = createFreshUsersStore()
+
+ XCTAssertNil(store.lastRestoreDate)
+ XCTAssertNoThrow(try store.restoreSnapshot(snapshot))
+ XCTAssertNotNil(store.lastRestoreDate)
+ XCTAssertEqual(store.allObjects().sorted(), users)
+ }
+
+ func testRestoreEmptySnapshot() {
+ let store = createFreshUsersStore()
+
+ let users = [TestUser.john, TestUser.johnson]
+ XCTAssertNoThrow(try store.save(users))
+
+ let snapshot = Snapshot(objects: [], dateCreated: Date())
+
+ XCTAssertNoThrow(try store.restoreSnapshot(snapshot))
+
+ XCTAssertNotNil(store.lastRestoreDate)
+ XCTAssertEqual(store.objectsCount, 0)
+ }
+
+ func testRestoreSnapshotWithInvalidObjects() {
+ let store = createFreshUsersStore()
+
+ let users = [TestUser.john, TestUser.johnson]
+ XCTAssertNoThrow(try store.save(users))
+
+ let snapshot = Snapshot(objects: [TestUser.invalid], dateCreated: Date())
+
+ XCTAssertThrowsError(try store.restoreSnapshot(snapshot))
+ XCTAssertNil(store.lastRestoreDate)
+ XCTAssertEqual(store.objectsCount, users.count)
+ XCTAssert(store.hasObject(withId: TestUser.john.id))
+ XCTAssert(store.hasObject(withId: TestUser.johnson.id))
+ }
+
+ func testSnapshotEquality() {
+ let now = Date()
+ let snapshot1 = Snapshot(objects: [TestUser.john], dateCreated: now)
+ let snapshot2 = Snapshot(objects: [TestUser.john], dateCreated: now)
+ XCTAssertEqual(snapshot1, snapshot2)
+
+ let snapshot3 = Snapshot(objects: [TestUser.john], dateCreated: Date())
+ let snapshot4 = Snapshot(objects: [TestUser.john], dateCreated: Date().addingTimeInterval(1))
+ XCTAssertNotEqual(snapshot3, snapshot4)
+ }
}
// MARK: - Helpers
private extension StoreTests {
-
func createFreshUsersStore(
encoder: JSONEncoder = .init(),
decoder: JSONDecoder = .init()
- ) -> UserDefaultsStore? {
+ ) -> UserDefaultsStore {
let store = UserDefaultsStore(
uniqueIdentifier: "users",
encoder: encoder,
decoder: decoder
)
- store?.deleteAll()
+ store.deleteAll()
return store
}
-
}
diff --git a/Tests/TestUser.swift b/Tests/TestUser.swift
index 8b2d6dd..078ea45 100644
--- a/Tests/TestUser.swift
+++ b/Tests/TestUser.swift
@@ -23,26 +23,28 @@
@testable import UserDefaultsStore
-struct TestUser: Codable, Equatable, CustomStringConvertible, Identifiable {
- static let idKey = \TestUser.userId
-
- var userId: Int
- var firstName: String
- var lastName: String
- var age: Double
+struct TestUser: Codable, Equatable, Comparable, CustomStringConvertible, Identifiable {
+ let id: Int
+ let firstName: String
+ let lastName: String
+ let age: Double
static func == (lhs: TestUser, rhs: TestUser) -> Bool {
- return lhs.userId == rhs.userId
+ return lhs.id == rhs.id
+ }
+
+ static func < (lhs: TestUser, rhs: TestUser) -> Bool {
+ lhs.id < rhs.id
}
var description: String {
return firstName
}
- static let john = TestUser(userId: 1, firstName: "John", lastName: "Appleseed", age: 21.5)
- static let johnson = TestUser(userId: 2, firstName: "Johnson", lastName: "Smith", age: 26.3)
- static let james = TestUser(userId: 3, firstName: "James", lastName: "Robert", age: 14)
+ static let john = TestUser(id: 1, firstName: "John", lastName: "Appleseed", age: 21.5)
+ static let johnson = TestUser(id: 2, firstName: "Johnson", lastName: "Smith", age: 26.3)
+ static let james = TestUser(id: 3, firstName: "James", lastName: "Robert", age: 14)
- static let invalid = TestUser(userId: 4, firstName: "", lastName: "", age: .nan)
+ static let invalid = TestUser(id: 4, firstName: "", lastName: "", age: .nan)
}
diff --git a/UserDefaultsStore.podspec b/UserDefaultsStore.podspec
index d81959a..4b7c203 100644
--- a/UserDefaultsStore.podspec
+++ b/UserDefaultsStore.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "UserDefaultsStore"
- s.version = "1.5.0"
+ s.version = "2.0.0"
s.summary = "Why not use UserDefaults to store Codable objects 😉"
s.description = <<-DESC
You love Swift"s Codable protocol and use it everywhere, here is an easy and very light way to store - reasonable amount 😅 - of Codable objects, in a couple lines of code.
@@ -15,11 +15,11 @@ Pod::Spec.new do |s|
s.module_name = "UserDefaultsStore"
s.source = { :git => "https://github.com/omaralbeik/UserDefaultsStore.git", :tag => s.version }
s.source_files = "Sources/**/*.swift"
- s.swift_version = "5.0"
+ s.swift_versions = ['5.1', '5.2']
s.requires_arc = true
- s.ios.deployment_target = "8.0"
- s.osx.deployment_target = "10.10"
- s.tvos.deployment_target = "9.0"
- s.watchos.deployment_target = "2.0"
+ s.ios.deployment_target = "13.0"
+ s.osx.deployment_target = "10.15"
+ s.tvos.deployment_target = "13.0"
+ s.watchos.deployment_target = "6.0"
end
diff --git a/UserDefaultsStore.xcodeproj/project.pbxproj b/UserDefaultsStore.xcodeproj/project.pbxproj
index e3583c7..fef488a 100644
--- a/UserDefaultsStore.xcodeproj/project.pbxproj
+++ b/UserDefaultsStore.xcodeproj/project.pbxproj
@@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
- 07563822210A396C005CDE89 /* Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0756381E210A396C005CDE89 /* Identifiable.swift */; };
07563831210A3C4A005CDE89 /* UserDefaultsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0756381D210A396C005CDE89 /* UserDefaultsStore.swift */; };
07563832210A3C4D005CDE89 /* StoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07563825210A3975005CDE89 /* StoreTests.swift */; };
078D2A692147BA7500923390 /* SingleUserDefaultsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 078D2A682147BA7500923390 /* SingleUserDefaultsStore.swift */; };
@@ -27,8 +26,6 @@
/* Begin PBXFileReference section */
0756381D210A396C005CDE89 /* UserDefaultsStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsStore.swift; sourceTree = ""; };
- 0756381E210A396C005CDE89 /* Identifiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Identifiable.swift; sourceTree = ""; };
- 0756381F210A396C005CDE89 /* UserDefaultsStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserDefaultsStore.h; sourceTree = ""; };
07563825210A3975005CDE89 /* StoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreTests.swift; sourceTree = ""; };
07563827210A39D8005CDE89 /* UserDefaultsStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UserDefaultsStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0756382B210A3A59005CDE89 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
@@ -61,10 +58,8 @@
isa = PBXGroup;
children = (
0756382B210A3A59005CDE89 /* Info.plist */,
- 0756381E210A396C005CDE89 /* Identifiable.swift */,
- 0756381D210A396C005CDE89 /* UserDefaultsStore.swift */,
078D2A682147BA7500923390 /* SingleUserDefaultsStore.swift */,
- 0756381F210A396C005CDE89 /* UserDefaultsStore.h */,
+ 0756381D210A396C005CDE89 /* UserDefaultsStore.swift */,
);
path = Sources;
sourceTree = "";
@@ -235,7 +230,6 @@
files = (
07563831210A3C4A005CDE89 /* UserDefaultsStore.swift in Sources */,
078D2A692147BA7500923390 /* SingleUserDefaultsStore.swift in Sources */,
- 07563822210A396C005CDE89 /* Identifiable.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -398,13 +392,17 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- MARKETING_VERSION = 1.5;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MARKETING_VERSION = 2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.UserDefaultsStore;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
+ TVOS_DEPLOYMENT_TARGET = 13.0;
+ WATCHOS_DEPLOYMENT_TARGET = 6.0;
};
name = Debug;
};
@@ -422,13 +420,17 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Sources/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
- MARKETING_VERSION = 1.5;
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MARKETING_VERSION = 2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.UserDefaultsStore;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
+ TVOS_DEPLOYMENT_TARGET = 13.0;
+ WATCHOS_DEPLOYMENT_TARGET = 6.0;
};
name = Release;
};
@@ -442,13 +444,17 @@
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Tests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.UserDefaultsStoreTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
+ TVOS_DEPLOYMENT_TARGET = 13.0;
+ WATCHOS_DEPLOYMENT_TARGET = 6.0;
};
name = Debug;
};
@@ -462,12 +468,16 @@
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = Tests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.omaralbeik.UserDefaultsStoreTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
+ TVOS_DEPLOYMENT_TARGET = 13.0;
+ WATCHOS_DEPLOYMENT_TARGET = 6.0;
};
name = Release;
};
diff --git a/docs/Classes.html b/docs/Classes.html
index 9ecf327..937eafe 100644
--- a/docs/Classes.html
+++ b/docs/Classes.html
@@ -21,7 +21,7 @@