À quelle fréquence utilisez-vous
Swift.assert()
dans votre code? Je l'utilise honnêtement assez souvent (si c'est une mauvaise pratique, alors veuillez écrire dans les commentaires - pourquoi est-ce mauvais?). Dans mon code, vous pouvez souvent voir, par exemple, un tel appel:
Swift.assert(Thread.isMainThread)
Il n'y a pas si longtemps, j'ai décidé que ce serait bien de continuer à observer les résultats de ces appels, non seulement dans le cadre du lancement de l'application dans le simulateur / appareil, mais aussi à partir des actions de vrais utilisateurs. Au fait, ici, nous pouvons parler de
Swift.precondition()
,
Swift.fatalError()
, etc., bien que j'essaie de les éviter. J'ai lu plus d'informations sur les
erreurs irrécupérables dans Swift dans cette publication et cela s'est avéré très instructif.
Plus près du but:
dans le code, j'ai trouvé environ 300 de ces appels. Aucun d'eux n'a travaillé pendant les tests locaux habituels, et cela m'a fait plaisir. Mais j'ai suggéré que les actions des utilisateurs pourraient toujours déclencher certains appels.
Du point de vue de l'utilisateur, les plantages ne devraient pas se produire (encore une fois, à mon avis). Dans les cas extrêmes, l'utilisateur doit comprendre qu'un scénario s'est mal passé et que l'équipe de développement travaille déjà à le corriger. Des exceptions similaires ont toujours été gérées par moi, et pour l'utilisateur, cela pourrait affecter sous la forme la plus inoffensive. Par exemple, l'une des centaines de cellules du tableau était tout simplement invisible.
Avec l'utilisateur plus ou moins, tout est clair. Il reste à gérer la livraison des logs au développeur. Premièrement, il était nécessaire avec un minimum d'effort de remplacer les appels en cours dans le code par des appels envoyant des journaux quelque part en dehors de l'application. Deuxièmement, il était nécessaire de localiser avec précision la scène de l'incident, sinon il serait pratiquement impossible de corréler l'exception avec le code réel. Troisièmement, il convient de noter que les appels modifiés peuvent fonctionner pendant les tests unitaires, où
Thread.isMainThread
doit déjà être ignoré. J'utilise le framework
RxTest pour certains types de tests (ici, je suis également prêt à écouter les conseils et les critiques). Le point principal restait que localement toutes les exceptions devraient être déclenchées comme auparavant, c'est-à-dire
Loggin.assert()
devrait se déclencher en même temps que
Swift.assert()
se déclencherait
Fabric (
Crashlytics ) fournit un excellent moyen d'envoyer des événements. Cela ressemble à ceci:
Crashlytics.sharedInstance().recordCustomExceptionName("", reason: ""...
Il reste à emballer
Crashlytics dans un cadre qui peut être chargé dans son
intégralité dans l'application et sous une forme tronquée (sans dépendance
Crashlytics ) dans des cibles de test.
J'ai décidé de faire le «
packaging » via
CocoaPods :
Pod::Spec.new do |s| s.name = 'Logging' ... s.subspec 'Base' do |ss| ss.source_files = 'Source/Logging+Base.swift' ss.dependency 'Crashlytics' end s.subspec 'Test' do |ss| ss.source_files = 'Source/Logging+Test.swift' end end
Le code de la "cible de combat" est le suivant:
import Crashlytics public enum Logging { public static func send(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { let file = __file.components(separatedBy: "/").last ?? __file let line = "\(__line)" let name = [line, file].joined(separator: "_") Crashlytics.sharedInstance().recordCustomExceptionName(name, reason: reason ?? "no reason", frameArray: []) } public static func assert(_ assertion: @escaping @autoclosure () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { if assertion() == false { self.assertionFailure(reason, __file: __file, __line: __line) } } public static func assert(_ assertion: @escaping () -> Bool, reason: String? = nil, __file: String = #file, __line: Int = #line) { if assertion() == false { self.assertionFailure(reason, __file: __file, __line: __line) } } public static func assertionFailure(_ reason: String? = nil, __file: String = #file, __line: Int = #line) { Swift.assertionFailure(reason ?? "") self.send(reason, __file: __file, __line: __line) } }
Pour la "cible de test", c'est-à-dire sans dépendance
Crashlytics :
import Foundation public enum Logging { public static func send(_ reason: String? = nil, __file: String = #file, __line: Int = #line) {
Résultats:
Les exceptions ont vraiment commencé à fonctionner. La plupart d'entre eux ont signalé un format incorrect pour les données reçues:
Decodable
recevait parfois des données de
Decodable
type. Parfois, les journaux de
Thread.isMainThread
fonctionnaient, qui ont été très rapidement corrigés dans les prochaines versions. Les erreurs les plus intéressantes ont été miraculeusement
détectées par
NSException .
Merci de votre attention.
PS Si vous envoyez trop souvent de tels journaux à
Crashlytics , le service peut reconnaître vos actions comme du spam. Et vous verrez le message suivant:
En raison d'une mauvaise utilisation, les rapports non fatals ont été désactivés pour plusieurs versions. Découvrez comment réactiver les rapports dans notre documentation.
Par conséquent, il convient de considérer à l'avance la fréquence d'envoi des journaux. Sinon, tous les journaux de génération risquent d'être ignorés par
Crashlytics.