@danthorpe danthorpe released this Feb 17, 2016 · 564 commits to development since this release

Assets 2


🚀 This release contains quite a few changes, with over 230 commits with input from 11 contributors! Thanks! 😀🎉

A note on quality: test coverage has increased from 64% in v2.5 to 76%. The code which remains untested is either untestable (fatalError etc) or is due for deletion or deprecation such as AddressBookCondition etc.

New Operations

  1. [OPR-150]: MapOperation, FilterOperation and ReduceOperation For advanced usage.

    These operations should be used in conjunction with ResultOperationType which was introduced in v2.5.0. Essentially, given an receiving operation, conforming to ResultOperationType, the result of mapping, filtering, or reducing the receiver’s result can be returned as the result of another operation, which also conforms to ResultOperationType. This means that it can be trivial to map the results of one operation inside another.

    It is suggested that this is considered for advanced users only as it’s pretty subtle behavior.

  2. [OPR-154, OPR-168]: RepeatedOperation

    The RepeatedOperation is a GroupOperation subclass which can be used in conjunction with a generator to schedule NSOperation instances. It is useful to remember that NSOperation is a “one time only” class, meaning that once an instance finishes, it cannot be re-executed. Therefore, it is necessary to construct repeatable operations using a closure or generator.

    This is useful directly for periodically running idempotent operations. It also forms the basis for operation types which can be retried.

    The operations may optionally be scheduled after a delay has passed, or a date in the future has been reached.

    At the lowest level, which offers the most flexibility, RepeatedOperation is initialized with a generator. The generator (something conforming to GeneratorType) element type is (Delay?, T), where T is a NSOperation subclass, and Delay is an enum used in conjunction with DelayOperation.

    RepeatedOperation can also be initialized with a simple () -> T? closure and WaitStrategy. The strategy offers standardized delays such as .Random and .ExpoentialBackoff, and will automatically create the appropriate Delay.

    RepeatedOperation can be stopped by returning nil in the generator, or after a maximum count of operations, or by calling cancel().

    Additionally, a RepeatableOperation has been included, which composes an Operation type, and adds convenience methods to support whether or not another instance should be scheduled based on the previous instance.

  3. [OPR-154, OPR-161, OPR-168]: RetryOperation

    RetryOperation is a subclass of RepeatedOperation, except that instead of repeating irrespective of the finishing state of the previous instance, RetryOperation only repeats if the previous instance finished with errors.

    Additionally, RetryOperation is initialized with an “error recovery” block. This block receives various info including the errors from the previous instance, the aggregate errors so far, a LoggerType value, plus the suggested (Delay, T?) tuple. This tuple is the what the RetryOperation would execute again without any intervention. The error block allows the consumer to adjust this, either by returning .None to not retry at all, or by modifying the return value.

  4. [OPR-160, OPR-165, OPR-167]: CloudKitOperation 2.0

    Technically, this work is a refactor of CloudKitOperation, however, because it’s a major overhaul it is best viewed as completely new.

    CloudKitOperation is a subclass of RetryOperation, which composes the CKOperation subclass inside a ReachableOperation.

    CloudKitOperation can be used to schedule CKOperation subclasses. It supports configuration of the underlying CKOperation instance “through” the outer CloudKitOperation, where the configuration applied is stored and re-applied on new instances in the event of retrying. For example, below

    // Modify CloudKit Records
    let operation = CloudKitOperation { CKModifyRecordsOperation() }
    // The user must be logged into iCloud 
    // Configure the container & database
    operation.container = container
    operation.database = container.privateCloudDatabase
    // Set the records to save
    operation.recordsToSave = [ recordOne, recordTwo ]
    // Set the policy
    operation.savePolicy = .ChangedKeys
    // Set the completion
    operation.setModifyRecordsCompletionBlock { saved, deleted in
        // Only need to manage the happy path here

    In the above example, all the properties set on operation are saved into an internal configuration block. This is so that it in the case of retrying after an error, the same configuration is applied to the new CKOperation instance returned from the generator. The same could also be achieved by setting these properties inside the initial block, however the completion block above must be called to setup the CloudKitOperation correctly.

    Thanks to RetryOperation, CloudKitOperation supports some standardized error handling for common errors. For example, if Apple’s CloudKit service is unavailable, your operation will be automatically re-tried with the correct delay. Error handling can be set for individual CKErrorCode values, which can replace the default handlers if desired.

    CKOperation subclasses also all have completion blocks which receives the result and an optional error. As discussed briefly above, CloudKitOperation provides this completion block automatically when the consumer sets the “happy path” completion block. The format of this function is always set<Name of the CKOperation completion block>() This means, that it is only necessary to set a block which is executed in the case of no error being received.

    BatchedCloudKitOperation is a RepeatedOperation subclass which composed CloutKitOperation instances. It can only be used with CKOperation subclasses which have the notion of batched results.

    See the class header, example projects, blog posts and (updated) guide for more documentation. This is significant change to the existing class, and should really be viewed as entirely new. Please get in touch if you were previously using CloudKitOperation prior to this version, and are now unsure how to proceed. I’m still working on improving the documentation & examples for this class.

Examples & Documentation

  1. [OPR-169]: Last Opened example project

    Last Opened, is the start of an iOS application which will demonstrate how to use the new CloudKitOperation. At the moment, it is not exactly complete, but it does show some example. However, the application does not compile until the correct development team & bundle id is set.

  2. [OPR-171]: CloudKitOperation documentation

    There is now quite a bit of public interface documentation. Still working on updating the programming guide right now.

Operation Changes

  1. [OPR-152]: Adding Conditions & Observers

    When adding conditions and observers, we sanity check the state of the operation as appropriate. For adding a Condition, the operation must not have started executing. For adding an Observer, it now depends on the kind, for example, it is possible to add a OperationDidFinishObserver right up until the operation enters its .Finishing state.

  2. [OPR-147]: Scheduling of Operations from Conditions

    When an Operation has dependencies and also has Conditions attached which also have dependencies, the scheduling of these dependencies is now well defined. Dependencies from Conditions are referred to as indirect dependencies versus direct for dependencies added normally.

    The indirect dependencies are now scheduled after all the direct dependencies finish. See original issue and the pull request for further explanation including a diagram of the queue.

  3. [OPR-129]: Dependencies of mutually exclusive Conditions.

    If a Condition is mutually exclusive, the OperationQueue essentially adds a lock on the associated Operation. However, this previously would lead to unexpected scheduling of that condition had a dependency operation. Now, the “lock” is placed on the dependency of the condition instead of the associated operation, but only if it’s not nil. Otherwise, standard behavior is maintained.

  4. [OPR-162]: Refactor of ComposedOperation and GatedOperation

    Previously, the hierarchy of these two classes was all mixed up. ComposedOperation has been re-written to support both Operation subclasses and NSOperation subclasses. When a NSOperation (but not Operation) subclass is composed, it is scheduled inside its own GroupOperation. However, if composing an Operation subclass, instead we “produce” it and use observers to finish the ComposedOperation correctly.

    Now, GatedOperation is a subclass of ComposedOperation with the appropriate logic.

  5. [OPR-163, OPR-171, OPR-179]: Refactor of ReachableOperation

    ReachableOperation now subclasses ComposedOperation, and uses SCNetworkReachablity callbacks correctly.

  6. [OPR-187]: Sanity check produceOperation(). Thanks to @bastianschilbe for this fix. Now the Operation must at least have passed the .Initialized state before produceOperation() can be called.

Project Configurations

  1. [OPR-182]: Extension Compatible

    Updates the extension compatible Xcode project. Sorry this got out of sync for anyone who was trying to get it to work!

Bug Fixes!

  1. [OPR-186, OPR-188]: Ensures UIOperation finishes correctly.

  2. [OPR-180]: Completion Blocks.

    Changes in this pull request improved the stability of working with OperationConditions attached to Operation instances. However, there is still a bug which is potentially an issue with KVO.

    Currently it is suggested that the completionBlock associated with NSOperation is avoid. Other frameworks expressly forbid its usage, and there is even a Radar from Dave De Long recommending it be deprecated.

    The original issue, #175 is still being tracked.

  3. [OPR-181]: Fixes a bug in GroupOperation where many child operations which failed could cause a crash. Now access to the aggregateErrors property is thread safe, and this issue is tested with a tight loop of 10,000 child operations which all fail. Thanks to @ansf for reporting this one.


I want to say a huge thank you to everyone who has contributed to this project so far. Whether you use the framework in your apps (~ 90 apps, 6k+ downloads via CocoaPods metrics), or you’ve submitted issues, or even sent me pull requests - thanks!

I don’t think I’d be able to find anywhere near the number of edge cases without all the help. The suggestions and questions from everyone keeps me learning new stuff.


What’s next?

I’ve not got anything too major planned right now, except improving the example projects. So the next big thing will probably be Swift 3.0 support, and possibly ditching NSOperation.