Gatekeeper par Guillaume Faure
-
Upload
cocoaheads-france -
Category
Software
-
view
3.778 -
download
3
Transcript of Gatekeeper par Guillaume Faure
Gatekeeper @ DeezerCocoheads Paris
Guillaume FaureMai 2016
Deezer
1
Concept
Qu’est qu’un GateKeeper
Permet au cours de l’exécution de l’application charger/déchargerdes modules.
2
Pourquoi un GateKeeper
• Pas de rollout progressif (contrairement au Playstore)
• Temps de validation appstore variable• Phases de beta test trop courtes / sans assez d’utilisateurs
3
Pourquoi un GateKeeper
• Pas de rollout progressif (contrairement au Playstore)• Temps de validation appstore variable
• Phases de beta test trop courtes / sans assez d’utilisateurs
3
Pourquoi un GateKeeper
• Pas de rollout progressif (contrairement au Playstore)• Temps de validation appstore variable• Phases de beta test trop courtes / sans assez d’utilisateurs
3
Conditions d’activation
Le choix ou non de l’activation est faite coté serveur
• Employé
• Utilisateur en béta• Office• Pays• Pourcentage d’utilisateur• Offre• Platform• Formfactor
4
Conditions d’activation
Le choix ou non de l’activation est faite coté serveur
• Employé• Utilisateur en béta
• Office• Pays• Pourcentage d’utilisateur• Offre• Platform• Formfactor
4
Conditions d’activation
Le choix ou non de l’activation est faite coté serveur
• Employé• Utilisateur en béta• Office
• Pays• Pourcentage d’utilisateur• Offre• Platform• Formfactor
4
Conditions d’activation
Le choix ou non de l’activation est faite coté serveur
• Employé• Utilisateur en béta• Office• Pays
• Pourcentage d’utilisateur• Offre• Platform• Formfactor
4
Conditions d’activation
Le choix ou non de l’activation est faite coté serveur
• Employé• Utilisateur en béta• Office• Pays• Pourcentage d’utilisateur
• Offre• Platform• Formfactor
4
Conditions d’activation
Le choix ou non de l’activation est faite coté serveur
• Employé• Utilisateur en béta• Office• Pays• Pourcentage d’utilisateur• Offre
• Platform• Formfactor
4
Conditions d’activation
Le choix ou non de l’activation est faite coté serveur
• Employé• Utilisateur en béta• Office• Pays• Pourcentage d’utilisateur• Offre• Platform
• Formfactor
4
Conditions d’activation
Le choix ou non de l’activation est faite coté serveur
• Employé• Utilisateur en béta• Office• Pays• Pourcentage d’utilisateur• Offre• Platform• Formfactor
4
Quel utilité ?
Les modules sont des pans entiers de fonctionnalités ou decomportement de l’application.
• Refactoring• Fonctionnalités utilisateur• Test utilisateurs• ...
5
Quel utilité ?
Les modules sont des pans entiers de fonctionnalités ou decomportement de l’application.
• Refactoring
• Fonctionnalités utilisateur• Test utilisateurs• ...
5
Quel utilité ?
Les modules sont des pans entiers de fonctionnalités ou decomportement de l’application.
• Refactoring• Fonctionnalités utilisateur
• Test utilisateurs• ...
5
Quel utilité ?
Les modules sont des pans entiers de fonctionnalités ou decomportement de l’application.
• Refactoring• Fonctionnalités utilisateur• Test utilisateurs• ...
5
Modules
Principe d’un module de gate keep
• Ce n’est pas juste un BOOL
• Est une interface vers une fonctionalité• Les utilisateurs de cette fonctionalité appellent cette interface
6
Principe d’un module de gate keep
• Ce n’est pas juste un BOOL• Est une interface vers une fonctionalité
• Les utilisateurs de cette fonctionalité appellent cette interface
6
Principe d’un module de gate keep
• Ce n’est pas juste un BOOL• Est une interface vers une fonctionalité• Les utilisateurs de cette fonctionalité appellent cette interface
6
Deux formes de modules
• Module simple : Le module n’est chargé que lorsque le gatekeepcorrespondant est actif.
• Module double : Le module est composé de deuximplémentations, l’une lorsque le gatekeep correspondant estactif, l’autre quand il est inactif.
7
Deux formes de modules
• Module simple : Le module n’est chargé que lorsque le gatekeepcorrespondant est actif.
• Module double : Le module est composé de deuximplémentations, l’une lorsque le gatekeep correspondant estactif, l’autre quand il est inactif.
7
Module interface
1 @interface DZRGateKeeperModule : NSObject2 + (nullable id)activatedModule;3 + (nullable id)deactivatedModule;4
5 + (nullable NSString *)name;6 + (nullable instancetype)module;7
8 - (void)moduleLoad;9 - (void)moduleUnload;
10 @end
8
Proxy
Comment Garantir le déchargement des modules
• Il faut pouvoir charger/décharger les modules à n’ importe quelmoment de l’exécution de l’application.
• Le module, peut être appelé de n’ import où.• On ne controlle pas la politique d’ownership
ProxyUtilisation d’un NSProxy pour protéger les modules.
Le code extérieur, ne voit jamais que le proxy, jamais le moduleréel.
Seul le proxy retain l’ instance du module.
9
Comment Garantir le déchargement des modules
• Il faut pouvoir charger/décharger les modules à n’ importe quelmoment de l’exécution de l’application.
• Le module, peut être appelé de n’ import où.
• On ne controlle pas la politique d’ownership
ProxyUtilisation d’un NSProxy pour protéger les modules.
Le code extérieur, ne voit jamais que le proxy, jamais le moduleréel.
Seul le proxy retain l’ instance du module.
9
Comment Garantir le déchargement des modules
• Il faut pouvoir charger/décharger les modules à n’ importe quelmoment de l’exécution de l’application.
• Le module, peut être appelé de n’ import où.• On ne controlle pas la politique d’ownership
ProxyUtilisation d’un NSProxy pour protéger les modules.
Le code extérieur, ne voit jamais que le proxy, jamais le moduleréel.
Seul le proxy retain l’ instance du module.
9
Comment Garantir le déchargement des modules
• Il faut pouvoir charger/décharger les modules à n’ importe quelmoment de l’exécution de l’application.
• Le module, peut être appelé de n’ import où.• On ne controlle pas la politique d’ownership
ProxyUtilisation d’un NSProxy pour protéger les modules.
Le code extérieur, ne voit jamais que le proxy, jamais le moduleréel.
Seul le proxy retain l’ instance du module.
9
Fonctionnement du proxy
Le Proxy est géré par le mécanisme de GateKeeper.
L’ implementation du module renvoie son proxy lorsqu’on demandeson singleton.
1 + (instancetype)module2 {3 return [[DZRGateKeeper sharedGateKeeper] moduleWithName:[[self class]
name]];♦
↣4 }
Le proxy doit maintenant se faire passer pour une instance dumodule.
10
GateKeeper Proxy
Le proxy a besoin d’ information sur la classe qu’ il va usurper(notamment pour les modules simples).
1 @interface DZRGateKeeperModuleProxy : NSProxy2 - (id)initWithClass:(Class)moduleClass;3 @end
11
Déguisons le proxy
Il va ensuite forwarder tous les messages.
1 - (id)forwardingTargetForSelector:(SEL)aSelector2 {3 return self.module ?: (id)self;4 }
Deux possibilités
Fast path
• Première partie de laconditionnelle
• Exécuté quand le proxy disposed’un module
• On donne le module commenouvelle target du message
Slow path
• Seconde partie de laconditionnelle
• Exécuté quand le proxy disposed’aucune instance de module
• Le proxy va devoir répondre aumessage lui-même
12
Forwarding: Slow path
1 - (void)forwardInvocation:(NSInvocation *)invocation2 {3 }
13
Forwarding: Slow path
Pour que le runtime puisse créer son NSInvocation, il a besoin dela signature de la méthode.
On la demande gentiment à la classe du module que l’on est entrain d’usurper.
1 - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel2 {3 Method m = class_getInstanceMethod(_moduleClass, sel);4 if (m != nil) {5 const char *typeEncoging = method_getTypeEncoding(m);6 return [NSMethodSignature signatureWithObjCTypes:typeEncoging];7 }8 else {9 return nil;
10 }11 }
14
Comment mentir ?
Si l’on veux juste créer un module simple mais que l’on a besoinqu’ il réponde autre chose que 0 ou nil, comment peut-on faire?
15
1 @interface DZRGateKeeperModuleProxy : NSProxy2 - (id)initWithClass:(Class)moduleClass;3
4 /** The lying machine **/5 - (void)forwardSelector:(SEL)sel returningDefaultObject:(NSObject
*)object;♦
↣6 - (void)forwardSelector:(SEL)sel returningDefaultBOOL:(BOOL)boolean;7 - (void)forwardSelector:(SEL)sel returningDefaultFloat:(float)f;8 - (void)forwardSelector:(SEL)sel returningDefaultInteger:(NSInteger)i;9 - (void)forwardSelector:(SEL)sel returningDefaultDouble:(double)d;
10 @end
16
Implementation des stubs
1 - (void)forwardSelector:(SEL)sel returningDefaultObject:(NSObject*)object
♦↣
2 {3 _stubs[NSStringFromSelector(sel)] = object;4 }5
6 - (void)forwardSelector:(SEL)sel returningDefaultBOOL:(BOOL)boolean7 {8 _stubs[NSStringFromSelector(sel)] = @(boolean);9 }
17
Améliorons le forwardInfovation:
1 - (void)forwardInvocation:(NSInvocation *)invocation2 {3 id v;4 if ((v = [_stubs
objectForKey:NSStringFromSelector(invocation.selector)])) {♦
↣5 [invocation dzr_setReturnValue:v];6 }7 }
18
GateKeeper
Le chef d’orchestre
• Encore un singleton...• Les modules s’enregistrent auprès de lui• L’application l’appelle à des moments clef pourcharger/décharger des modules
19
Enregistrement d’un module
1 - (id)registerModule:(Class)module2 {3 DZRGateKeeperModule * moduleInstance = nil;4
5 if ([self checkDZRGateKeeperModule:module]) {6 NSString *name = [module name];7 DZRGateKeeperModuleProxy *proxy = [[DZRGateKeeperModuleProxy
alloc] initWithClass:module];♦
↣8 DZRGateKeeperModule * deactivatedInstance = moduleInstance =
[module deactivatedModule];♦
↣9 [deactivatedInstance moduleLoad];
10 proxy.module = deactivatedInstance;11 (self.modules)[name] = proxy;12 (self.registeredModules)[name] = module;13 }14 else {15 [NSException16 raise:NSGenericException format:@"You can only register class
deriving DZRGateKeeperModule"];♦
↣17 }18 return moduleInstance;19 }
20
Charger un module
1 - (DZRGateKeeperModule*)activateModuleWithName:(NSString *)moduleName2 {3 /** Check registration **/4
5 Class module = self.registeredModules[moduleName];6 DZRGateKeeperModuleProxy *proxy = self.modules[moduleName];7 DZRGateKeeperModule *moduleInstance = [module activatedModule];8
9 if (moduleInstance) {10 /** swap modules instances **/11 }12
13 return moduleInstance;14 }
21
Check registration
1 if (self.registeredModules[moduleName] == nil) {2 [NSException raise:NSGenericException3 format:@"The %@ module was not registered to the Gate
Keeper system",♦
↣4 moduleName];5 }
22
Swap modules instance
1 [proxy.module moduleUnload];2 [moduleInstance moduleLoad];3 proxy.module = moduleInstance;4 proxy.activated = YES;5 [[NSNotificationCenter defaultCenter]6 postNotificationName:DZRGateKeeperModuleLoadedNotification7 object:self8 userInfo:@{DZRGateKeeperUserInfoModuleInstance: proxy,9 DZRGateKeeperUserInfoModuleName: moduleName}];
23
Appliquer la configuration
1 - (void)applyConfiguration2 {3 /** Gathering necessary information **/4 /** Compute the set of module ids to desactivate **/5 /** Compute the set of module ids to activate **/6 [toDesactivate enumerateObjectsUsingBlock:^(NSString *module,
BOOL *stop) {♦
↣7 [self desactivateModuleWithName:module];8 }];9 [toActivate enumerateObjectsUsingBlock:^(NSString *module, BOOL
*stop) {♦
↣10 [self activateModuleWithName:module];11 }];12 }
24
Gathering necessary information
1 NSSet *activated = self.activatedModules;2 NSSet *configured = self.persistedConfiguration;3 NSSet *configuredAndForced = [configured
setByAddingObjectsFromSet:[DZRGateKeeper activationForced]];♦
↣4 NSSet *registered = [NSSet setWithArray:self.registeredModules.allKeys];
25
Compute the set of module ids to desactivate
1 NSMutableSet *toDesactivate = [activated mutableCopy];2 [toDesactivate minusSet:configuredAndForced];3 [toDesactivate intersectSet:registered];
26
Compute the set of module ids to activate
1 NSMutableSet *toActivate = [configuredAndForced mutableCopy];2 [toActivate minusSet:activated];3 [toActivate intersectSet:registered];
27
Conclusion
En résumé
Pro
• Rollout progressif et maitrisé
• Desactivation de fonctionalitéinstable
• Partagé avec les autresplateformes
Cons
• Impose une architecture clientpour les modules
• Mutiplication des configurations
• Difficile de supprimer un module(fonctionalité devenuepermanante)
28
Questions ?
28