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