Chop 0.4.0

Chop 0.4.0

TestsTested
LangLanguage SwiftSwift
License MIT
ReleasedLast Release Jan 2019
SwiftSwift Version 4.0
SPMSupports SPM

Maintained by Ivan Moskalev.



Chop 0.4.0

Chop

Write synchronous, testable core easily with Chop.

CI Status codecov.io Version License Platform

What is Chop?

Chop is a Swift microframework providing implementations of tasks and task groups.

  • Tasks are abstractions over asynchronous processes that can yield several values during their lifetime (think of progressive images or cached-then-remote values). They can also finish with an error. Tasks are lazy – which means they are not started before they really need to. Furthermore, tasks are cancelled when they are no longer referenced (i.e. deallocated), freeing up resources they are associated with.
  • Task Groups are execution contexts for tasks. They allow to place uniqueness constraints on tasks and apply a variety of behaviors around that. Task Groups also free up all tasks they manage when they are deallocated, making them a nice way to tie particular tasks to, say, user interface.

Here is a small example of how Chop can be used to execute a task that progressively loads a collection of items (for example, first fetching the locally cached value, and then the remote value). Only one task is allowed to execute; subsequent similar tasks are ignored until the first one finishes:

// The context in which the tasks can execute. 
// `policy` describes what should be done if a task with an identical `taskId` is added to group.
// In this case we want to ignore subsequent tasks with the same `taskId` until the first is completed.
// Other options exist.
let taskGroup = TaskGroup(policy: .ignore)

self.isLoading = true
self.interactor
  .fetchIssues(request, itemLimit: 10, progressive: true) // API that exposes a Chop'esque interface.
  .onUpdate { [weak self] in
    // Can be executed multiple times (i.e. providing first cached items, then remote items).
    self?.items = $0
  }
  .onFailure { [weak self] in
    // Executed only once if error occurs.
    self?.handleError($0)  
  }
  .onCompletion { [weak self] in
    // Executed when the task has finished, regardless of result.
    self?.isLoading = false 
  }
  // `taskId` is optional - it will be 
  .registerIn(self.taskGroup, taskId: "itemFetch")

The task is created like this:

func fetchIssues(request: IssueRequest, itemLimit: UInt, progressive: Bool) -> Task<[Issue]> {
  return Task {
    let localItems = self.localDB.getItems(request)
    // Send local items through to the Task subscribers.
    $0(.update(localItems))
    
    // The following operation is asynchronous.
    var urlSessionTask = self.httpClient.getItems(request) { result in
      switch result {
        // Once again send data through to the Task subscribers.
        case .success(let issues): $0(.update(issues))
        // Or, otherwise, send error.
        case .failure(let error):  $0(.error(localResponse))
      }
      $0(.completion)
    }
    
    // In this closure we perform cleanup. It will be called if the task is finished or cancelled.
    return { urlSessionTask.cancel(); urlSessionTask = nil }
  }
}

The task performs work only as long as it is referenced by a TaskGroup. Consequently, if the TaskGroup is destroyed, all managed tasks are interrupted and you don't have to care about them anymore.

Requirements

Chop requires Swift 3.2. Swift 4 is supported as well.

Installation

Chop is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "Chop"

Author

Ivan Moskalev, [email protected]

License

Chop is available under the MIT license. See the LICENSE file for more info.