Version d'iOS et rétro-compatibilité - Bonnes pratiques
LeChatNoir
Membre, Modérateur
Ma future version sera iOS6 et 7 compatible (en principe).
Je dois donc tester la version iOS en cours.
J'ai fait une macro pour me simplifier la vie.
#define iOS7AtLeast floor([[[UIDevice currentDevice] systemVersion] doubleValue]<7)?1:0
et je teste simplement :
if (iOS7AtLeast) ...
Est ce a good practice ?
Thxs :-)
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
En toute logique tu n'as pas à tester la version d'iOS, en aucune manière.
Ce que tu dois tester c'est la présence de fonctionnalité. En jouant avec du NSClassFromString ou avec les API de capacité de certains framework par exemple.
Relire de toute urgence le SDK Compatibility Guide dans la doc Apple.
Et ben...
To check the version of iOS at run time, use code like this:.
NSString *osVersion = [[UIDevice currentDevice] systemVersion];
Bon, après, je peux prendre une classe présente uniquement dans iOS7 et tester son existence mais bon...
Heu ça c'est la solution vraiment dernier recours. A n'utiliser que si vraiment toutes les autres solutions listés dans le SDK Compatibility Guide ne peuvent pas être appliquées. Mais avant tu as les respondsToSelector, les if([Machin class]), etc.
Mais le principe reste toujours le même : ne pas tester la version de l'OS, mais tester, quand tu veux utiliser une fonctionnalité, si elle est disponible ou pas. Faut pas prendre une classe au hasard présente que dans iOS7 non plus, ça ne fait que déporter le problème, c'est pas la solution non plus. Faut tester... ce que tu veux tester.
Si tu veux tester si ton objet roule, il ne faut pas tester si c'est une voiture, mais si ça roule. Car un camion aussi ça roule, et un vélo aussi. Si tu veux tester si ton objet sait démarrer, pareil. Si les dernières versions de voitures sont électriques, et que tu veux savoir si ta voiture est électrique ou pas, tu ne testes pas la version de ta voiture est supérieure à N, tu testes si elle est électrique ou pas. Car peut-être que demain elles ne seront plus électriques mais fonctionneront à eau et ton test version>N ne collera plus.
Si tu veux utiliser une méthode qui n'est que dispo dans iOS7, tu testes la présence de cette méthode avant de l'appeler, c'est pas plus compliqué que ça.
Ben si, c'est un peu plus compliqué que ça en fait...
Car sous iOS7, je n'ai pas le même design... Donc par exemple, si je suis en iOS7, ma navigationBar n'est pas tintée, alors qu'en iOS6, oui...
Donc dans ce cas, je fais comment ?
Ca dépend, certes dans ce cas précis si tu veux changer de design selon la version de l'OS, pourquoi pas ta macro alors (sauf qu'elle est mal écrite, parenthèse fermante du floor mal placée + opérateur ternaire inutile car équivalent à un "if YES then YES else NO")
Après, faut quand même vérifier que c'est pas pour d'autres raisons que tu changes tel ou tel élément d'interface. Pour bien tester la raison première du changement et pas une conséquence induite. Par exemple si tu veux changer la couleur de ta statusBar sous iOS7 parce que sous iOS7 ton ViewController passe en dessous de la statusBar et que tu veux qu'elle soit donc plus pâle, alors il faut peut-être plutôt faire un test pour savoir "est-ce que mon VC passe en dessous de ma statusBar ou pas" (tester le frame.origin.y de ton viewController.view ?)... Ca dépend de tes besoins, mais l'idée est d'éviter autant que possible de tester une conséquence de notre besoin plutôt que notre besoin réel (même si parfois on ne réalise pas que ce qu'on teste n'est pas la condition vraiment racine/sous-jacente, c'est étonnant comme on peut avoir la tête dans le guidon sur ce genre de conditions parfois)
C'est comme quand tu veux tester si tu es sur l'iPhone 5 avec un écran 4" ou sur un iPhone 4S ou inférieur, pour pouvoir dans ce cas mettre une rangée de boutons en plus par exemple. Y'en a qui testent pour ça le modèle d'iPhone. Ou si la hauteur de leur window est 568. Alors que si tu veux pouvoir mettre cette rangée de boutons en plus, c'est plus logique de tester si tu as la place de les mettre, donc si la hauteur de ton ViewController est >= la hauteur nécesaire pour rajouter cette rangée de boutons. Si tu as assez de place, alors tu la mets, sinon non. Comme ça le jour où l'iPhone 12 avec son écran de 6" va sortir, ça marchera toujours, même si la hauteur ne vaut plus exactement 568. Et s'il y a un iPhone qui sort un écran de 3.8", de hauteur un peu plus petit que 568 mais quand même juste assez grand pour ta rangée de boutons supplémentaires, tu l'auras quand même.
La pour ton cas c'est pareil, faut bien être sûr que tu testes la vrai raison et pas une conséquence.
rooo là là ... Tu mets le doigt sur un truc qui fache la...
Justement, j'ai des pb avec mon UINavigationController... Sous iOS6, ma vue est dessous (je veux dire bien calée en dessous), sous iOS7, ma vue passe effectivement en dessous...
J'ai bien compris qu'elles étaient transparentes par défaut donc c'est un comportement normal mais le pb, c'est que la transparence n'y est pas... (on touche peut être au NDA mais bon, faut bien se bouger un peu vu les délais impartis...).
Bref, je retiens ton idée.
Je l'avais appliquée pour une police qui existe sous iOS7 mais pas iOS6. Je teste donc simplement si la police existe ou pas.
Je vais essayer de gérer la partie navigationBar avec cette histoire de position (quand j'aurai compris :-))
Après moult tergiversations, pour traiter ce genre de pb (design différent selon version), j'ai fait cette macro, directement inspirée du " iOS 7 UI Transition Guide" :
#define prior_iOS7 floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1
Je viens de regarder le SDK Compatibility Guide.
Ma question n'est pas pour tester la présence d'une classe, mais le changement de type d'un argument de la méthode, sachant que la méthode est la même.
Je pense notamment à setLineBreakMode qui prend UILineBreakMode jusqu'à iOS 5, puis NSLineBreakMode maintenant ?! Apple veut uniformiser OS X et iOS ?
Cela est surtout suite au fait que maintenant UIKit supporte la gestion des NSAttributedString (via UILabel par exemple) et qu'il n'est plus nécessaire de passer par CoreText pour ça (adieu OHAttributedLabel).
Et dans ce cas cela ne dépend nullement du Runtime, et donc de la version de iOS sur laquelle ton application va tourner. Donc ce n'est pas la version d'iOS qu'il faut tester.
Ce qu'il faut tester, c'est la version du SDK. A partir d'un certain SDK, les headers indiquent que c'est un autre type pour cette propriété, donc si tu compiles avec un SDK récent tu vas utiliser NSLineBreakMode. Si tu compiles avec un SDK plus ancien UILineBreakMode sera de mise pour coller aux headers qui ne connaissent pas NSLineBreakMode.
La version minimum supportée par ton application, c'est défini par le Deployment Target. Mais la version maximum que ton application est assurée de supporter c'est la version du SDK (normal, tu ne peux pas utiliser des API de iOS7 si tu compiles avec le SDK iOS6 par exemple). Donc c'est un "#if __IPHONE_OS_VERSION_MAX_ALLOWED < 60000" qu'il faut utiliser.
OK, je crois avoir compris.
Et du coup, je m'aperçois que je dois quand-même utiliser une nouvelle méthode qui a remplacé l'ancienne, dont je dois donc tester la présence, et aussi ta macro pour taire les warnings !
But: compatibilité iOS 5, iOS 6, iOS 7.
Vois-tu une hérésie dans ce double test ?
En effet ça me parait pas déconnant
C'est du Ali ça ?
T'as ton neveux sur tes genoux ? 8--) :-*
Ce code ne marche pas pour une appli compilée iOS 7 DSK qui doit tourner sur iOS 6.x:
1) En effet, __IPHONE_OS_VERSION_MAX_ALLOWED < 70000 indique au compilateur de ne pas compiler, donc le else est vide.
Comment alors taire les warnings sur UILineBreakModeWordWrap ?
Je ne comprends pas ce que je dois faire.
2) Autre question: quand une méthode est deprecated, on peut quand-même l'utiliser. Jusqu'à quand reste-t-elle deprecated en général ? Jusqu'à l'OS suivant, puis ils la retirent complètement ?
__IPHONE_OS_VERSION_MAX_REQUIRED correspond à la version de ton Dominique Strauss-Kann avec laquelle tu compiles.
Là si tu compiles ton code avec le iOS 7 DSK le "else" va être vide comme tu l'as si bien dit. Du coup c'est normal que sous iOS6 ça ne fasse plus rien.
Et si tu compilais ton code avec Xcode4 et l'ancien iOS6 DSK, le "else" aurait le code, mais le code ne compilerait pas car à l'inverse le "drawInRect:withAttributes:" que tu utilises dans ton "if" n'existe que depuis iOS7 donc n'est déclaré dans Dominique que depuis sa version 7. Donc même si tu fais ton tests sur respondsToSelector, la méthode n'existant même pas dans les headers de Dominique 6.0 le compilateur de Xcode4 va gueuler.
Ne pas confondre version sous laquelle ton code tourne, version du SDK, Dominique Strauss-Kann et SDK.
J'ai bien compris ce que tu dis.
Mais je ne vois toujours pas comment taire les warnings dont je parle. Le SDK 7 bronche.
héhé, celui-là il est partout et nique même mon code !
OK.
Par contre, je m'aperçois que dans le else je dois utiliser NSLineBreakByWordWrapping et non UILineBreakModeWordWrap.
Tu me diras, c'est normal, c'est le SDK 7 qui compile et il le connaà®t.
Et moi je te dis, OK, mais comment ça arrive à ne pas planter sous iOS 6 qui est sensé ne connaà®tre que UILineBreakModeWordWrap ?
Par contre ton code a une incohérence. Tu testes l'existence du @selector "drawWithRect:options:attributes:context:" mais c'est pour appeler "drawInRect:withAttributes:" et non pas la méthode dont tu as testé le @selector
Ah ok, je comprends.
Bien vu, ça m'a échappé ! Merci !
Bon, même sur iOS 6, je rentre dans le if:
::)
Je suppose que ce n'est pas le bon test, pourtant je veux tester la présence de cette méthode d'instance de NSString.
J'avoue que j'en perds mon latin.
Ouais mais du coup il rentre dans le if, veut exécuter la méthode et paf, ça plante.
C'est quoi le plantage exact que tu as ?
Le plantage ne semble pas être sur la méthode mais sur son contenu passé en argument.
-[__NSDictionaryI font]: unrecognized selector sent to instance 0xb1ef390
Le code exécuté pour rappel:
Du coup ça doit être un cas particulier où la méthode existait dans iOS6... mais était privée et prenait autre chose qu'un NSDictionary comme argument ?!
C'est ce que je me suis dit, mais ça en devient vraiment étrange.
T'es sûr que mon code de test est bon ?
if ([NSString instancesRespondToSelector:@selector(drawInRect:withAttributes:)])
parce que j'y rentre aussi sous iOS 5...
Sous iOS6 :
Sous iOS7 :
(Pour la référence des Type Encodings, voir la doc Apple ici)
Donc déjà première analyse : la méthode existe bien sous iOS6, même si elle n'est pas publique. Elle devait faire partie des API privée.
Et deuxième analyse : la méthode privée du temps de iOS6 n'a pas la même signature que la méthode publique apparue avec iOS7. Ce qui tend vers ma théorie qu'ils ont certainement changé les arguments de cette méthode avant de la passer publique.
- Evidemment les 2 premiers arguments (#0 et #1) sont de type "id" ("@) et SEL" (":"), puisque ce sont les paramètres cachés "self" et "_cmd" de toute méthode Objective-C.
- Ensuite vient un CGRect dans les 2 cas, normal c'est l'argument de "drawInRect:".
- Puis vient un objet (id) pour l'argument "withAttributes:", bon on ne pourra pas savoir précisément ce que c'est (tous les objects Cocoa sont représentés par le type encoding "@ / le type id" générique dans les signatures au final) et si c'est un NSDictionary qui était déjà attendu sous iOS6 comme ça l'est officiellement pour iOS7
- Par contre on voit bien que le "Return type" est "void" ("v") sous iOS7, ce qui est tout à fait ce que dit la doc... mais qu'il était de type CGSize ("CGSize{ff}" autrement dit une struct qui s'appelle CGSize et contient 2 champs de type float, oui oui c'est donc bien le CGSize qu'on connait tous) sous iOS6. Donc quand c'état encore une API privée, cette méthode retournait un CGSize, sans doute la taille prise par le dessin. Dommage du coup qu'ils ne retournent plus rien quand ils l'ont passée publique sous iOS7, mais bon c'est comme ça.
Conclusion bah tu viens de tomber dans un cas un peu sioux d'une API dispo que sous iOS7 mais pour laquelle il existait une méthode privée du même nom mais avec signature différente sous iOS6... Pas de bol