TSM - Extensiile din Swift

Mihai Fischer - mihaifischer.com

Proiectele iOS pot deveni foarte ușor pline de clase foarte mari, greu de citit și de înțeles. Deși în Swift putem spune că am scăpat de jumătate din fișierele dintr-un proiect, aglomerarea din interiorul lor, în special a viewControllerelor a rămas cel puțin la fel.

În acest articol, voi prezenta trei modalități în care extensiile pot ajuta în păstrarea ordinii și lizibilității codului.

Extensiile din Swift sunt folosite în mod normal, pentru a extinde funcționalitatea unui tip, unei clase, unui struct, enum sau al unui protocol.

Asemănător categoriilor din Objective-C, extensiile pot fi folosite și la extinderea unor tipuri la care nu avem acces la codul sursă. Dar, spre deosebire de categorii, sunt anonime:

// se foloseste cuvantul cheie extension urmat de 
// tipul extins
extension UIViewController {
   // noua funcționalitate
}

Conformarea la noi protocoale se face la fel ca la clase sau structuri:

extension UIViewController: Protocol, AltProtocol {
   // implementarea noilor protocoale
}

Separarea metodelor private de partea publică a clasei

În Objective-C am avut fișierele .h și .m. Cu toate că era anevoios de le menținut pe ambele, era ușor să vezi interfața publică a clasei respective doar aruncând o scurtă privire în .h. În timp ce tot ce era privat, se afla în fișierul de implementare .m.

Cum în Swift avem un singur fișier, ne putem folosi de extensii pentru a obține o implementare asemănătoare.

// așa e ușor să vezi care metode și proprietăți ale 
// structurii sunt publice
struct TodoItemViewModel {    
    let item: TodoItem
    let indexPath: NSIndexPath

    var delegate: ImageWithTextCellDelegate {
        return TodoItemDelegate(item: item)
    }

    var attributedText: NSAttributedString {
        // logica itemContent e în extensia privată
        // făcând codul mult mai ușor de citit
        return itemContent
    }
}

// toată logica e în afara API-ului public
// MARK: Metode Private
private extension TodoItemViewModel {

  static var spaceBetweenInlineImages: 
   NSAttributedString {
        return NSAttributedString(string: "   ")
    }

  var itemContent: NSAttributedString {
    let text = NSMutableAttributedString(
      string: item.content, attributes: 
      [NSFontAttributeName : 
  Monsetrat.regularFontOfSize(16.0)])

    if let dueDate = item.dueDate {
      appendDueDate(dueDate, toText: text)
    }

    for assignee in item.assignees {
      appendAvatar(ofUser: assignee, toText: text)
    }

      return text
    }

    func appendDueDate(dueDate: NSDate, 
      toText text: NSMutableAttributedString) {
        //...
    }

    func appendAvatar(ofUser user: User, 
      toText text: NSMutableAttributedString) {
       //...
    }

    func downloadAvatarImage(
      forResource resource: Resource?) {
        //...
    }

    func appendDefaultAvatar(ofUser user: 
      User, toText text: NSMutableAttributedString) {
        //...
    }

    func appendImage(image: UIImage, 
      toText text: NSMutableAttributedString) {
      //...

}

Conformarea la protocoale

Cum afirmam și la începutul articolului, în Swift se pot folosi extensiile pentru conformarea unui tip existent la un protocol.

Vom da ca exemplu protocoalele UITableViewDalagate si UITableViewDataSource. Este un caz foarte des întâlnit, care poate duce la îngreunarea controllerului în care se face implementarea.

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    //...

}

Putem organiza mai bine codul, creând o extensie pentru fiecare protocol ce urmează implementat:

import UIKit

class ViewController: UIViewController {
    //...
}

extension ViewController: UITableViewDataSource {
    //...
}

extension ViewController: UITableViewDelegate {
    //...
}

În acest fel, inclusiv navigarea în fișier devine mai ușoară.

Salvarea funcțiilor de inițializare implicite

Vom da ca exemplu o structură simplă cu două constante. Deși este ușor de trecut cu vederea peste, Swift ne creează în mod implicit o funcție de inițializare.

struct Person {
    // MARK: - Proprietăți

    let first: String
    let last: String

}

// inițializare implicită:
let person = Person(first: "Ionica", last: "Magie")

În momentul în care vrem să facem o funcție de inițializare personalizată, cea implicită nu mai este disponibilă.

struct Person {

    // MARK: - Proprietăți

    let first: String
    let last: String

    // MARK: - Inițializare

    init(dictionary: [String: String]) {
        self.first = dictionary["first"] ?? "Ionica"
        self.last = dictionary["last"] ?? "Magie"
    }
}

Putem preveni acest lucru cu extensii. E bine să avem disponibile toate funcțiile de inițializare, chiar și cea implicită, lucru care duce la o transparență mai bună a ce se întâmplă în proiect.

struct Person {
    // MARK: - Proprietăți
    let first: String
    let last: String
}
extension Person {
    // MARK: - Inițializare

    init(dictionary: [String: String]) {
        self.first = dictionary["first"] ?? "Ionica"
        self.last = dictionary["last"] ?? "Magie"
    }
}

Concluzie

Extensiile folosite în acest mod mai puțin clasic, pot face un proiect mult mai ușor de înțeles și de citit. Dar important este să menținem echilibrul și să nu exagerăm cu întrebuințarea lor.