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

Minimizando o acoplamento entre Views e ViewControllers


Esse artigo foi publicado originalmente no equinociOS.


O excesso de responsabilidades do ViewController é algo que vem me incomodando há algum tempo. Já escrevi um pouco sobre a relação entre ViewControllers, hoje quero falar sobre a relação entre as Views e o ViewController.

Views, de maneira geral, são genéricas. Não contêm lógica e podem ser reutilizadas em diferentes contextos. Por exemplo, UILabel possui uma série de customizações como text, font e textColor, que permitem que ela seja usada em diversos contextos. Até aqui tudo certo1. Na maioria dos casos nem todas as configurações são alteradas, o mais comum é alterar as 3 citadas. Em alguns casos é necessário definir uma configuração para o mesmo valor, por exemplo textColor, cujo o valor padrão é blackColor mas no app todos os textos são grayColor, para evitar a replicação de código algumas vezes eu já fiz uma subclasse de UILabel com configurações padrão diferentes, mas isso sempre me pareceu estranho. Essa questão de onde deve ficar o código que define as configurações é um ponto desconfortável. Por exemplo o text, normalmente quem atribui uma string é o ViewController que é um estranho pois normalmente ele tira essa string de um Model. Quem deve ter o propósito de definir essas configurações?

Primeiramente o que são essas configurações? São um conjunto de customizações de um componente que correspondem a um resultado final específico, vamos chamar esse conjunto de configurações de estado. Note que a definição de estado aqui é a mesma usada em física2, uma descrição que define de maneira única e não ambígua a configuração de um sistema. Por exemplo, se o sistema for a localização de algo em uma rua, um estado é definido pelo número da casa. Se o sistema for a localização de algo em alguma cidade na terra, temos várias descrições (ou modelos) de estados possíveis, por exemplo [País, cidade, rua, número da casa] ou [latitude, longitude, altitude].

Então cada componente possui um número quase infinito de estados diferentes, pois, no caso do UILabel, para cada text diferente temos um estado diferente. Se cada estado é único e corresponde a um conjunto de configurações, porque não criar uma classe responsável por definir o estado de uma View? Precisamos de um nome para essa descrição de estado, como devemos fazer uma modelagem de quais as configurações são relevantes acho que ViewModel pode ser um nome adequado ;-)

Um LabelViewModel simplificado seria:

struct LabelViewModel {
    let text: String
    let textColor: UIColor
    let font: UIFont
}

A ViewModel deve ser imutável, então um struct parece mais adequado. Para definir uma configuração padrão é só definir um init:

init(text: String = "", 
     textColor: UIColor = UIColor.blackColor(),
     font: UIFont = UIFont.systemFontOfSize(17))
{
    self.text = text
    self.textColor = textColor
    self.font = font
}

E uma extension do UILabel faz a conexão:

extension UILabel {
    var viewModel: LabelViewModel {
        set(viewModel) {
            self.text = viewModel.text
            self.textColor = viewModel.textColor
            self.font = viewModel.font
        }
        get {
            return LabelViewModel(
                text: self.text ?? "",
                textColor: self.textColor,
                font: self.font)
        }
    }
}

A ideia parece interessante, mas esse exemplo dá uma impressão de excesso de complexidade. Concordo, realmente estamos trocando 6 por meia dúzia. Vamos tomar um exemplo mais real. Temos uma View que contém uma UILabel e um UIButton. Sua interface pública seria:

public class View : UIView {

    public var viewModel: ViewModel

    internal let label: UILabel
    internal let button: UIButton
}

Note que as subviews não são expostas e a única maneira de alterar o estado dessa View é alterando o ViewModel, que seria assim:

public struct ViewModel {

    public let attributedLabel: NSAttributedString
    public let attributedButtonText: NSAttributedString
    public let backgroundColor: UIColor
    public let onTap: ((viewModel: ViewModel) -> Void)?

    public init(
        attributedLabel: NSAttributedString = NSAttributedString(string: "Label"),
        attributedButtonText: NSAttributedString = NSAttributedString(string: "Button"),
        backgroundColor: UIColor = .grayColor(),
        onTap: ((viewModel: ViewModel) -> Void)? = nil)
    {
        self.attributedLabel = attributedLabel
        self.attributedButtonText = attributedButtonText
        self.backgroundColor = backgroundColor
        self.onTap = onTap
    }
}

Os atributos que podem ser alterados são oNSAttributedString da label, NSAttributedString do UIButton em UIControlState.Normal, a backgroundColor e um closure que é executado quando o botão tem um tap.

O estado dessa View depende de um Model:

public struct Model {

    public enum Emoji: String {
        case 👍, 👎, 👊
    }

