Operation Oriented Programming in Swift

Overview

Flow

Travis CocoaPods Carthage

Flow is a lightweight Swift library for doing operation oriented programming. It enables you to easily define your own, atomic operations, and also contains an exensive library of ready-to-use operations that can be grouped, sequenced, queued and repeated.

Operations

Using Flow is all about splitting your code up into multiple atomic pieces - called operations. Each operation defines a body of work, that can easily be reused throughout an app or library.

An operation can do anything, synchronously or asynchronously, and its scope is really up to you. The true power of operation oriented programming however, comes when you create groups, sequences and queues out of operations. Operations can potentially make code that is either asynchronous, or where work has to be done in several places, a lot simpler.

How to use

  • Create your own operations by conforming to FlowOperation in a custom object. All it needs to do it implement one method that performs it with a completion handler. It’s free to be initialized in whatever way you want, and can be either a class or a struct.

  • Use any of the built-in operations, such as FlowClosureOperation, FlowDelayOperation, etc.

  • Create sequences of operations (that get executed one by one) using FlowOperationSequence, groups (that get executed all at once) using FlowOperationGroup, or queues (that can be continuously filled with operations) using FlowOperationQueue.

Example

Let’s say we’re building a game and we want to perform a series of animations where a Player attacks an Enemy, destroys it and then plays a victory animation. This could of course be accomplished with the use of completion handler closures:

player.moveTo(enemy.position) {
    player.performAttack() {
        enemy.destroy() {
            player.playVictoryAnimation()
        }
    }
}

However, this quickly becomes hard to reason about and debug, especially if we start adding multiple animations that we want to sync. Let’s say we decide to implement a new spin attack in our game, that destroys multiple enemies, and we want all enemies to be destroyed before we play the victory animation. We’d have to do something like this:

player.moveTo(mainEnemy.position) {
    player.performAttack() {
        var enemiesDestroyed = 0
                
        for enemy in enemies {
            enemy.destroy({
                enemiesDestroyed += 1
                        
                if enemiesDestroyed == enemies.count {
                    player.playVictoryAnimation()
                }
            })
        }
    }
}

It becomes clear that the more we add to our animation, the more error prone and hard to debug it becomes. Wouldn’t it be great if our animations (or any other sequence of tasks) could scale gracefully as we make them more and more complex?

Let’s implement the above using Flow instead. We’ll start by defining all tasks that we need to perform during our animation as operations:

/// Operation that moves a Player to a destination
class PlayerMoveOperation: FlowOperation {
    private let player: Player
    private let destination: CGPoint
    
    init(player: Player, destination: CGPoint) {
        self.player = player
        self.destination = destination
    }
    
    func perform(completionHandler: @escaping () -> Void) {
        self.player.moveTo(self.destination, completionHandler: completionHandler)
    }
}

/// Operation that performs a Player attack
class PlayerAttackOperation: FlowOperation {
    private let player: Player
    
    init(player: Player) {
        self.player = player
    }
    
    func perform(completionHandler: @escaping () -> Void) {
        self.player.performAttack(completionHandler)
    }
}

/// Operation that destroys an enemy
class EnemyDestroyOperation: FlowOperation {
    private let enemy: Enemy
    
    init(enemy: Enemy) {
        self.enemy = enemy
    }
    
    func perform(completionHandler: @escaping () -> Void) {
        self.enemy.destroy(completionHandler)
    }
}

/// Operation that plays a Player victory animation
class PlayerVictoryOperation: FlowOperation {
    private let player: Player
    
    init(player: Player) {
        self.player = player
    }
    
    func perform(completionHandler: @escaping () -> Void) {
        self.player.playVictoryAnimation()
        completionHandler()
    }
}

Secondly; we’ll implement our animation using the above operations:

let moveOperation = PlayerMoveOperation(player: player, destination: mainEnemy.position)
let attackOperation = PlayerAttackOperation(player: player)
let destroyEnemiesOperation = FlowOperationGroup(operations: enemies.map({
    return EnemyDestroyOperation(enemy: $0)
}))
let victoryOperation = PlayerVictoryOperation(player: player)
        
let operationSequence = FlowOperationSequence(operations: [
    moveOperation,
    attackOperation,
    destroyEnemiesOperation,
    victoryOperation
])
        
operationSequence.perform()

