Un cadre rapide inspiré de la session avancée de NSOperations WWDC 2015. Auparavant connu sous le nom d' opérations , développé par @danthorpe avec beaucoup d'aide de notre fantastique communauté.
| Ressource | Où le trouver |
|---|---|
| Vidéo de session | développeur.apple.com |
| Documentation de référence ancienne mais plus complète | docs.danthorpe.me/Operations |
| Docs de référence mis à jour mais pas encore complets | procédure.kit.run/development |
| Guide de programmation | opérations.readme.io |
ProcedureKit prend en charge toutes les plates-formes Apple actuelles. Les exigences minimales sont:
La version publiée actuelle de ProcedureKit (5.1.0) prend en charge Swift 4.2+ et Xcode 10.1. La branche development est compatible Swift 5 et Xcode 10.2.
ProcedureKit est un framework "multi-module" (ne vous embêtez pas à googler cela, je viens de le faire). Ce que je veux dire, c'est que le projet Xcode a plusieurs cibles / produits dont chacun produit un module Swift. Certains de ces modules sont multiplateformes, d'autres sont dédiés, par exemple ProcedureKitNetwork vs ProcedureKitMobile .
Voir le guide de procédure d'installation.
Procedure est une base Foundation.Operation . Il s'agit d'une classe abstraite qui doit être sous-classée.
import ProcedureKit
class MyFirstProcedure : Procedure {
override func execute ( ) {
print ( " Hello World " )
finish ( )
}
}
let queue = ProcedureQueue ( )
let myProcedure = MyFirstProcedure ( )
queue . add ( procedure : myProcedure )Les points clés ici sont:
Procedure de sous-classeexecute mais n'appelez pas super.execute()finish() une fois les travaux terminés, ou si la procédure est annulée. Cela pourrait être fait de manière asynchrone.ProcedureQueue . Les observateurs sont attachés à une sous-classe Procedure . Ils reçoivent des rappels lorsque des événements de cycle de vie se produisent. Les événements de cycle de vie sont: DIET attachés , exécutera , fait l'exécution , fait annuler , ajoutera une nouvelle opération , ajouté une nouvelle opération , terminera et terminée .
Ces méthodes sont définies par un protocole, de sorte que des classes personnalisées peuvent être écrites pour se conformer à plusieurs événements. Cependant, des méthodes basées sur des blocs existent pour ajouter des observateurs plus naturellement. Par exemple, pour observer lorsqu'une procédure se termine:
myProcedure . addDidFinishBlockObserver { procedure , errors in
procedure . log . info ( message : " Yay! Finished! " )
} Le framework fournit également BackgroundObserver , TimeoutObserver et NetworkObserver .
Voir le wiki sur [[observateurs | observateurs]] pour plus d'informations.
Les conditions sont fixées à une sous-classe Procedure . Avant qu'une procédure ne soit prête à l'exécuter, elle évaluera de manière asynchrone toutes ses conditions. Si une condition échoue, il se termine par une erreur au lieu d'exécuter. Par exemple:
myProcedure . add ( condition : BlockCondition {
// procedure will execute if true
// procedure will be ignored if false
// procedure will fail if error is thrown
return trueOrFalse // or throw AnError()
}Les conditions peuvent être mutuellement exclusives. Cela s'apparente à une serrure maintenue empêchant d'autres opérations avec la même exclusion exécutée.
Le cadre fournit les conditions suivantes: AuthorizedFor , BlockCondition , MutuallyExclusive , NegatedCondition , NoFailedDependenciesCondition , SilentCondition et UserConfirmationCondition (dans Procedurekitmobile ).
Voir le wiki sur [[Conditions | Conditions]], ou l'ancien guide de programmation sur les conditions | pour plus d'informations.
Une capacité représente la capacité de l'application à accéder à des capacités de périphérique ou de compte utilisateur, ou potentiellement tout type de ressource fermée. Par exemple, les services de localisation, les conteneurs de kit cloud, les calendriers, etc. ou un service Web. Le CapabiltiyProtocol fournit un modèle unifié pour:
GetAuthorizationStatusProcedure ,AuthorizeCapabilityProcedureAuthorizedFor .Par exemple:
import ProcedureKit
import ProcedureKitLocation
class DoSomethingWithLocation : Procedure {
override init ( ) {
super . init ( )
name = " Location Operation "
add ( condition : AuthorizedFor ( Capability . Location ( . whenInUse ) ) )
}
override func execute ( ) {
// do something with Location Services here
finish ( )
}
} ProcedureKit fournit les capacités suivantes: Capability.CloudKit et Capability.Location .
Dans les opérations , (une version précédente de ce cadre), plus de fonctionnalités existaient (calendrier, santé, photos, carnet d'adresses, etc.), et nous envisageons toujours de les offrir dans ProcedureKit .
Voir le wiki sur [[Capacités | Capacités]], ou l'ancien guide de programmation sur les capacités pour plus d'informations.
Procedure a sa propre fonctionnalité de journalisation interne exposée via une propriété log :
class LogExample : Procedure {
override func execute ( ) {
log . info ( " Hello World! " )
finish ( )
}
}Voir le guide de programmation pour plus d'informations sur la journalisation et la prise en charge des cadres journaux tiers.
Souvent, les procédures nécessiteront des dépendances pour s'exécuter. Comme c'est typique des applications asynchrones / basées sur des événements, ces dépendances pourraient ne pas être connues au moment de la création. Au lieu de cela, ils doivent être injectés après l'initialisation de la procédure, mais avant qu'il ne soit exécuté. ProcedureKit prend en charge cela via un ensemble de protocoles et de types qui fonctionnent ensemble. Nous pensons que ce schéma est excellent, car il encourage la composition des procédures à petit but unique. Ceux-ci peuvent être plus faciles à tester et potentiellement permettre une réutilisation plus importante. Vous trouverez l'injection de dépendance utilisée et encouragée tout au long de ce cadre.
Quoi qu'il en soit, tout d'abord, une valeur peut être prête ou en attente. Par exemple, lorsqu'une procédure est initialisée, elle peut ne pas avoir toutes ses dépendances, donc elles sont dans un état en attente. J'espère qu'ils se préparent au moment où il s'exécute.
Deuxièmement, si une procédure acquiert la dépendance requise par une autre procédure, elle peut réussir, ou elle peut échouer avec une erreur. Par conséquent, il existe un type de résultat simple qui le prend en charge.
Troisièmement, il existe des protocoles pour définir les propriétés input et output .
InputProcedure associe un type Input . Une sous-classe Procedure peut être conforme à cela pour permettre l'injection de dépendance. Remarque, une seule propriété input est prise en charge, donc, créez des types de structures intermédiaires pour contenir plusieurs dépendances. Bien sûr, la propriété input est un type de valeur en attente.
OutputProcedure expose le type Output associé via sa propriété output , qui est un type de résultat en attente.
Le rapprochement est un ensemble d'API sur InputProcedure qui permet les dépendances de chaînage ensemble. Comme ça:
import ProcedureKitLocation
// This class is part of the framework, it
// conforms to OutputProcedure
let getLocation = UserLocationProcedure ( )
// Lets assume we've written this, it
// conforms to InputProcedure
let processLocation = ProcessUserLocation ( )
// This line sets up dependency & injection
// it automatically handles errors and cancellation
processLocation . injectResult ( from : getLocation )
// Still need to add both procedures to the queue
queue . add ( procedures : getLocation , processLocation ) Dans ce qui précède, il est supposé que le type Input correspondait au type Output , dans ce cas, CLLocation . Cependant, il est également possible d'utiliser une fermeture pour masser le type de sortie au type d'entrée requis, par exemple:
import ProcedureKitLocation
// This class is part of the framework, it
// conforms to OutputProcedure
let getLocation = UserLocationProcedure ( )
// Lets assume we've written this, it
// conforms to InputProcedure, and
// requires a CLLocationSpeed value
let processSpeed = ProcessUserSpeed ( )
// This line sets up dependency & injection
// it automatically handles errors and cancellation
// and the closure extracts the speed value
processLocation . injectResult ( from : getLocation ) { $0 . speed }
// Still need to add both procedures to the queue
queue . add ( procedures : getLocation , processLocation ) D'accord, alors qu'est-ce qui vient de se passer? Eh bien, l'API injectResult a une variante qui accepte une fermeture de fuite. La fermeture reçoit la valeur de sortie et doit renvoyer la valeur d'entrée (ou lancer une erreur). Ainsi, { $0.speed } renvoie la propriété de vitesse de l'instance CLLocation de l'utilisateur.
La clé à noter ici est que cette fermeture fonctionne de manière synchrone. Il est donc préférable de ne rien mettre onéreux. Si vous devez effectuer des mappages de données plus complexes, consultez TransformProcedure et AsyncTransformProcedure .
Voir le guide de programmation sur l'injection des résultats pour plus d'informations.