Com que frequência você usa
Swift.assert()
no seu código? Eu honestamente o uso com bastante frequência (se isso é uma prática ruim, por favor escreva nos comentários - por que é ruim?). No meu código, você pode ver, por exemplo, uma chamada:
Swift.assert(Thread.isMainThread)
Há pouco tempo, decidi que seria bom continuar observando os resultados dessas chamadas, não apenas como parte do lançamento do aplicativo no simulador / dispositivo, mas também das ações de usuários reais. A propósito, aqui podemos falar sobre
Swift.precondition()
,
Swift.fatalError()
etc., embora eu tente evitá-los. Li mais sobre
Erros irrecuperáveis no Swift nesta publicação e acabou sendo muito informativo.
Mais perto do ponto:
no código, encontrei cerca de 300 chamadas desse tipo. Nenhum deles funcionou durante os testes locais habituais, e isso me agradou. Mas sugeri que as ações do usuário ainda possam acionar algumas das chamadas.
Do ponto de vista do usuário, as falhas não devem ocorrer (novamente, na minha opinião). Em casos extremos, o usuário deve entender que algum cenário deu errado e a equipe de desenvolvimento já está trabalhando para corrigi-lo. Exceções semelhantes sempre foram tratadas por mim e, para o usuário, isso pode afetar da forma mais inofensiva. Por exemplo, uma das centenas de células da tabela era simplesmente invisível.
Com o usuário mais ou menos tudo fica claro. Resta lidar com a entrega de logs para o desenvolvedor. Em primeiro lugar, foi necessário, com um esforço mínimo, substituir as chamadas atuais no código pelas chamadas que enviam logs em algum lugar fora do aplicativo. Em segundo lugar, era necessário localizar com precisão a cena do incidente, caso contrário, seria praticamente impossível correlacionar a exceção com o código real. Em terceiro lugar, deve-se observar que as chamadas modificadas podem funcionar durante o teste de unidade, onde o
Thread.isMainThread
já deve ser ignorado. Eu uso a estrutura
RxTest para certos tipos de teste (aqui também estou pronto para ouvir conselhos e críticas). O ponto principal era que localmente todas as exceções deveriam ser acionadas como antes, ou seja,
Loggin.assert()
deve disparar ao mesmo tempo que
Swift.assert()
dispara
O Fabric (
Crashlytics ) fornece uma ótima maneira de enviar eventos. É assim:
Crashlytics.sharedInstance().recordCustomExceptionName("", reason: ""...
Resta
incluir o Crashlytics em alguma estrutura que possa ser carregada
totalmente no aplicativo e em um formato truncado (sem dependência do
Crashlytics ) nos destinos de teste.
Eu decidi fazer a "
embalagem " através do
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
O código para o "alvo de combate" é o seguinte:
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) } }
Para o "alvo de teste", ou seja, sem dependência do
Crashlytics :
import Foundation public enum Logging { public static func send(_ reason: String? = nil, __file: String = #file, __line: Int = #line) {
Resultados:
Exceções realmente começaram a funcionar. A maioria deles relatou um formato incorreto para os dados recebidos:
Decodable
vezes,
Decodable
recebem dados com o tipo
Decodable
. Às vezes, os logs do
Thread.isMainThread
funcionavam, que eram rapidamente corrigidos nas próximas versões. Os erros mais interessantes foram miraculosamente detectados pelo
NSException .
Obrigado pela atenção.
PS Se você costuma enviar esses logs para o
Crashlytics , o serviço pode reconhecer suas ações como spam. E você verá a seguinte mensagem:
Devido ao uso inadequado, os relatórios não fatais foram desativados para várias compilações. Saiba como reativar os relatórios em nossa documentação.
Portanto, vale a pena considerar antecipadamente a frequência do envio de logs. Caso contrário, todos os logs de construção podem estar em risco de serem ignorados pelo
Crashlytics.