While we had to write a bit more code using operations; this approach has some big advantages.

Firstly; we can now use a FlowOperationGroup to make sure that all enemy animations are finished before moving on, and by doing this we’ve reduced the state we need to keep within the animation itself.

Secondly; all parts of the animation are now independant operations that don’t have to be aware of each other, making them a lot easier to test & debug - and they could also be reused in other parts of our game.

API reference

Protocols

FlowOperation Used to declare custom operations.

FlowOperationCollection Used to declare custom collections of operations.

Base operations

FlowClosureOperation Operation that runs a closure, and returns directly when performed.

FlowAsyncClosureOperation Operation that runs a closure, then waits for that closure to call a completion handler before it finishes.

FlowDelayOperation Operation that waits for a certain delay before finishing. Useful in sequences and queues.

Operation collections & utilities

FlowOperationGroup Used to group together a series of operations that all get performed at once when the group is performed.

FlowOperationSequence Used to sequence a series of operations, performing them one by one once the sequence is performed.

FlowOperationQueue Queue that keeps executing the next operation as soon as it becomes idle. New operations can constantly be added.

FlowOperationRepeater Used to repeat operations, optionally using an interval in between repeats.

How is this different from NSOperations?

NSOperations are awesome - and are definetly one of the main sources of inspiration for Flow. However, NSOperations are quite heavyweight and can potentially take a long time to implement. Flow was designed to have the power of NSOperations, but be a lot easier to use. It’s also written 100% using Swift - making it ideal for Swift-based projects.

Compatibility

Flow supports all current Apple platforms with the following minimum versions:

  • iOS 8
  • macOS 10.11
  • watchOS 2
  • tvOS 9

The current version of Flow supports Swift 3. If you need Swift 2 support, either use version 1.1, or the swift 2 branch.

Installation

CocoaPods:

Add the line pod "FlowOperations" to your Podfile

Carthage:

Add the line github "johnsundell/flow" to your Cartfile

Manual:

Clone the repo and drag the file Flow.swift into your Xcode project.

Swift Package Manager:

Add the line .Package(url: "https://github.com/johnsundell/flow.git", majorVersion: 2) to your Package.swift

Hope you enjoy using Flow!

For support, feedback & news about Flow; follow me on Twitter: @johnsundell.

You might also like...
 GCDTimer - Well tested Grand Central Dispatch (GCD) Timer in Swift
GCDTimer - Well tested Grand Central Dispatch (GCD) Timer in Swift

GCDTimer Well tested Grand Central Dispatch (GCD) Timer in Swift. Checkout the test file. Usage Long running timer import GCDTimer

Schedule timing task in Swift using a fluent API. (A friendly alternative to Timer)
Schedule timing task in Swift using a fluent API. (A friendly alternative to Timer)

Schedule(简体中文) Schedule is a timing tasks scheduler written in Swift. It allows you run timing tasks with elegant and intuitive syntax. Features Elega

Grand Central Dispatch simplified with swift.

GCDKit GCDKit is Grand Central Dispatch simplified with Swift. for Swift 1.2: Use version 1.0.1 for Swift 2.1 / 2.2: Use the master branch Introductio

Queues, timers, and task groups in Swift
Queues, timers, and task groups in Swift

Dispatcher eases the pain of using Grand Central Dispatch by introducing 4 new Swift classes. Dispatcher Queue Group Timer Requirements Swift 2.0+ Ins

A wrapper of Grand Central Dispatch written in Swift

GCD A wrapper of Grand Central Dispatch written in Swift. Examples gcd // submit your code for asynchronous execution on a global queue with high prio

A Swift microframework for very easy atomic values.

Atomic Atomic is a fast, safe class for making values thread-safe in Swift. It is backed by pthread_mutex_lock which is the fastest, most-efficient lo

Syntactic sugar in Swift for asynchronous dispatches in Grand Central Dispatch (iOS7+ and OS X 10.9+ compatible)

Async.legacy Syntactic sugar in Swift for asynchronous dispatches in Grand Central Dispatch (GCD) Async rewritten for iOS7 and OS X 10.9 Compatibility

⏳ Collection of Swift 5.5 async/await utility functions.

⏳ FunAsync Collection of Swift 5.5 async/await utility functions. Throw - Result conversion asyncThrowsToAsyncResult asyncResultToAsyncThrows More C