    public let name: String
    public let emoji: Emoji

}

Dado que a configuração da View é necessariamente feita de maneira a representar visualmente o Model o ViewModel funciona como um adapter, uma interface com apenas com as configurações relevantes. Isso é representado por uma extension do ViewModel:

public extension ViewModel {

    public init(fromModel model: Model, onTap: (viewModel: ViewModel)->Void) {

        let name: NSAttributedString =
        NSAttributedString(
           string: model.name,
           attributes: [NSForegroundColorAttributeName: UIColor.redColor()])

        let text: String = model.emoji.rawValue

        self.init(
            attributedLabel: NSAttributedString(string: text),
            attributedButtonText: name,
            onTap: onTap)
    }

}

Aqui vemos mais uma grande vantagem dessa maneira de separar o código. Testar como será a representação da tela dado um modelo fica muito simples, não é necessário instanciar View nem ViewController, um simples teste unitário nessa extension é suficiente. Isso porque toda a lógica está isolada em apenas um lugar!

Já que falamos de ViewController, qual seria seu papel aqui? Muito simples:

let viewModel = ViewModel(fromModel: model) { viewModel in
    print("tap")
}
aView.viewModel = viewModel

Cria o ViewModel à partir do Model e define qual a ação que será tomada quando o botão for acionado. Menos código na ViewController, menos complexidade para testar porque tudo está isolado, mais uma vitória do bem!

Dessa forma todo o fluxo de informação entre a View e o ViewController necessariamente passa pelo ViewModel.

Um exemplo completo pode ser encontrado no repositório PlainMVVM.


O nome ViewModel não foi usado aqui por mero acaso, ele vem de um padrão de arquitetura conhecido como MVVM (Model-View-ViewModel). Ele tem se tornado popular, principalmente num contexto de programação reativa, inclusive em alguns artigos do equinociOS. Mas pouco se fala fora desse contexto, a NatashaTheRobot deu uma palestra sobre Protocol Oriented MVVM, com algumas idéias interessantes para organizar o código usando MVVM. A idéia que eu descrevi aqui pode não corresponder à um MVVM formal, mas ilustra de uma maneira razoavelmente simples como separar mais as responsabilidades facilitando testes e reaproveitamento de código.


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. certo?

  2. ¯\_(ツ)_/¯

Tela de login

Nota do autor: Estou começando um projeto novo e nele vou tentar usar o máximo sobre RxSwift que eu conseguir. E vou compartilhar grande parte dessa caminhada aqui no Invariante.

Todos nós já estivemos nessa situação: projeto novo, aplicativo novo. E, invariantemente: uma tela de login.

E muitas vezes é nesse momento que nos questionamos sobre as decisões mais básicas em relação a arquitetura e organização do código que vamos construir a partir desse momento. Se pararmos para pensar, a tela de login agrega e unifica várias faces do desenvolvimento de software: conexão com uma API, armazenamento (seguro) de informações do usuário, validação de dados, interação com o usuário e até bons cenários de testes.

E por isso é sempre uma ótima oportunidade para tentarmos, testarmos - e muitas vezes aprender - coisas novas. Eu acredito que uma tela de login oferece desafios para desenvolvedores de todos os níveis de experiência.

A tela de login

Nome de usuário, senha e um botão.

O cenário não poderia ser mais ideal para exercitarmos o uso de bindings. A idéia é simples: queremos habilitar o botão se os campos de nome de usuário e senha forem válidos. Por enquanto, vamos supor que ambos os campos são válidos se tiverem pelo menos um caractere.

Nenhuma novidade por aqui. Apenas por clareza, esses são nossos IBOutlets:

@IBOutlet private weak var usernameTextField: LoginTextField!
@IBOutlet private weak var passwordTextField: LoginTextField!
@IBOutlet private weak var loginButton: UIButton!

E, como em qualquer mundo, vamos definir as nossas duas funções de validação:

func validateUsername(username: String) -> Bool {
    return username.characters.count > 0
}
    
func validatePassword(password: String) -> Bool {
    return password.characters.count > 0
}

E agora, vamos fazer nosso binding:

let validUsername = usernameTextField.rx_text.map(validateUsername)
let validPassword = passwordTextField.rx_text.map(validatePassword)
[validUsername, validPassword]
    .combineLatest { $0.first! && $0.last! }
    .bindTo(loginButton.rx_enabled)
    .addDisposableTo(disposeBag)

É exatamente isso: essas 6 linhas de código resolvem o nosso problema.

Se você não tem idéia do que o código acima signfica, recomendo ler o meu post sobre RxSwift no equinocios.com.

