Invariante   Algumas coisas nunca mudam

Logs em Swift


Depois de quase 5 meses estamos de volta, 🖖.


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 idéia 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:

Xcode precompiler macros

Então se você copia-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 usado1 é 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:

Xcode swift custom flags

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


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


Diogo Tridapalli
@diogot


  1. Já em Swift 3.0