🎭 Swift async/await & Actor-powered effectful state-management framework.
🎭 Swift async/await & Actor-powered effectful state-management framework.

🎭 Actomaton 🧑‍🎤 Actor + 🤖 Automaton = 🎭 Actomaton Actomaton is Swift async/await & Actor-powered effectful state-management framework inspired by

Comments
Releases(2.0)
  • 2.0(Oct 6, 2016)

    This release moves Flow to Swift 3 by default (you can either use earlier versions or the swift2 branch if you want to use Swift 2) and updates the API to conform to the Swift API naming guidelines.

    ⚠️ Note that this release includes breaking changes for users of Flow 1.x. Please upgrade with caution and make sure that you migrate to the new APIs. Also note that Flow 2.0 is only compatible with Swift 3.

    Updated APIs

    • FlowOperation.performWithCompletionHandler() is now perform(completionHandler:).
    • FlowOperationCollection.addOperation() is now add(operation:).
    • FlowOperationCollection.addOperations() is now add(operations:).

    New APIs

    • You can now pause & resume a FlowOpeationQueue using the paused property. You can also set paused as part of the initializer.
    Source code(tar.gz)
    Source code(zip)
  • 1.1(Jun 6, 2016)

    This version of Flow makes improvements to working with collections & observing queues:

    • Multiple operations can now be added to any FlowOperationCollection (groups, queues, sequences, etc) in one go - using addOperations().
    • Flow now contains default implementations for both methods in the FlowOperationQueueObserver protocol, meaning that they are both optional to implement - reducing boilerplate.
    Source code(tar.gz)
    Source code(zip)
  • 1.0(May 29, 2016)

Owner
John Sundell
I build apps, games and Swift developer tools! Passionate about open source & developer productivity. You can follow me on Twitter @JohnSundell.
John Sundell
Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift.

Futures Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift. It's lightweight, fast, and easy to understa

David Ask 60 Aug 11, 2022
Egg-Timer - Intermediate Swift Programming - Control Flow and Optionals

Egg-Timer Intermediate Swift Programming - Control Flow and Optionals What I lea

null 0 Jan 10, 2022
GroupWork is an easy to use Swift framework that helps you orchestrate your concurrent, asynchronous functions in a clean and organized way

GroupWork is an easy to use Swift framework that helps you orchestrate your concurrent, asynchronous functions in a clean and organized way. This help

Quan Vo 42 Oct 5, 2022
Hydra ⚡️ Lightweight full-featured Promises, Async & Await Library in Swift

Lightweight full-featured Promises, Async & Await Library in Swift What's this? Hydra is full-featured lightweight library which allows you to write b

Daniele Margutti 2k Dec 24, 2022
Kommander is a Swift library to manage the task execution in different threads.

A lightweight, pure-Swift library for manage the task execution in different threads. Through the definition a simple but powerful concept, Kommand.

Intelygenz 173 Apr 11, 2022
SwiftCoroutine - Swift coroutines for iOS, macOS and Linux.

Many languages, such as Kotlin, Go, JavaScript, Python, Rust, C#, C++ and others, already have coroutines support that makes the async/await pattern i

Alex Belozierov 808 Dec 1, 2022
Venice - Coroutines, structured concurrency and CSP for Swift on macOS and Linux.

Venice provides structured concurrency and CSP for Swift. Features Coroutines Coroutine cancelation Coroutine groups Channels Receive-only chan

Zewo 1.5k Dec 22, 2022
Syntactic sugar in Swift for asynchronous dispatches in Grand Central Dispatch

Async Now more than syntactic sugar for asynchronous dispatches in Grand Central Dispatch (GCD) in Swift Async sugar looks like this: Async.userInitia

Tobias Due Munk 4.6k Dec 27, 2022
AwaitKit is a powerful Swift library which provides a powerful way to write asynchronous code in a sequential manner.

AwaitKit is a powerful Swift library inspired by the Async/Await specification in ES8 (ECMAScript 2017) which provides a powerful way to write asynchronous code in a sequential manner.

Yannick Loriot 752 Dec 5, 2022
Elegant ⏱ interface for Swift apps

Each Elegant ⏱ interface for Swift apps Each is a NSTimer bridge library written in Swift. Features Requirements Installation Usage Leaks License Feat

Luca D'Alberti 764 Dec 29, 2022