Primeiramente, é importante saber que existe uma extensão do RxSwift chamada RxCocoa que aplica os conceitos do RxSwift em várias classes do Cocoa/UIKit. É através do RxCocoa que conseguimos fazer os bindings.

No código acima, fazemos um map em cima do rx_text (que nada mais é do que o stream de strings do UITextField. Lembre-se: no RxSwift tudo são streams). E as funções que utilizamos para fazer o map, nada mais são do que as nossas funções de validação declaradas mais acima.

Com isso, nossas variáveis validUsername e validPassoword são do tipo Observable<Bool> e não Bool, o que nos permite usar o operador combineLatest, que emitirá um array com dois Bool, toda vez que um dos streams validUsername ou validPassword emitirem um valor. E esses dois streams por sua vez irão emitir valores quando houver alguma mudança no usernameTextField e passwordTextField respectivamente.

O nosso combineLatest nada mais faz do que um && entre o primeiro e o último elemento do nosso array. Aqui usamos um force unwrap (é, eu sei: dói até de ver) porque sabemos que nosso array terá sempre dois elementos.

E agora? Qual o tipo do resultado do resultado do nosso combineLatest? Se você pensou Observable<Bool>, você já está pensando de uma maneira mais zen ☮️.

Por último (ou quase último), fazemos o bind desse resultado com o valor rx_enabled do nosso loginButton. Ou seja, toda vez que o resultado do combineLatest for true, nosso botão irá passar para o estado ativo. E toda vez que o resultado for false, nosso botão ficará inativo.

Por último (de verdade), temos o addDisposableTo. Essa é a forma que o RxSwift gerencia a memória e os recursos alocados. Se você quiser saber mais sobre as DisposeBags, recomento ler o Getting Started do RxSwift.


No próximo artigo, vamos trabalhar em cima de outro conceito simples, mas com uma abordagem funcional: vamos fazer com que a ação do nosso botão de login dispare a requisição de autenticação para a nossa API.

Bruno Koga
@brunokoga

Em Swift, nil é diferente de Zero

Objective-C possui uma característica muito interessante e polêmica, enviar mensagens para nil é válido e não lança excessão como em outras linguagens. Não vou discutir se isso é bom ou ruim, mas é algo que gosto e uso frequentemente quando programo nessa linguagem.

Um uso comum seria quando é necessário testar se um array é vazio:

if(array.count == 0) {
    NSLog(@"empty array");
}

Isso funciona porque o retorno de qualquer mensagem enviada para nil é 0, nil ou NULL dependendo do tipo de retorno da mensagem, semanticamente diferentes mas tecnicamente iguais a ZERO.

Então se array for nil, array.count retorna 0, mais uma vitória do bem e menos código escrito 😎.

Sem pensar muito o podemos escrever o equivalente em Swift:

if array.count == 0 {
    print("empty array")
}

Se array for um opcional, novamente sem pensar muito, poderíamos fazer um optional chaining e só colocar um ?:

if array?.count == 0 {
    print("empty array")
}

NÃO!! Quando array = nil, array?.count == 0 é falso, mas porque?

A resposta está em no funcionamento do optional chaining: ? tem um comportamento semelhante ao ! (force unwrapping), com a diferença que se o valor opcional for nil não é lançado um erro de runtime. Como não ocorre a interrupção do programa e afim de evitar inconsistências o resultado de chamadas de métodos, propriedades e subscripts sempre vão retornar um opcional, mesmo que o tipo original não seja. Por exemplo:

var array: [Int]?
let count = array?.count // count is Int? not Int

Isso significa que quando array = nil, count = nil que não é igual a 0 e por isso o teste anterior falha! Ou seja em Swift, nil != 0!

Para escrever o teste de maneira que funciona temos várias opções.

Definir que quando o resultado do count for nil o resultado esperado é 0:

if (array?.count ?? 0) == 0 {
    print("empty array")
}

Testar o nil e fazer force unwrapping. Não me agrada o force unwrapping, sei que nesse caso nunca aconteceria um erro de runtime mas prefiro evitar ao máximo o !:

if array == nil || array!.count == 0 {
    print("empty array")
}

Por algum motivo ainda desconhecido para mim, nil é menor que qualquer Int. Então temos uma opção que eu não recomendo, ¯\_(ツ)_/¯:

if !(array?.count > 0) {
    print("empty array")
}

Com certeza devem haver mais uma dezena de maneiras de escrever mas acho que já deu para ter uma idéia. Provavelmente o erro desse caso é transpor exatamente a mesma lógica do Objective-C para Swift.


Update 2016/02/16 - O Fabri e o Koga lembraram que o Array adota o protocolo CollectionType e portanto o isEmpty seria mais adequado:

if array?.isEmpty ?? true {
    print("empty array")
}

