Skip to content

Instantly share code, notes, and snippets.

@gokselkoksal
Last active February 22, 2021 20:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gokselkoksal/ee9802e3a48ac2ee5a5f5576b5a4e150 to your computer and use it in GitHub Desktop.
Save gokselkoksal/ee9802e3a48ac2ee5a5f5576b5a4e150 to your computer and use it in GitHub Desktop.
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))
}
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