Le compilateur met en œuvre variadic arguments par la coulée de chaque argument de l'déclaré variadic type, les emballer dans un Array
de ce type, et de passer ce tableau à la variadic fonction. Dans le cas d' testWrapper
, la déclaration de l'variadic type est - CVarArg
, de sorte que lors de l' testWrapper
des appels logDefault
, c'est ce qui se passe sous les couvertures: testWrapper
jette 1.2345
d'un CVarArg
, crée un Array<CVarArg>
et le passe à logDefault
comme args
.
Ensuite, logDefault
des appels os_log
, passant qu' Array<CVarArg>
comme argument. C'est le bug dans votre code. Le bug est assez subtile. Le problème est qu' os_log
ne prend pas un Array<CVarArg>
de l'argument; os_log
est lui-même variadic plus de CVarArg
. Tellement rapide jette args
(un Array<CVarArg>
) à l' CVarArg
, et les bâtons que coulé CVarArg
dans un autre Array<CVarArg>
. La structure ressemble à ceci:
Array<CVarArg> created in `logDefault`
|
+--> CVarArg (element at index 0)
|
+--> Array<CVarArg> (created in `testWrapper`)
|
+--> CVarArg (element at index 0)
|
+--> 1.2345 (a Double)
Ensuite, logDefault
passe ce nouveau Array<CVarArg>
de os_log
. Si vous vous posez os_log
pour le format de son premier élément, qui est (en quelque sorte) un Array<CVarArg>
, à l'aide de %f
, ce qui est absurde, et il vous arrive de 0.000000
en sortie. (Je dis "une sorte de" parce qu'il y a des subtilités qui sont ici, qui je l'explique plus loin.)
Donc, logDefault
passe ses entrants Array<CVarArg>
comme l'un de potentiellement beaucoup des paramètres variants. à la os_log
, mais ce que vous voulez réellement logDefault
à faire est de passer sur ce entrantes Array<CVarArg>
comme l'ensemble des des paramètres variants. à la os_log
, sans ré-enroulant. Cela est parfois appelé "argument splatting" dans d'autres langues.
Malheureusement pour vous, Swift n'a pas encore de syntaxe pour l'argument splatting. Il a été discuté plus d'une fois en Rapide Évolution (dans ce fil, par exemple), mais il n'y a pas encore de solution à l'horizon.
L'habitude solution à ce problème est de trouver un compagnon de fonction qui prend déjà groupés jusqu'variadic arguments comme un seul argument. Souvent, le compagnon a un v
ajouté le nom de la fonction. Exemples:
-
printf
(variadic) et vprintf
(prend un va_list
, C est l'équivalent de Array<CVarArg>
)
-
NSLog
(variadic) et NSLogv
(prend un va_list
)
-
-[NSString initWithFormat:]
(variadic) et -[NSString WithFormat:arguments:]
(prend un va_list
)
Donc, vous pouvez aller à la recherche d'un os_logv
. Malheureusement, vous ne trouverez pas une. Il n'existe aucune preuve compagnon os_log
qui prend pré-livré arguments.
Vous avez deux options s'offrent à vous:
Donner de l'habillage os_log
dans votre propre variadic wrapper, car il n'y a tout simplement pas de bonne façon de le faire, ou
-
Prendre Kamran conseils (dans son commentaire sur votre question) et utiliser %@
au lieu de %f
. Mais notez que vous ne pouvez avoir qu'un seul %@
(et pas d'autres spécificateurs de format) dans votre chaîne de message, car vous êtes seulement de passage d'un argument unique de os_log
. La sortie ressemble à ceci:
2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: (
"1.2345"
)
Vous pouvez également déposer une demande d'amélioration de radar à https://bugreport.apple.com pour demander un os_logv
de la fonction, mais vous ne devriez pas s'attendre à être mis en œuvre prochainement.
Donc, c'est ça. Effectuez l'une de ces deux choses, peut-être déposer un radar, et de passer avec votre vie. Sérieusement. Arrêter de lire ici. Il n'y a rien de bon après cette ligne.
Ok, vous avez gardé la lecture. Let's coup d'oeil sous le capot d' os_log
. Il s'avère que la mise en œuvre de la Swift os_log
fonction est une partie du public Swift code source:
@_exported import os
@_exported import os.log
import _SwiftOSOverlayShims
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
public func os_log(
_ type: OSLogType,
dso: UnsafeRawPointer = #dsohandle,
log: OSLog = .default,
_ message: StaticString,
_ args: CVarArg...)
{
guard log.isEnabled(type: type) else { return }
let ra = _swift_os_log_return_address()
message.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in
// Since dladdr is in libc, it is safe to unsafeBitCast
// the cstring argument type.
buf.baseAddress!.withMemoryRebound(
to: CChar.self, capacity: buf.count
) { str in
withVaList(args) { valist in
_swift_os_log(dso, ra, log, type, str, valist)
}
}
}
}
Donc, il s'avère qu'il y est une version d' os_log
, appelés _swift_os_log
, qui prend pré-livré arguments. La Swift wrapper utilise withVaList
(ce qui est documenté) pour convertir l' Array<CVarArg>
d'un va_list
et passe à _swift_os_log
, qui est, elle aussi, partie du public Swift code source. Je ne vais pas la peine de citer son code ici parce que c'est long et nous n'avons pas réellement besoin de le regarder.
De toute façon, même si elle n'est pas documentée, on peut réellement appeler _swift_os_log
. Nous pouvons copier le code source de l' os_log
et le mettre dans votre logDefault
fonction de:
func logDefaultHack(_ message: StaticString, dso: UnsafeRawPointer = #dsohandle, _ args: CVarArg...) {
let ra = _swift_os_log_return_address()
message.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in
buf.baseAddress!.withMemoryRebound(to: CChar.self, capacity: buf.count) { str in
withVaList(args) { valist in
_swift_os_log(dso, ra, .default, .default, str, valist)
}
}
}
}
Et il fonctionne. Le code de Test:
func testWrapper() {
logDefault("WTF: %f", 1.2345)
logDefault("WTF: %@", 1.2345)
logDefaultHack("Hack: %f", 1.2345)
}
Sortie:
2018-06-20 02:22:56.131875-0500 test[39313:6086331] WTF: 0.000000
2018-06-20 02:22:56.132704-0500 test[39313:6086331] WTF: (
"1.2345"
)
2018-06-20 02:22:56.132807-0500 test[39313:6086331] Hack: 1.234500
Je recommanderais cette solution? Pas de. L'enfer n'. Le fonctionnement interne de l' os_log
sont un détail de l'implémentation et susceptibles de changer dans les futures versions de Swift. Donc, ne comptez pas sur eux comme ça. Mais il est intéressant de regarder sous le couvre de toute façon.
Une dernière chose. Pourquoi ne pas le compilateur se plaindre de conversion Array<CVarArg>
de CVarArg
? Et pourquoi ne Kamran de la suggestion (de l'aide d' %@
) travail?
Il s'avère que ces questions ont la même réponse: c'est parce qu' Array
est "comblé" Objective-C objet. Plus précisément:
Cette conversion silencieuse est probablement souvent une erreur (comme il l'a été dans votre cas), de sorte qu'il serait raisonnable que le compilateur afin de les avertir, et vous permettent de réduire au silence l'avertissement avec un cast explicite (par exemple, args as CVarArg
). Vous pouvez déposer un rapport de bug à https://bugs.swift.org si vous le souhaitez.