Solution facile/simple
Le point d'entrée commun d'une application est le délégué d'une application. applicationDidFinishLaunching
. Nous pourrions simplement ajouter une fonction statique à chaque classe que nous voulons notifier à l'initialisation, et l'appeler d'ici.
Cette première solution est simple et facile à comprendre. Pour la plupart des cas, c'est ce que je recommanderais. Bien que la solution suivante fournisse des résultats plus proches de ceux de la solution originale, elle n'est pas recommandée. initialize()
mais le temps de démarrage de l'application s'en trouve légèrement allongé. Je ne pense plus que cela vaut l'effort, la dégradation des performances ou la complexité du code dans la plupart des cas. Un code simple est un bon code.
Lisez la suite pour une autre option. Vous aurez peut-être des raisons d'en avoir besoin (ou peut-être des parties de celle-ci).
Une solution pas si simple
La première solution n'est pas forcément adaptée. Et que se passe-t-il si vous construisez un framework, où vous souhaitez que votre code s'exécute sans que personne n'ait besoin de l'appeler depuis le délégué de l'application ?
Première étape
Définissez le code Swift suivant. L'objectif est de fournir un point d'entrée simple pour toute classe que vous souhaitez doter d'un comportement similaire à celui de la classe initialize()
- cela peut maintenant être fait simplement en se conformant à SelfAware
. Il fournit également une fonction unique pour exécuter ce comportement pour chaque classe conforme.
protocol SelfAware: class {
static func awake()
}
class NothingToSeeHere {
static func harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
types.deallocate(capacity: typeCount)
}
}
Deuxième étape
Tout cela est bien beau, mais il nous faut encore un moyen d'exécuter la fonction que nous avons définie, c'est-à-dire NothingToSeeHere.harmlessFunction()
au démarrage de l'application. Auparavant, cette réponse suggérait d'utiliser le code Objective-C pour ce faire. Cependant, il semble que nous puissions faire ce dont nous avons besoin en utilisant uniquement Swift. Pour macOS ou d'autres plateformes où UIApplication n'est pas disponible, une variation de ce qui suit sera nécessaire.
extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
UIApplication.runOnce
return super.next
}
}
Troisième étape
Nous disposons maintenant d'un point d'entrée au démarrage de l'application et d'un moyen d'y accéder à partir des classes de votre choix. Tout ce qu'il reste à faire : au lieu d'implémenter initialize()
, se conformer à SelfAware
et mettre en œuvre la méthode définie, awake()
.
1 votes
Pour être plus précis, le méthode
+initialize
est exécuté avant que quoi que ce soit dans la classe soit utilisé. En réalité, elle est exécutée lorsque le paquet qui contient la classe est chargé, ce qui peut arriver après le démarrage de l'application.0 votes
@AminNegm-Awad : Je ne suis pas sûr que ce soit correct. Peut-être pensez-vous à la
load
(qui n'est pas disponible en Swift) ?1 votes
Il en est de même pour
+initialize
. Le runtime envoie initialize à chaque classe d'un programme juste avant que la classe, ou toute classe qui en hérite, reçoive son premier message depuis le programme. Il n'y a aucune garantie que cela soit fait au début du programme. Une telle garantie entraînerait un chargement immédiat de tous les paquets afin d'obtenir le code en+initialize
. - La différence avec+load
c'est-à-dire qu'il est exécuté sur des classes et les catégories . (Méthodes avec le même sélecteur dans la classe et la catégorie !) Mais, bien sûr, le fait de l'invocation tardive possible est plus important pour les catégories que pour les classes.0 votes
@AminNegm-Awad : J'ai peut-être mal compris votre premier commentaire. Vous avez dit qu'en réalité,
initialize
est appelé lorsque le paquet ... est chargé. Ce n'est pas ce que j'ai constaté.0 votes
Peut-être. L'OP a dit, que
+initialize
est exécuté au démarrage de l'application. Mais ce n'est pas le cas si un bundle est chargé après le démarrage de l'application. Dans ce cas, l'heure d'exécution la plus précoce de la commande+initialize
c'est-à-dire lorsque le paquet est chargé. (Évidemment+initialize
ne peut être exécuté avant que le bundle qui le contient ne soit chargé). Je suis d'accord que la plupart des développeurs n'ont jamais fait cette expérience. C'est parce que vous avez besoin d'une situation dans laquelle un bundle est chargé après le démarrage de l'application. C'est rarement le cas. Et le moment du chargement des bundles est un détail de mise en œuvre. Vous ne pouvez tout simplement pas compter sur le chargement de tous les bundles au démarrage de l'application.0 votes
Pour avoir un exemple : J'ai écrit un framework AOP pour Objective-C. Comme la logique métier ne doit pas faire référence aux aspects, j'avais besoin d'un moyen d'installer automatiquement les conseils. Évidemment,
+initialize
(AOPSomeBaseClass
) serait un bon endroit pour le faire. Je m'attendais et j'ai fait l'expérience que cette méthode n'est jamais exécutée, parce que la logique métier n'a pas intentionnellement fait référence au framework et que le framework n'a jamais été chargé, ce qui fait que la méthode n'a jamais été exécutée. Vous devez donc donner un coup de pouce. Puisque vous comprenez l'allemand, je vous renvoie à l'exposé : macoun.de/video2009/ts6.php0 votes
@AminNegm-Awad pour être très techniquement correct,
initialize()
est appelé sur toute classe NSObject avant que le premier message qui lui est destiné ne soit envoyé. Dans le cas d'une utilisation de méthode (la raison courante d'utiliser la méthodeinitialize()
en Swift), cela n'a pas d'importance - l'échange de méthodes se fera avant l'envoi de tout message, donc tout va bien.1 votes
Oui, comme je l'ai dit il y a des heures. Mais cela compte parfois, c'est-à-dire dans le cas que j'ai mentionné. Cependant, il n'est pas garanti qu'il soit envoyé à l'objet de classe (en Objective-C, il n'existe pas de fonctions membres comme
initialize()
et ils ne sont pas appelés, mais des messages sont envoyés) au début de l'application.0 votes
@AminNegm-Awad (content de te voir btw) - Je pense que l'une des choses légitimes que vous voulez faire dans initialize est de mettre NSApplicationCrashOnExceptions à true (NSUserDefaults). Sinon, votre application ne se plantera pas et donc les outils de rapport de crash ne pourront pas fonctionner correctement si ce drapeau est activé trop tard.
0 votes
@ChristianKienle Salut Chris :-) Oui, quelque chose comme ça ou autre chose. Dans la première version de la Q, il était indiqué que
+initialize
est exécuté au démarrage de l'application. Je voulais juste corriger cela, car ce n'est pas vrai dans tous les cas. (Et maintenant, c'est corrigé dans la Q.) Pensez à un cadre de débogage lié dynamiquement et qui est non mentionné à partir de l'application. Si vous mettez quelque chose dans un+initialize
il ne sera jamais exécuté. Toutefois, il s'agissait d'un commentaire, pas d'une réponse.0 votes
Un hack simple pour
NSObject
-serait simplement d'introduire une superclasse Objective-C qui ne fait queinitialize
. J'imagine que cela peut être fait en utilisant de manière créative le runtime Objective-C - en générant automatiquement la superclasse et en faisant en sorte que le nom de l'utilisateur soit le même que celui de l'utilisateur.initialize
implémentation d'un wrapper pour appeler une méthode déclarée dans un protocole.0 votes
Non, cela ne peut pas être fait par un "simple hack" de
NSObject
car le RTE ne connaît même pas les classes des faisceaux non chargés. Et ce, parce qu'ils ne sont pas chargés. Vous devriez rechercher tous les chemins à partir desquels une application peut charger des paquets pour obtenir l'effet négatif de tous les paquets chargés au démarrage de l'application. Pour quoi faire ? Le seul effet positif serait que la version précédente de la Q est correcte, ce qui n'est évidemment pas un objectif de conception.