-
-
Save gokselkoksal/ee9802e3a48ac2ee5a5f5576b5a4e150 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum PostTask { | |
case like(id: UUID) | |
} | |
enum PostUpdate { | |
case isLiked(Bool) | |
} | |
struct Post { | |
var id: UUID | |
var message: String | |
var isLiked: Bool | |
mutating func apply(_ update: PostUpdate) { | |
switch update { | |
case let .isLiked(value): | |
self.isLiked = value | |
} | |
} | |
} | |
func test() { | |
let service = PostService() | |
var post = Post(id: UUID(), message: "Test", isLiked: false) | |
// 1. Create an optimistic updater: | |
let postUpdater = OptimisticUpdater<PostTask, PostUpdate, PostTaskError> { task, optimisticUpdate, finish in | |
service.perform(task) { (result) in | |
switch result { | |
case .success: | |
finish(.success) | |
case .failure(let error): | |
finish(.failure(error, rollbackUpdate: optimisticUpdate.revert())) | |
} | |
} | |
} | |
// 2: Set a callback for updater events: | |
postUpdater.eventHandler = { event in | |
switch event { | |
case let .optimisticUpdateRequested(update, task: task): | |
post.apply(update) | |
case let .taskSucceeded(task, update: Update): | |
showConfirmation(task) | |
case let .taskFailed(task, error: error, failedUpdate: failedUpdate, rollbackUpdate: rollbackUpdate): | |
showError(error, for: task) | |
post.apply(rollbackUpdate) | |
} | |
} | |
// 3. Use the updater to like a post: | |
postUpdater.perform(.like(id: post.id), optimisticUpdate: .isLiked(true)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public enum OptimisticUpdaterTaskResult<Update, Error: Swift.Error> { | |
/// Task is successful. Optimistic update can be kept. | |
case success | |
/// Task has failed. Optimistic update should be reverted. | |
case failure(Error, rollbackUpdate: Update) | |
} | |
/// Optimistic updater is used to perform a task along with optimistic updates. It is responsible from starting the | |
/// task, applying and reverting optimistic updates when needed. | |
public final class OptimisticUpdater<Task, Update, Error: Swift.Error> { | |
public enum Event { | |
/// Optimistic update should be applied. Task is about to start executing. | |
case optimisticUpdateRequested(Update, task: Task) | |
/// Task is successful. Optimistic update should be kept. | |
case taskSucceeded(Task, update: Update) | |
/// Task has failed. Optimistic update should be reverted. | |
case taskFailed(Task, error: Error, failedUpdate: Update, rollbackUpdate: Update) | |
} | |
public typealias PerformTaskFunction = ( | |
_ task: Task, | |
_ update: Update, | |
_ finish: @escaping (OptimisticUpdaterTaskResult<Update, Error>) -> Void | |
) -> Void | |
public var eventHandler: ((Event) -> Void)? | |
private let performTask: PerformTaskFunction | |
/// Creates an instance. | |
/// - Parameter performTask: A function that performs the actual action and reports the result. | |
public init(performTask: @escaping PerformTaskFunction) { | |
self.performTask = performTask | |
} | |
/// Performs the task while applying the given optimistic update. | |
/// - Parameters: | |
/// - task: The actual task that needs to be performed. | |
/// - optimisticUpdate: Optimistic update to be applied while the task is in progress. | |
public func perform(_ task: Task, optimisticUpdate: Update) { | |
send(.optimisticUpdateRequested(optimisticUpdate, task: task)) | |
performTask(task, optimisticUpdate) { [weak self] (result) in | |
switch result { | |
case .success: | |
self?.send(.taskSucceeded(task, update: optimisticUpdate)) | |
case let .failure(error, rollbackUpdate: rollbackUpdate): | |
self?.send( | |
.taskFailed( | |
task, | |
error: error, | |
failedUpdate: optimisticUpdate, | |
rollbackUpdate: rollbackUpdate | |
) | |
) | |
} | |
} | |
} | |
private func send(_ event: Event) { | |
eventHandler?(event) | |
} | |
} | |
// MARK: - Event + Equatable | |
extension OptimisticUpdater.Event: Equatable where Task: Equatable, Update: Equatable, Error: Equatable { | |
public static func == ( | |
lhs: OptimisticUpdater<Task, Update, Error>.Event, | |
rhs: OptimisticUpdater<Task, Update, Error>.Event | |
) -> Bool { | |
switch (lhs, rhs) { | |
case let (.optimisticUpdateRequested(update1, task: task1), | |
.optimisticUpdateRequested(update2, task: task2)): | |
return update1 == update2 && task1 == task2 | |
case let (.taskSucceeded(task1, update: update1), | |
.taskSucceeded(task2, update: update2)): | |
return task1 == task2 && update1 == update2 | |
case let (.taskFailed(task1, error: error1, failedUpdate: failedUpdate1, rollbackUpdate: rollbackUpdate1), | |
.taskFailed(task2, error: error2, failedUpdate: failedUpdate2, rollbackUpdate: rollbackUpdate2)): | |
return task1 == task2 | |
&& error1 == error2 | |
&& failedUpdate1 == failedUpdate2 | |
&& rollbackUpdate1 == rollbackUpdate2 | |
default: | |
return false | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment