<commit_before># Operations

[![Build status](https://badge.buildkite.com/4bc80b0824c6357ae071342271cb503b8994cf0cfa58645849.svg?branch=master)](https://buildkite.com/blindingskies/operations)

A Swift framework inspired by WWDC 2015 Advanced NSOperations session. See the session video here: https://developer.apple.com/videos/wwdc/2015/?id=226

## Status - 21st Sept, 2015

The Swift 1.2 compatible version of Operations is version 1.0. This is no longer under active development, however if you’re still stuck on Swift 1.2, and have a bug, please create ticket and I’ll do a point release.

Development from now on is using Swift 2.0, for all of Apple’s platforms as of version 2.1, which adds support for OS X and extension compatible frameworks. watchOS 2.0 and tvOS frameworks are built (if using the correct version of Xcode), however, the current version of Cocoapods (0.38.2) doesn’t yet support these platforms correctly. I’ll be looking into Carthage support in the coming week.

## Usage

`NSOperation` is a class which enables composition of discrete tasks or work for asynchronous execution on an operation queue. It is therefore an abstract class, and `Operation` is a similar abstract class. Therefore, typical usage in your own codebase would be to subclass `Operation` and override `execute`.

For example, an operation to save a `Contact` value in `YapDatabase` might be:

```swift
class SaveContactOperation: Operation {
    typealias CompletionBlockType = Contact -> Void

    let connection: YapDatabaseConnection
    let contact: Contact
    let completion: CompletionBlockType?

    init(connection: YapDatabaseConnection, contact: Contact, completion: CompletionBlockType? = .None) {
        self.connection = connection
        self.contact = contact
        self.completion = completion
        super.init()
        name = “Save Contact: \(contact.displayName)”
    }

    override func execute() {
        connection.asyncWrite(contact) { (returned: Contact) in
            self.completion?(returned)
            self.finish()
        }
    }
}
```

The power of the `Operations` framework however, comes with attaching conditions and observer to operations. For example, perhaps before the user is allowed to delete a `Contact`, we want them to confirm their intention. We can achieve this using the supplied `UserConfirmationCondition`.

```swift
func deleteContact(contact: Contact) {
    let delete = DeleteContactOperation(connection: readWriteConnection, contact: contact)
    let confirmation = UserConfirmationCondition(
        title: NSLocalizedString("Are you sure?", comment: "Are you sure?"),
        message: NSLocalizedString("The contact will be removed from all your devices.", comment: "The contact will be removed from all your devices."),
        action: NSLocalizedString("Delete", comment: "Delete"),
        isDestructive: true,
        cancelAction: NSLocalizedString("Cancel", comment: "Cancel"),
        presentingController: self)
    delete.addCondition(confirmation)
    queue.addOperation(delete)
}
```

When this delete operation is added to the queue, the user will be presented with a standard system `UIAlertController` asking if they're sure. Additionally, other `AlertOperation` instances will be prevented from running.

The above “save contact” operation looks quite verbose for though for a such a simple task. Luckily in reality we can do this:

```swift
let save = ComposedOperation(connection.writeOperation(contact))
save.addObserver(BlockObserver { (_, errors) in
    print(“Did save contact”)
})
queue.addOperation(save)
```

Because sometimes creating an `Operation` subclass is a little heavy handed. Above we composed an existing `NSOperation` but we can utilize a `BlockOperation`. For example, let say we want to warn the user before they cancel a "Add New Contact" controller without saving the Contact. 

```swift
@IBAction didTapCancelButton(button: UIButton) {
    dismiss()
}

func dismiss() {
    // Define a dispatch block for unwinding.
    let dismiss = {
        self.performSegueWithIdentifier(SegueIdentifier.UnwindToContacts.rawValue, sender: nil)
    }

    // Wrap this in a block operation
    let operation = BlockOperation(mainQueueBlock: dismiss)

    // Attach a condition to check if there are unsaved changes
    // this is an imaginary conditon - doesn't exist in Operation framework
    let condition = UnsavedChangesCondition(
        connection: connection,
        value: contact,
        save: save(dismiss),
        discard: BlockOperation(mainQueueBlock: dismiss),
        presenter: self
    )
    operation.addCondition(condition)

    // Attach an observer to see if the operation failed because
    // there were no edits from a default Contact - in which case
    // continue with dismissing the controller.
    operation.addObserver(BlockObserver { [unowned queue] (_, errors) in
        if let error = errors.first as? UnsavedChangesConditionError {
            switch error {
            case .NoChangesFromDefault:
                queue.addOperation(BlockOperation(mainQueueBlock: dismiss))

            case .HasUnsavedChanges:
                break
            }
        }
    })

    queue.addOperation(operation)
}

```

In the above example, we're able to compose reusable (and testable!) units of work in order to express relatively complex control logic. Another way to achieve this kind of behaviour might be through FRP techniques, however those are unlikely to yield re-usable types like `UnsavedChangesCondition`, or even `DismissController` if the above was composed inside a custom `GroupOperation`.

## Device & OS Permissions

Requesting permissions from the user can often be a relatively complex task, which almost all apps have to perform at some point. Often developers put requests for these permissions in their AppDelegate, meaning that new users are bombarded with alerts. This isn't a great experience, and Apple expressly suggest only requesting permissions when you need them. However, this is easier said than done. The Operations framework can help however. Lets say we want to get the user's current location.

```swift
func getCurrentLocation(completion: CLLocation -> Void) {
    queue.addOperation(UserLocationOperation(handler: completion))
}
```

This operation will automatically request the user's permission if the application doesn't already have the required authorization, the default is "when in use".

Perhaps also you want to just test to see if authorization has already been granted, but not ask for it if it hasn't. In Apple’s original sample code from WWDC 2015, there are a number of `OperationCondition`s which express the authorization status for device or OS permissions. Things like, `LocationCondition`, and `HealthCondition`. However, in version 2.3 of Operations I moved away from this model to unify this functionality into th `CapabilityType` protocol. Where previously there were bespoke conditions (and errors) to test the status, there is now a single condition, which is initialized with a `CapabilityType`. 

For example, where previously you would have written this:

```swift
operation.addCondition(LocationCondition(usage: .WhenInUse))
```

now you write this:

```swift
operation.addCondition(AuthorizedFor(Capability.Location(.WhenInUse)))
```

As of 2.3 the following capabilities are expressed:

- [x] `Capability.Calendar` - includes support for `EKEntityType` requirements, defaults to `.Event`.
- [x] `Capability.Cloud` - includes support for default `CKContainer` or with specific identifier, and `.UserDiscoverable` cloud container permissions.
- [x] `Capability.Health` - improved support for exactly which `HKObjectType`s have read permissions. Please get in touch if you want to use HealthKit with Operations, as I currently don't, and would like some feedback or input on further improvements that can be made for HealthKit.
- [x] `Capability.Location` - includes support for required usage, either `.WhenInUse` (the default) or `.Always`.
- [x] `Capability.Passbook` - note here that there is void "status" type, just an availability boolean.
- [x] `Capability.Photos` 

In addition to a generic operation condition, which can be used as before with `SilentCondition` and `NegatedCondition`. There are also two capability generic operations. `GetAuthorizationStatus` will retrieve the current status of the capability. `Authorize` will explicity request the required permissions for the capability. Both of these operations accept the capability as their first initializer argument without a label, and have a completion block with the same type. Therefore, it is trivial to write a function which can update your controller or UI and use it for both operations.

For example here we can check the status of location services for the app:

```swift
func locationServicesEnabled(enabled: Bool, withAuthorization status: CLAuthorizationStatus) {
    switch (enabled, status) {
        case (false, _):
           // Location services are not enabled
        case (true, .NotDetermined):
           // Location services are enabled, but not currently determined for the app.
    }<commit_msg>[release]: Updates the README.<commit_after># Operations

[![Build status](https://badge.buildkite.com/4bc80b0824c6357ae071342271cb503b8994cf0cfa58645849.svg?branch=master)](https://buildkite.com/blindingskies/operations)

A Swift framework inspired by WWDC 2015 Advanced NSOperations session. See the session video here: https://developer.apple.com/videos/wwdc/2015/?id=226

## Status - 6th Oct, 2015

As of version 2.1, Operations is a multi-platform framework, with framework targets for iOS Extensions, iOS Apps, OS X, watchOS and tvOS. Version 2.2 adds initial support for Contacts framework in iOS 9 and El Capitan. It also introduces the concept of CapabilityTypes, meaning that many device permission conditions have been refactored. See the release notes, and below or #97 for details.

Current development is focused on unifying APIs for AddressBook. This might well cause some disruption to your current code if using the AddressBook operations, hence I’m spreading these changes across two releases. 

## Usage

`NSOperation` is a class which enables composition of discrete tasks or work for asynchronous execution on an operation queue. It is therefore an abstract class, and `Operation` is a similar abstract class. Therefore, typical usage in your own codebase would be to subclass `Operation` and override `execute`.

For example, an operation to save a `Contact` value in `YapDatabase` might be:

```swift
class SaveContactOperation: Operation {
    typealias CompletionBlockType = Contact -> Void

    let connection: YapDatabaseConnection
    let contact: Contact
    let completion: CompletionBlockType?

    init(connection: YapDatabaseConnection, contact: Contact, completion: CompletionBlockType? = .None) {
        self.connection = connection
        self.contact = contact
        self.completion = completion
        super.init()
        name = “Save Contact: \(contact.displayName)”
    }

    override func execute() {
        connection.asyncWrite(contact) { (returned: Contact) in
            self.completion?(returned)
            self.finish()
        }
    }
}
```

The power of the `Operations` framework however, comes with attaching conditions and observer to operations. For example, perhaps before the user is allowed to delete a `Contact`, we want them to confirm their intention. We can achieve this using the supplied `UserConfirmationCondition`.

```swift
func deleteContact(contact: Contact) {
    let delete = DeleteContactOperation(connection: readWriteConnection, contact: contact)
    let confirmation = UserConfirmationCondition(
        title: NSLocalizedString("Are you sure?", comment: "Are you sure?"),
        message: NSLocalizedString("The contact will be removed from all your devices.", comment: "The contact will be removed from all your devices."),
        action: NSLocalizedString("Delete", comment: "Delete"),
        isDestructive: true,
        cancelAction: NSLocalizedString("Cancel", comment: "Cancel"),
        presentingController: self)
    delete.addCondition(confirmation)
    queue.addOperation(delete)
}
```

When this delete operation is added to the queue, the user will be presented with a standard system `UIAlertController` asking if they're sure. Additionally, other `AlertOperation` instances will be prevented from running.

The above “save contact” operation looks quite verbose for though for a such a simple task. Luckily in reality we can do this:

```swift
let save = ComposedOperation(connection.writeOperation(contact))
save.addObserver(BlockObserver { (_, errors) in
    print(“Did save contact”)
})
queue.addOperation(save)
```

Because sometimes creating an `Operation` subclass is a little heavy handed. Above we composed an existing `NSOperation` but we can utilize a `BlockOperation`. For example, let say we want to warn the user before they cancel a "Add New Contact" controller without saving the Contact. 

```swift
@IBAction didTapCancelButton(button: UIButton) {
    dismiss()
}

func dismiss() {
    // Define a dispatch block for unwinding.
    let dismiss = {
        self.performSegueWithIdentifier(SegueIdentifier.UnwindToContacts.rawValue, sender: nil)
    }

    // Wrap this in a block operation
    let operation = BlockOperation(mainQueueBlock: dismiss)

    // Attach a condition to check if there are unsaved changes
    // this is an imaginary conditon - doesn't exist in Operation framework
    let condition = UnsavedChangesCondition(
        connection: connection,
        value: contact,
        save: save(dismiss),
        discard: BlockOperation(mainQueueBlock: dismiss),
        presenter: self
    )
    operation.addCondition(condition)

    // Attach an observer to see if the operation failed because
    // there were no edits from a default Contact - in which case
    // continue with dismissing the controller.
    operation.addObserver(BlockObserver { [unowned queue] (_, errors) in
        if let error = errors.first as? UnsavedChangesConditionError {
            switch error {
            case .NoChangesFromDefault:
                queue.addOperation(BlockOperation(mainQueueBlock: dismiss))

            case .HasUnsavedChanges:
                break
            }
        }
    })

    queue.addOperation(operation)
}

```

In the above example, we're able to compose reusable (and testable!) units of work in order to express relatively complex control logic. Another way to achieve this kind of behaviour might be through FRP techniques, however those are unlikely to yield re-usable types like `UnsavedChangesCondition`, or even `DismissController` if the above was composed inside a custom `GroupOperation`.

## Device & OS Permissions

Requesting permissions from the user can often be a relatively complex task, which almost all apps have to perform at some point. Often developers put requests for these permissions in their AppDelegate, meaning that new users are bombarded with alerts. This isn't a great experience, and Apple expressly suggest only requesting permissions when you need them. However, this is easier said than done. The Operations framework can help however. Lets say we want to get the user's current location.

```swift
func getCurrentLocation(completion: CLLocation -> Void) {
    queue.addOperation(UserLocationOperation(handler: completion))
}
```

This operation will automatically request the user's permission if the application doesn't already have the required authorization, the default is "when in use".

Perhaps also you want to just test to see if authorization has already been granted, but not ask for it if it hasn't. In Apple’s original sample code from WWDC 2015, there are a number of `OperationCondition`s which express the authorization status for device or OS permissions. Things like, `LocationCondition`, and `HealthCondition`. However, in version 2.2 of Operations I moved away from this model to unify this functionality into th `CapabilityType` protocol. Where previously there were bespoke conditions (and errors) to test the status, there is now a single condition, which is initialized with a `CapabilityType`. 

For example, where previously you would have written this:

```swift
operation.addCondition(LocationCondition(usage: .WhenInUse))
```

now you write this:

```swift
operation.addCondition(AuthorizedFor(Capability.Location(.WhenInUse)))
```

As of 2.2 the following capabilities are expressed:

- [x] `Capability.Calendar` - includes support for `EKEntityType` requirements, defaults to `.Event`.
- [x] `Capability.Cloud` - includes support for default `CKContainer` or with specific identifier, and `.UserDiscoverable` cloud container permissions.
- [x] `Capability.Health` - improved support for exactly which `HKObjectType`s have read permissions. Please get in touch if you want to use HealthKit with Operations, as I currently don't, and would like some feedback or input on further improvements that can be made for HealthKit.
- [x] `Capability.Location` - includes support for required usage, either `.WhenInUse` (the default) or `.Always`.
- [x] `Capability.Passbook` - note here that there is void "status" type, just an availability boolean.
- [x] `Capability.Photos` 

In addition to a generic operation condition, which can be used as before with `SilentCondition` and `NegatedCondition`. There are also two capability generic operations. `GetAuthorizationStatus` will retrieve the current status of the capability. `Authorize` will explicity request the required permissions for the capability. Both of these operations accept the capability as their first initializer argument without a label, and have a completion block with the same type. Therefore, it is trivial to write a function which can update your controller or UI and use it for both operations.

For example here we can check the status of location services for the app:

```swift
func locationServicesEnabled(enabled: Bool, withAuthorization status: CLAuthorizationStatus) {
    switch (enabled, status) {
        case (false, _):
           // Location services are not enabled
        case (true, .NotDetermined):
           // Location services are enabled, but not currently determined for the app.
    }
{'fraction_non_alphanumeric': 0.06887546923453566, 'fraction_numerical': 0.00783417659539742, 'mean_word_length': 4.13463687150838, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7670135', 'n_tokens_mistral': 4620, 'n_tokens_neox': 4363, 'n_words': 2137}