Gerência de Projetos Dev & TI

5 out, 2016

Criando logs em Swift

Publicidade

Todo mundo, uma hora ou outra, coloca uns NSLogs no código. Muitas vezes, esse log só é útil apenas para o desenvolvimento ou debug. Então, pode não ser uma boa ideia usar direto o NSLog ou o print do Swift, pois esses log aparecem no console dos aparelhos.

Em Objective-C, eu tenho duas macros (que escrevi faz muitos anos) para resolver esse problema:

#ifdef DEBUG
    #define DTLog(fmt, ...) NSLog((@"%s:%d %s : " fmt), __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
#else
    #define DTLog NSLog
#endif

#ifdef DEBUG
    #define DTLogD(fmt, ...) NSLog((@"%s:%d %s : " fmt), __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
#else
    #define DTLogD(...)
#endif

Um adicional delas que eu gosto bastante é que quando o programa roda em debug, além da mensagem, são apresentados o nome do arquivo __FILE__, a linha __LINE__ e o nome da função __PRETTY_FUNCTION__ em que elas foram chamadas. Além disso, em release, uma delas não mostra nada.

Note que essas são macros do pré-processador de C, isso significa que são avaliadas antes do código ser compilado e que seu comportamento depende da macro DEBUG estar definida. Quando criamos um projeto, o Xcode já define essa macro para nós:

log-01

Então, se você copiá-las para seu projeto tudo já vai estar funcionando!

Em Swift, as coisas mudam um pouco: não temos macros do pré-processador. Mas o equivalente direto seriam funções globais, o que não tem muito cara de Swift. O que tenho usado é um struct com duas funções estáticas:

public struct Log {

    public static func info(
        _ items: Any...,
        functionName: String = #function,
        fileName: String = #file,
        lineNumber: Int = #line)
    {
        log(items,
            functionName: functionName,
            fileName: fileName,
            lineNumber: lineNumber)
    }

    public static func debug(
        _ items: Any...,
        functionName: String = #function,
        fileName: String = #file,
        lineNumber: Int = #line)
    {
        #if DEBUG
            log(items,
                functionName: functionName,
                fileName: fileName,
                lineNumber: lineNumber)
        #endif
    }

    private static func log(
        _ items: [Any],
        functionName: String,
        fileName: String,
        lineNumber: Int)
    {
        let url = NSURL(fileURLWithPath: fileName)
        let lastPathComponent = url.lastPathComponent ?? fileName
        #if DEBUG
            print("[\(lastPathComponent):\(lineNumber)] \(functionName):",
                separator: "",
                terminator: " ")
        #endif
        for item in items {
            print(item, separator: "", terminator: "")
        }
        print("")
    }

}

Apesar de Swift não ter macros, temos as Special Literal Expressions #file, #line e #function que têm praticamente o mesmo comportamento. Juntando com um pouco de malabarismo com Variadic Parameters fica até mais elegante que as macros. Mas se você copiar e colar esse código no seu projeto, provavelmente ele não vai funcionar direito, isso por conta do #if DEBUG. Isso não é uma macro, é um Conditional Compilation Blocks e o Xcode não cria automaticamente um DEBUG para você. Mas não tem problema, é só adicionar no Build Settings do projeto:

log-02

Note que diferente da macro não é atribuído um valor e a flag é precedida de -D.

Críticas, sugestões e comentários são sempre bem-vindos; é só me pingar no @diogot ou no Slack do iOS Dev BR.