From 9894e663cac64e855a475b7bc9c3d37c810e7fda Mon Sep 17 00:00:00 2001 From: JP Wright Date: Mon, 17 Jul 2017 22:33:27 +0200 Subject: [PATCH 1/2] Send entire SyncSpace to persistence integration Prune dead code and minor refactors --- Sources/Client.swift | 21 +++++++++++++++------ Sources/Persistence.swift | 30 ++++++++++++++++++++++++++++++ Sources/SyncSpace.swift | 28 ++++++++-------------------- Tests/ObjectMappingTests.swift | 12 ++++++------ 4 files changed, 59 insertions(+), 32 deletions(-) diff --git a/Sources/Client.swift b/Sources/Client.swift index e80b0003..74dc0d97 100644 --- a/Sources/Client.swift +++ b/Sources/Client.swift @@ -466,7 +466,8 @@ extension Client { - Returns: The data task being used, enables cancellation of requests. */ - @discardableResult public func fetchContentType(id: String, completion: @escaping ResultsHandler) -> URLSessionDataTask? { + @discardableResult public func fetchContentType(id: String, + completion: @escaping ResultsHandler) -> URLSessionDataTask? { return fetch(url: URL(forComponent: "content_types/\(id)"), then: completion) } @@ -542,7 +543,8 @@ extension Client { - Returns: The data task being used, enables cancellation of requests. */ - @discardableResult public func fetchEntry(id: String, completion: @escaping ResultsHandler) -> URLSessionDataTask? { + @discardableResult public func fetchEntry(id: String, + completion: @escaping ResultsHandler) -> URLSessionDataTask? { let fetchEntriesCompletion: (Result>) -> Void = { result in switch result { case .success(let entries) where entries.items.first != nil: @@ -651,7 +653,8 @@ extension Client { - Returns: An `Observable` which will be fired when the `SyncSpace` is fully synchronized with Contentful. */ - @discardableResult func nextSync(for syncSpace: SyncSpace, matching: [String: Any] = [:]) -> Observable> { + @discardableResult func nextSync(for syncSpace: SyncSpace, + matching: [String: Any] = [:]) -> Observable> { let observable = Observable>() self.nextSync(for: syncSpace) { result in @@ -698,7 +701,8 @@ extension Client { return task } - fileprivate func sync(matching: [String: Any] = [:], completion: @escaping ResultsHandler) -> URLSessionDataTask? { + fileprivate func sync(matching: [String: Any] = [:], + completion: @escaping ResultsHandler) -> URLSessionDataTask? { return fetch(url: URL(forComponent: "sync", parameters: matching)) { (result: Result) in @@ -710,11 +714,16 @@ extension Client { } } - fileprivate func finishSync(for syncSpace: SyncSpace, newestSyncResults: Result, completion: ResultsHandler) { + fileprivate func finishSync(for syncSpace: SyncSpace, + newestSyncResults: Result, + completion: ResultsHandler) { switch newestSyncResults { case .success(let newSyncSpace): - syncSpace.updateWithDiffs(from: newSyncSpace, persistenceIntegration: self.persistenceIntegration) + syncSpace.updateWithDiffs(from: newSyncSpace) + persistenceIntegration?.update(with: newSyncSpace) + + // Send fully merged syncSpace to completion handler. completion(Result.success(syncSpace)) case .error(let error): completion(Result.error(error)) diff --git a/Sources/Persistence.swift b/Sources/Persistence.swift index fd91194f..e6f91868 100644 --- a/Sources/Persistence.swift +++ b/Sources/Persistence.swift @@ -17,9 +17,21 @@ import Foundation */ public protocol PersistenceIntegration: Integration { + /** + Update the local datastore with the latest delta messages from the most recently fetched SyncSpace response. + + There is no guarantee which thread this will be called from, so it is your responsibility when implementing + this method, to execute on whatever thread your local datastore may require operations to be executed on. + */ + func update(with syncSpace: SyncSpace) + + /** This is called whenever a new Asset was created or an existing one was updated. + There is no guarantee which thread this will be called from, so it is your responsibility when implementing + this method, to execute on whatever thread your local datastore may require operations to be executed on. + - parameter asset: The created/updated Asset */ func create(asset: Asset) @@ -27,6 +39,9 @@ public protocol PersistenceIntegration: Integration { /** This is called whenever an Asset was deleted. + There is no guarantee which thread this will be called from, so it is your responsibility when implementing + this method, to execute on whatever thread your local datastore may require operations to be executed on. + - parameter assetId: Identifier of the Asset that was deleted. */ func delete(assetWithId: String) @@ -34,6 +49,9 @@ public protocol PersistenceIntegration: Integration { /** This is called whenever a new Entry was created or an existing one was updated. + There is no guarantee which thread this will be called from, so it is your responsibility when implementing + this method, to execute on whatever thread your local datastore may require operations to be executed on. + - parameter entry: The created/updated Entry */ func create(entry: Entry) @@ -41,6 +59,9 @@ public protocol PersistenceIntegration: Integration { /** This is called whenever an Entry was deleted. + There is no guarantee which thread this will be called from, so it is your responsibility when implementing + this method, to execute on whatever thread your local datastore may require operations to be executed on. + - parameter entryId: Identifier of the Entry that was deleted. */ func delete(entryWithId: String) @@ -49,6 +70,9 @@ public protocol PersistenceIntegration: Integration { This method is called on completion of a successful `Client.initialSync` and `Client.nextSync` calls so that the sync token can be cached and future launches of your application can synchronize without doing an `initialSync`. + + There is no guarantee which thread this will be called from, so it is your responsibility when implementing + this method, to execute on whatever thread your local datastore may require operations to be executed on. */ func update(syncToken: String) @@ -56,12 +80,18 @@ public protocol PersistenceIntegration: Integration { This method is called after all `Entry`s have been created and all links have been resolved. The implementation should map `Entry` fields that are `Link`s to persistent relationships in the underlying persistent data store. + + There is no guarantee which thread this will be called from, so it is your responsibility when implementing + this method, to execute on whatever thread your local datastore may require operations to be executed on. */ func resolveRelationships() /** This method is called after all `Asset`s and `Entry`s have been tranformed to persistable data structures. The implementation should actually perform the save operation to the persisent database. + + There is no guarantee which thread this will be called from, so it is your responsibility when implementing + this method, to execute on whatever thread your local datastore may require operations to be executed on. */ func save() } diff --git a/Sources/SyncSpace.swift b/Sources/SyncSpace.swift index 67fea518..21fc11cd 100644 --- a/Sources/SyncSpace.swift +++ b/Sources/SyncSpace.swift @@ -15,8 +15,8 @@ public final class SyncSpace: ImmutableMappable { internal var assetsMap = [String: Asset]() internal var entriesMap = [String: Entry]() - var deletedAssets = [String]() - var deletedEntries = [String]() + public var deletedAssetIds = [String]() + public var deletedEntryIds = [String]() var hasMorePages: Bool @@ -101,11 +101,10 @@ public final class SyncSpace: ImmutableMappable { } } - internal func updateWithDiffs(from syncSpace: SyncSpace, persistenceIntegration: PersistenceIntegration?) { + internal func updateWithDiffs(from syncSpace: SyncSpace) { for asset in syncSpace.assets { assetsMap[asset.sys.id] = asset - persistenceIntegration?.create(asset: asset) } // Update and deduplicate all entries. @@ -113,31 +112,20 @@ public final class SyncSpace: ImmutableMappable { entriesMap[entry.sys.id] = entry } - // resolve all entries. + // Resolve all entries in-memory. for entry in entries { entry.resolveLinks(against: entries, and: assets) - persistenceIntegration?.create(entry: entry) } - for deletedAssetId in syncSpace.deletedAssets { + for deletedAssetId in syncSpace.deletedAssetIds { assetsMap.removeValue(forKey: deletedAssetId) - persistenceIntegration?.delete(assetWithId: deletedAssetId) } - for deletedEntryId in syncSpace.deletedEntries { + for deletedEntryId in syncSpace.deletedEntryIds { entriesMap.removeValue(forKey: deletedEntryId) - persistenceIntegration?.delete(entryWithId: deletedEntryId) } - // Reset so that we can't send same deletion messages twice. - self.deletedEntries = [String]() - self.deletedAssets = [String]() - syncToken = syncSpace.syncToken - - persistenceIntegration?.update(syncToken: syncToken) - persistenceIntegration?.resolveRelationships() - persistenceIntegration?.save() } internal func cache(resources: [Resource]) { @@ -151,8 +139,8 @@ public final class SyncSpace: ImmutableMappable { case let deletedResource as DeletedResource: switch deletedResource.sys.type { - case "DeletedAsset": self.deletedAssets.append(deletedResource.sys.id) - case "DeletedEntry": self.deletedEntries.append(deletedResource.sys.id) + case "DeletedAsset": self.deletedAssetIds.append(deletedResource.sys.id) + case "DeletedEntry": self.deletedEntryIds.append(deletedResource.sys.id) default: break } default: break diff --git a/Tests/ObjectMappingTests.swift b/Tests/ObjectMappingTests.swift index 01e68b4a..1ebab0d4 100644 --- a/Tests/ObjectMappingTests.swift +++ b/Tests/ObjectMappingTests.swift @@ -109,7 +109,7 @@ class ObjectMappingTests: XCTestCase { } } - func testDecodeSyncResponsesWithDeletedAssets() { + func testDecodeSyncResponsesWithdeletedAssetIds() { do { let map = Map(mappingType: .fromJSON, JSON: ObjectMappingTests.jsonData("deleted-asset")) @@ -117,14 +117,14 @@ class ObjectMappingTests: XCTestCase { expect(syncSpace.assets.count).to(equal(0)) expect(syncSpace.entries.count).to(equal(0)) - expect(syncSpace.deletedAssets.count).to(equal(1)) - expect(syncSpace.deletedEntries.count).to(equal(0)) + expect(syncSpace.deletedAssetIds.count).to(equal(1)) + expect(syncSpace.deletedEntryIds.count).to(equal(0)) } catch _ { fail("Decoding sync responses with deleted Assets should not throw an error") } } - func testDecodeSyncResponsesWithDeletedEntries() { + func testDecodeSyncResponsesWithdeletedEntryIds() { do { let map = Map(mappingType: .fromJSON, JSON: ObjectMappingTests.jsonData("deleted")) @@ -132,8 +132,8 @@ class ObjectMappingTests: XCTestCase { expect(syncSpace.assets.count).to(equal(0)) expect(syncSpace.entries.count).to(equal(0)) - expect(syncSpace.deletedAssets.count).to(equal(0)) - expect(syncSpace.deletedEntries.count).to(equal(1)) + expect(syncSpace.deletedAssetIds.count).to(equal(0)) + expect(syncSpace.deletedEntryIds.count).to(equal(1)) } catch _ { fail("Decoding sync responses with deleted Entries should not throw an error") } From 9b986689a323f500e7e7e469269ec4f568752da1 Mon Sep 17 00:00:00 2001 From: JP Wright Date: Tue, 18 Jul 2017 10:28:40 +0200 Subject: [PATCH 2/2] Update changelog, readme, and bump version number for 0.7.5 patch --- .env | 2 +- .envrc | 2 +- CHANGELOG.md | 11 ++++++++++- Config.xcconfig | 2 +- README.md | 4 ++-- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.env b/.env index b308ab52..da384387 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -CONTENTFUL_SDK_VERSION=0.7.4 +CONTENTFUL_SDK_VERSION=0.7.5 diff --git a/.envrc b/.envrc index 76c4742f..301c0615 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -export CONTENFUL_SDK_VERSION=0.7.4 +export CONTENFUL_SDK_VERSION=0.7.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eb99b31..7af6de1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,21 @@ This project adheres to [Semantic Versioning](http://semver.org/) starting from ## Table of contents #### 0.x Releases -- `0.7.x` Releases - [0.7.0](#070) | [0.7.1](#071) | [0.7.2](#072)) | [0.7.3](#073) | [0.7.4](#074) +- `0.7.x` Releases - [0.7.0](#070) | [0.7.1](#071) | [0.7.2](#072)) | [0.7.3](#073) | [0.7.4](#074) | [0.7.5](#075) - `0.6.x` Releases - [0.6.0](#060) | [0.6.1](#061) - `0.5.x` Releases - [0.5.0](#050) - `0.4.x` Releases - [0.4.0](#040) | [0.4.1](#041) - `0.3.x` Releases - [0.3.0](#030) | [0.3.1](#031) + +--- + +## [`0.7.5`](https://github.com/contentful/contentful.swift/releases/tag/0.7.5) +Released on 2017-07-18 + +#### Changed +- Simplified and optimized sending messages to types conforming to `PersistenceIntegration`. + --- ## [`0.7.4`](https://github.com/contentful/contentful.swift/releases/tag/0.7.4) diff --git a/Config.xcconfig b/Config.xcconfig index b308ab52..da384387 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -1 +1 @@ -CONTENTFUL_SDK_VERSION=0.7.4 +CONTENTFUL_SDK_VERSION=0.7.5 diff --git a/README.md b/README.md index 02072be4..7bd2575d 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ pod 'Contentful' You can specify a specific version of Contentful depending on your needs. To learn more about operators for dependency versioning within a Podfile, see the [CocoaPods doc on the Podfile][7]. ```ruby -pod 'Contentful', '~> 0.7.4' +pod 'Contentful', '~> 0.7.5' ``` Note that for Swift 2.3 support (contentful.swift `v0.2.3`) you will need to add a post-install script to your Podfile if installing with Cocoapods: @@ -100,7 +100,7 @@ end You can also use [Carthage][8] for integration by adding the following to your `Cartfile`: ``` -github "contentful/contentful.swift" ~> 0.7.4 +github "contentful/contentful.swift" ~> 0.7.5 ``` ## License