5.0.0 Beta 1
5.0.0
This is a rather long-awaited next major version of ProcedureKit.
Headline Changes
- Networking procedures no longer use an associated type for the
URLSession. InsteadSessionis a free-floating protocol. This makes general usage, subclassing and composing much simpler. - There is now a Core Data module
BlockProcedureAPI has changed.Procedureonly supports a singleErrorvalue, instead of[Error]- this has had some fairly wide reaching changes to APIs.- New built-in logger, which uses
os_logby default. - Changes to
UIProcedurein ProcedureKitMobile module.
Breaking Changes
-
[823]: Removes associated types from Network
Originally raised as an issue by @ericyanush in which I totally missed the point initially. But, after thinking about it more, made so much sense. Instead of having a generic
URLSessionTaskFactoryprotocol, where the various types of tasks were associated types, we now just have a non-genericNetworkSessionprotocol, to whichURLSessionconforms. The impact of this subtle change, is that what was once:NetworkDataProcedure<Session: URLSessionTaskFactory>is nowNetworkDataProcedure. In otherwords, no longer generic, and now super easy to use as that genericSessiondoesn't leak all over the place. -
[#875]: Refactored
BlockProcedureThere has been a long-standing wish for
BlockProcedureinstances to "receive themselves" in their block to allow for access to its logger etc. In v5, the following is all possible, see this comment:-
Simple synchronous block (existing functionality):
let block = BlockProcedure { print("Hello World") } -
Synchonous block, accessing the procedure inside the block:
let block = BlockProcedure { this in this.log.debug.message("Hello World") this.finish() }Note that here, the block is responsible for finishing itself - i.e. call
.finish()or.finish(with:)to finish the Procedure. Using this initializer, by default,BlockProcedurewill add aTimeoutObserverto itself, usingdefaultTimeoutIntervalwhich is set to 3 seconds. This can be modified if needed.BlockProcedure.defaultTimeoutInterval = 5 -
Asynchronous block with cancellation check,
AsyncBlockProcedureandCancellableBlockProcedureget deprecated warnings.let block = BlockProcedure { this in guard !this.isCancelled else { this.finish() } DispatchQueue.default.async { print("Hello world") this.finish() } } -
ResultProcedureas been re-written as a subclass ofBlockProcedure(previously, it was the superclass). Existing functionality has been maintained:let hello = ResultProcedure { "Hello World" }
-
-
[#851]: Errors
At WWDC18 I spent some time with some Swift engineers from Apple talking about framework design and error handling. The key take-away from these discussions was to increase clarity which reduces confusion, and makes intent clear.
This theme drove some significant changes. To increase clarity, each Procedure can only have a single
Error, because ultimately, how can a framework consumer "handle" an array ofErrorvalues over just a single one? I realised that the only reasonProcedurehas an[Error]property at all was fromGroupProcedurecollecting all of the errors from its children, yet the impact of this is felt throughout the codebase.This means, to finish a procedure with an error, use:
finish(with: .downloadFailedError) // this is a made up error typeObservers only receive a single error now:
procedure.addDidFinishBlockObserver { (this, error) in guard let error = error else { // there is an error, the block argument is Error? type return } // etc }Plus more API changes in
ProcedureandGroupProcedurewhich will result in deprecation warnings for framework consumers.For
GroupProcedureitself, it will now only set its own error to the first error received. However, to access the errors from child procedures, use the.childrenproperty. Something like:let errors = group.children.operationsAndProcedures.1.compactMap { $0.error } -
ProcedureKit has its own logging system, which has received an overhawl in v5. The changes are:
1. Now uses `os_log` instead of `print()` where available.2. Dedicated severity levels for caveman debugging & user event. See this comment. 3. Slight API change:
swift procedure.log.info.message("This is my debug message")previously, it was:swift procedure.log.info("This is my debug message")For module-wide settings:swift Log.enabled = true Log.severity = .debug // default is .warning Log.writer = CustomLogWriter() // See LogWriter protocol Log.formatter = CustomLogFormatter() // See LogFormatter protocol -
[#860]: Swift 3/4 API naming & conventions
@lukeredpath initially raised the issue in #796, that some APIs such as
add(condition: aCondition)did not Swift 3/4 API guidelines, and contributed to inconsistency within the framework. These have now been tidied up.
New Features & Improvements
-
[#830, #837]: Swift 4.1 & Xcode 9.3 support, (Xcode 10 is ready to go).
These changes take advantage of Swift 4.1 capabilities, such as synthesized
Equatableand conditional conformance. -
[#828, #833]: Result Injection & Binding
Result Injection conformance is added to
RepeatProcedure(and subclasses such asRetryProcedure&NetworkProcedure). This means the input can be set on the outRepeatProcedure, and this value will be set on every instance of the target procedure (assuming it also conforms toInputProcedure). This avoids having to jump through hoops like this.Additionally, a new binding API can be used, particularly with
GroupProceduresubclasses, so that the input of a child procedure is "bound" to that of the group itself, likewise, the output of the group is bound to a child. This makes it very easy to encapsulate a chain of procedures which use result injection into aGroupProceduresubclass. See the docs.
Notes
Thanks to everyone who has contributed to ProcedureKit - v5 has been quite a while in development. There is still quite a bit left to do on the documentation effort - but that will be ongoing for evermore.