30 votes

Pourquoi l'emballage os_log() fait-il que les doubles ne soient pas enregistrés correctement?

Prenons l'exemple suivant :

Si je crée une nouvelle instance et que j'appelle

J'obtins la sortie suivante dans la console Xcode :

J'ai vérifié tout ce que je peux penser et je ne peux pas faire des têtes ou des queues de ce qui ne va pas ici. En regardant à travers la documentation ne donne rien d'utile.

Merci pour l'aide!

68voto

rob mayoff Points 124153

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.

1voto

TimeDelta Points 316

Comme mentionné dans mon commentaire à la réponse de Rob Mayoff ci-dessus, pour tous ceux qui éprouvent le même genre de problème avec `` , voici une classe d'emballage que j'ai fait autour d'elle:

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X