Swift: appel d'une méthode Obj-C à nombre d'arguments variables
muqaddar
Administrateur
Salut,
Il semble impossible d'appeler ce genre de méthode au nombre d'arguments variable depuis une classe Swift:
- (BOOL)executeUpdate:(NSString*)sql, ...;
D'ailleurs, l'auto-complétion ne la suggère pas.
Alors que son alternative au nombre d'arguments fixes est parfaitement reconnue:
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
Y-a-t-il une astuce car je préfère utiliser la première méthode... (dans la deuxième, on envoie un array d'arguments... ce qui me pose problème quand ils ont des types différents)
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Par contre peut-être que le bridging ObjC -> Swift n'arrive pas à traduire la méthode ObjC correctement... T'as fait un Pomme-Clic sur le nom de ta classe ObjC dans ton code Swift, histoire de voir les headers Swifts (headers ObjC traduits à la volée en Swift par Xcode) et donc voir comment il t'a traduit ce nom de méthode ?
Normalement cette méthode devrait être traduite en Swift sous la forme "func executeUpdate(sql: NSString, _ args: AnyObject...)" (ou peut-être "func executeUpdate(sql: NSString, _ args: CVarArg...)" mais bon ça change pas grand chose) et donc peut être appelée avec executeUpdate(sql: "TA REQUETE", arg1, arg2, arg3) depuis Swift.
Après quand tu utilises des APIs ObjC traduites automatiquement en Swift, peut-être que la traduction à la volée des APIs en Swift ne donne pas toujours exactement la signature que tu attends... et peut-être que la traduction de méthodes à nombre variable d'argument donne des APIs différentes, dans le doute faut regarder les headers Swift
OK, merci.
J'ai réussi à la faire afficher dans l'auto-complétion finalement. (avec un Pomme-Clic ça l'a réveillé ?).
Donc, ça me suggère ça:
sauf que je ne vois pas comment remplir CVaListPointer qui est une structure déclarée dans le header swift ?
T'es sûr qu'il n'y a pas 2 variantes ?
Par exemple pour [NSString stringWithFormat:] en ObjC il y a 2 variantes : "stringWithFormat:" qui prend un format + un nombre variable d'arguments, et "stringWithFormat:arguments:" qui prend un format + un va_list.
Ce paramètre de type va_list (qui est un type C bas niveau pour manipuler les arguments d'une fonction) collerait avec son équivalent CVaListPointer que tu vois en Swift. Et ca collerait aussi avec le nom de la méthode "executeUpdate:(_, withVAList:)" équivalent à une hypothétique méthode ObjC "executeUpdate:withVAList:" (comme on a "stringWithFormat:arguments:" sur NSString).
Donc à mon avis il doit en trainer encore une autre version de ta méthode, qui ne prend pas un va_list / CVaListPointer en paramètre mais prend directement les paramètres variables... non ?
Je ne vois que ça:
L'API Swift idéale serait celle qui te permettrait de passer les param1 & param2 directement, ce que tu peux faire en te faisant un wrapper en attendant que l'auteur de FMDatabase adapte son API : PS : Je n'utilise pas FMDatabase donc je ne sais pas ce qu'on est sensé être en placeholder dans la requête SQL, j'ai mis "$1" et "$2" mais c'est p'tet pas comme ça qu'on fait je te laisse adapter
Sauf que si je lui envoie un array Swift, il faut que les types soient les mêmes, ce qui n'est pas souvent le cas... (on peut envoyer un ID Int, un name String...etc). C'est pour ça que je voulais l'autre méthode qui envoie les arguments aux types différents sans soucis.
J'ai essayé. J'en suis là :
Et j'appelle comme ça:
Par contre, il me force à unwrapper food.ID et food.name avec (!) ce qui est contraire à ce que je veux faire: je veux pouvoir envoyer nil pour chaque argument (et c'est pour ça qu'ils sont déclarés en String?).
J'imagine que c'est la transformation en array qui pose problème pour les possibles arguments à nil ?
J'ai essayé d'ajouter (?) à AnyObject pour autoriser des valeurs nil, mais le compilo bronche ! Pas simple !
Essayé avec le type explicite Optional<AnyObject> plutôt que AnyObject? pour voir si ça passe mieux ?
Ah, je crois que j'ai trouvé !
Il fallait caster arguments en NSArray en plus de mettre ? à AnyObject.
Sûrement parce que NSArray supporte des nil contrairement à son homologue array de Swift.
Cela dit, si tu peux m'expliquer le underscore (avant arguments) que je commence à voir en Swift... je n'ai pas encore lu de doc dessus ? Bizarre.
C'est un placeholder qui sert principalement dans 2 cas :
1) pour dire "là je pourrais mettre une variable sauf que je m'en fous de sa valeur". Exemple typique dans les boucles for, si tu t'en fous de la valeur de l'index dans ta boucle for donc autant mettre "_" plutôt que d'avoir une variable i qui ne sert à rien. Ou encore si tu itères sur les clés d'un dictionaire mais que tu t'en fous des valeurs associées : 2) Pour donner un "nom de paramètre externe" qui soit "vide" dans une déclaration de fonction.
En effet, quand tu déclares une méthode dans une classe, alors par défaut le premier argument n'a pas de nom externe mais les arguments suivants ont le même nom externe et interne, pour coller avec ce qu'on a l'habitude de voir en ObjC. Par exemple une méthode ObjC avec la signature "winesWithColor:vineyard:year:" aura pour équivalent en Swift "winesWithColor(color: VCColor, vineyard: VCVineyard, year: Int)" mais tu vas l'appeler "winesWithColor(aColor, vineyard:aVineyard, year:2007)" (et pas juste winesWithColor(aColor,aVineyard,2007) à cause de ce comportement par défaut. C'est comme si tu avais écrit "func winesWithColor(color: VCColor, vineyard vineyard: VCVineyard, year year: Int)", avec le même nom pour les paramètres externes (nom à donner au paramètre lors de l'appel) et internes (noms de variables que tu vas utiliser dans le corps de ta fonction) pour les paramètres 2 et 3.
C'est le comportement par défaut des déclarations de méthodes dans une classe (bien pratique pour retrouver ses repères ObjC) mais du coup si tu n'en veux pas de ces "noms de paramètres externes" et que tu veux pouvoir appeler cette méthode avec "winesWithColor(aColor, aVineyard, 2007)", tu peux utiliser ce "_" pour forcer un "nom de paramètre externe" qui soit "vide" : avec "func winesWithColor(color: VCColor, _ vineyard: VCVineyard, _ year: Int)", le 2e et le 3e paramètre ont comme nom interne "vineyard" et "year", mais ont comme nom de paramètre externe "_" qui est la valeur spéciale pour forcer "pas de nom". Ce qui permet donc de contourner le comportement par défaut et de forcer ce paramètre à ne pas avoir de "nom de paramètre externe".
(A voir ce que j'ai rédigé je me rends compte que c'est plus compliqué à expliquer que ça ne l'est, le concept est très simple, c'est une sorte de placeholder pour "pas de nom" / "je veux pas de cette variable")
Non, j'ai bien compris ce que tu as expliqué.
C'est pratique, même si je ne sais pas si je vais souvent m'en servir.
L'exemple dans les boucles à la limite, oui, ça peut être sympa.
Mais pour des rares cas genre ici avec ton nombre variable d'arguments, bah en général tu veux mettre tes paramètres variables direct à la suite de la virgule sans avoir à les préfixer par "arguments:" donc c'est quand même utile ;-)
Oui tu as eu une bonne idée... ou plutôt un bon réflexe.
(merci la commande "pod try FMDB", en 2 secondes j'avais un projet vide avec FMDB d'importé où j'ai pu tester un peu directement du code sur FBDatabase alors que je ne l'avais jamais utilisé )
Pour moi il y a clairement un bug concernant l'exposition des méthodes ObjC à nombre variables d'argument sur Swift. Aucune des méthodes déclarées avec un nombre variable d'argument n'est visible par Swift.
Les 3 seules méthodes que tu vois sur la classe FMDatabase qui commencent par "executeUpdate..." sont celles déclarées en ObjC comme : Par contre les autres méthodes, celles qui prennent un nombre variable d'argument, sont impossibles à appeler depuis Swift, en pratique je pense que Swift (enfin le module de LLVM qui se charge de faire la transformation des APIs ObjC en Swift pour être précis) ne les voit même pas : Du coup pour moi c'est un réel bug, officiel, et qu'il faut remonter à Apple avec un BugReport.
(C'est en remontant des bugs qu'Apple va les corriger, sinon tu peux toujours attendre...)
Oui, je vais le faire.
ça sera mon deuxième BugReport de la semaine !
EDIT: cela dit le Wrapper créé plante à l'exécution, et plante Xcode (même en ajoutant un pointeur)... j'ai juste le temps de voir Exec Bad Access.
EDIT 2: le Wrapper marche en SELECT mais pas sur UPDATE (sûrement un problème avec les arguments)
EDIT 3: chaque breakpoint ajouté plante Xcode 6