O Fabri pensou mais no assunto1 e propôs uma extensão para o Optional de IntegerType que evitaria o problema apresentado:

extension Optional where Wrapped: IntegerType {
    var valueOrZero: Wrapped {
        return self ?? 0
    }
}

Assim o teste ficaria:

if (array?.count).valueOrZero == 0 {
    print("empty")
}

Um bom exercício para evitar o erro, mas acho que a versão com o isEmpty ainda fica mais legível.


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. Assumindo que eu seja um cara teimoso e me recuse a usar o isEmpty

Uma collection view cell to rule them all

Feliz ano novo, feliz post novo!


Um dia desses o caro Vinicius estava reclamando da curva de aprendizado do UICollectionView, para quem não conhece é uma UITableViewController com esteroides.
Você pode usar layouts customizados, transições de animações e muitas outras coisas que eu nem consigo imaginar. Por coincidência nesse mesmo dia eu estava implementando minha primeira UICollectionView em Swift. Nesse post não vou falar sobre essa classe mas de sobre suas células UICollectionViewCell.

No último mês venho brigando muito com generics (ou genéricos) e consegui montar um exemplo interessante de uso aplicado à UICollectionViewCell. Esse classe não tem um label como a UITableViewCell, apenas uma contentView, isso dificulta exemplos mais simples pois implica que 100% da vezes você vai ter que customizar as células.

A UICollectionViewCell precisa de uma ou mais UIView que vão ser adicionadas à contentView para essa customização. Então seria natural que eu uma célula genérica dependesse desse tipo:

class CollectionViewCell<View: UIView>: UICollectionViewCell {

    private(set) var customView: View

    override init(frame: CGRect)
    {
        customView = View()

        super.init(frame: frame)
        
        contentView.addSubview(customView)
        
        customView.frame = contentView.bounds
        customView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
    }
}

Nesse caso específico não vejo necessidade de usar AutoLayout. Aqui temos algo bem simples, na criação da célula uma instância da View é criada adicionada à contentView de forma a ter sempre o seu tamanho. Para customizar essa view em collectionView(_: cellForItemAtIndexPath:) seria apenas utilizar a referência a ela em customView.

Mas se começarmos a pensar na linha do MVVM seria interessante que essa view aceitasse configuração via um ViewModel. Nesse caso teríamos um protocolo para tipos que possuem um model:

protocol HasModel {
    typealias Model
    var model: Model { get set }
}

Aqui temos um protocolo que possui um tipo associado (Associated Type), isso significa que o tipo da propriedade model pode ser diferente para cada tipo que adotar esse protocol. Entretanto isso tem alguns efeitos colaterais não relevantes para o exemplo corrente.

Vamos supor que eu não queira acessar a customView , mas passar o view model diretamente para a célula, como o modelo depende de cada view nossa célula vai passar a ter dois parâmetros, View e ViewModel. Essa classe então ficaria:

class CollectionViewCell<View: UIView, ViewModel where View: HasModel, View.Model == ViewModel>: UICollectionViewCell {

    private(set) var customView: View

    override init(frame: CGRect)
    {
        customView = View()

        super.init(frame: frame)
        contentView.addSubview(customView)

        customView.frame = contentView.bounds
        customView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
    }

    var model: ViewModel {
        get {
            return customView.model
        }

        set(newModel) {
            customView.model = newModel
        }
    }
}

Agora a coisa fica mais interessante, note a definição do generics <View: UIView, ViewModel where View: HasModel, View.Model == ViewModel>. Ele define um tipo View que é subclasse de UIView e um tipo ViewModel, o where aplica restrições a esse tipos, o View adota o HasModel e o tipo associado Model da View é o mesmo do ViewModel.

Isso é suficiente para que qualquer UIView que adote o HasModel seja usada em uma collection view. Vamos supor que eu queira usar um UILabel para isso, uma extension de poucas linhas isso está resolvido:

extension UILabel: HasModel {

    var model: String {
        get {
            return text ?? ""
        }
        set(newModel) {
            text = newModel
            textAlignment = .Center
        }
    }
}

Para usar isso numa collection view precisamos primeiro registrar a classe da célula genérica (no viewDidLoad por exemplo):

collectionView.registerClass(CollectionViewCell<UILabel, String>.self, forCellWithReuseIdentifier: reuseIdentifier)

E então configurar a célula:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)

    if let cell = cell as? CollectionViewCell<UILabel, String> {
        let max = collectionView.numberOfItemsInSection(indexPath.section)
        cell.model = "Cell \(indexPath.row+1)/\(max)"
    }

    return cell
}

Um exemplo completo pode ser encontrado no repositório GenericCollectionViewCell. 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