Delegation Pattern – SWIFT

Delegation pattern reprezintă un mijloc elegant prin care se realiza comunicarea între instanțe, o situație foarte des întâlnită fiind aceea a transferului de informații între diferite viewcontroller-e. Pentru cei din Apple@DMT, situația este des întâlnită.

Pentru început să considerăm clasele următoare:

class LoginAPIService {
}
class LoginViewController {
  private let callLoginService = LoginAPIService()
}

În acest moment avem două clase LoginAPIService – ce pune la dispoziție servicii specifice de login și LoginViewController – clasă care va acoperi un View Controller desenat în Storyboard, de exemplu.

Putem transmite mesaje către LoginAPIService foarte lejer prin instanța privată callLoginService. E o problemă, în schimb, transmiterea de informații de la LoginAPIService la  LoginViewController.

Cineva ar putea spune: de ce să nu facem la fel, o instanță privată LoginViewController  în LoginAPIService? Păi dacă LoginAPIService este un serviciu oferit ViewController-elor, e nevoie de câte o instanță a fiecărui ViewController în LoginAPIService. E puțin peste mână o astfel de abordare și, prin urmare, trebuie să găsim o soluție pentru această problemă a transmisiei de mesaje de la LoginAPIService către LoginViewController.

Delegation pattern reprezintă  soluția pentru astfel de probleme.  Apple ne ajută să implementăm astfel de pattern-uri prin intermediul protocoalelor.

Protocolul, pe scurt, reprezintă o clasă abstractă care, atunci când cineva aderă la el, trebuie obligatoriu implementat. Altfel spus, daca ader la un protocol, trebuie sa implementez toate metodele declarate de către acesta.

protocol LoginAPIDelegate: class {
  func didRetrieveLoginUserDetailsFromServer(_ user: UserModel)
}

Cum folosim acest protocol în continuare?

class LoginAPIService {
weak var delegate: LoginAPIDelegate?
} 

Ce este acest delegate  declarat ca și  proprietate în interiorul LoginAPIService?

Este o instanță de LoginAPIService care mă asigură de faptul că toate metodele definite în interiorul protocolului vor fi implementate de către această proprietate.

Cine anume “va dori” să fie de fapt acest delegat? Dacă vom extinde clasa LoginViewController astfel încât ea să fie conformă cu protocolul nostru LoginAPIDelegate, atunci acea proprietate delegate va putea fi chiar LoginViewController. 

extension LoginViewController: LoginAPIDelegate {
    func didRetrieveLoginUserDetailsFromServer(_ user: UserModel)
 {
    // prelucram datele care au fost furnizate de catre serviciu 
  }
}

Ceea ce este cel mai interesant, acum urmează. În interiorul clasei LoginViewController specificăm că aceasta este, de fapt cea care-și atribuie calitatea de delegat pentru instanța callLoginService de tip LoginAPIService. Astfel, callLoginService va putea comunica mesaje de la LoginViewController -> LoginAPIService, iar callLoginService.delegate va comunica mesaje de la LoginAPIService -> LoginViewController. 

Comunicare  LoginAPIService -> LoginViewController 

Pentru a realiza transportul de mesaje de la  LoginAPIService către LoginViewController vom face apel de  didRetrieveLoginUserDetailsFromServer(_: ). Astfel, cine și-a asumat rolul de delegat (deci e conform cu protocolul LoginAPIDelegate), va executa apelul. 

class LoginAPIService {
  weak var delegate: LoginAPIDelegate?
  func connectToServer() {
    // facem request-ul de login și ambalăm datele în userModel 
    delegate?.didRetrieveLoginUserDetailsFromServer(userModel)
  }
}

Astfel, am reușit să rezolvăm problema transferului de mesaje între cele două entități.

Share Button

De ce @objc în SWIFT 4?

Atunci când adăugăm o clauză @objc unei metode în Swift, aceasta din urmă devine accesibilă din Objective C. Bun, teoretic sună foarte riguros, dar dacă eu nu am nici un fel de cod Objective C în aplicație? Ar trebui să nu am nici o problemă, nu?

Swift 4 folosește încă în fundal foarte multe protocoale ObjectiveC.


UITableViewDataSource in ObjC

Ar trebui de fiecare dată când implementăm metodele din UITableViewDataSource să le specificăm fiecăreia un @objc. Swift 4 are grijă și nu ne forțează în acest sens.

Bun, și atunci unde și de ce am nevoie de @objc în cod Swift 4?

Cel mai bun exemplu în acest sens este o metodă care folosește un #selector. Avem nevoie să executăm o metodă loadRecordings după un delay de 0.5 secunde în thread-ul curent.

Pentru aceasta folosesc perform(_:with:afterDelay:), unde primul argument intern este un Selector, un struct ObjectiveC Runtime.

    perform(#selector(loadRecordings), with: nil, afterDelay: 0.5)

Deci selectorul din perform va trebui sa fie vizibil în runtime direct în ObjectiveC. Deci ar trebui ca metoda loadRecordings să aibe specificată o clauză @objc.
Deci prin deducție inversă, am ajuns la concluzia că trebuie să definesc astfel metoda loadRecordings

@objc func loadRecordings() {
.....
    }

Lipsa acestei clauze @objc îmi va genera o eroare la compilare așa cum se vede și mai jos:

Share Button

Swift 4 – Cât de util este closure în UiAlertController?

UIAlertController

Vorbeam acum ceva timp despre closure într-un context mai didactic. De curând am avut nevoie pentru a oferi o explicație despre closure într-un context ceva mai amplu.

Cadrul în care se desfășoară activitatea noastră este următorul:

 

Sunt într-un ViewController – VC1 – și apăsând pe un Button (pasul 1) ajungem într-un al doilea ViewControoler – VC2. În acest al doilea VC trebuie sa completăm câteva detalii să zicem – nume și email și să le aruncăm peste rețea (pasul 2). Serverul procesează ceea ce i-am trimis noi și ne răspunde (pasul 3) cu un mesaj de “succes“.

În momentul în primim mesajul de “succes” deschidem un UIAlertController (pasul 4) care imi arată mesajul primit. La apăsarea butonului de OK (pasul 5) trebuie sa eliberăm VC2 și să ne întoarcem în VC1.

Ce problemă avem la pasul 5? Operațiunea de eliberare a VC2 trebuie făcută în momentul în care se face click pe OK, nu în momentul în care se afisează UIAlertController-ul.

Funcția care face afișarea UIAlertController-ului este definită într-un Alertmanager astfel:

static func showGenericDialog(_ message: String, viewController: UIViewController, completionHandler: @escaping () -> Void ) {
        
        let alert = UIAlertController(title: "TITLE", message: message, preferredStyle: UIAlertControllerStyle.alert)
        let okAction: UIAlertAction = UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction) in
            completionHandler()
            
        })
        alert.addAction(okAction)
        OperationQueue.main.addOperation {
            viewController.present(alert, animated: true, completion: nil)
        }
        
    }

Ce se observă? Am definit în okAction un UIAlertAction cu un handler care se va “activa” atunci când se apasă pe okAction. Acțiunea din handler, funcția anonimă prezentă acolo va fi implementată în momentul în care această funcție este apelată.

Prin urmare, avem un minunat closure în momentul în care vom apela funcția showGenericDialog:

AlertManager.showGenericDialog(message, viewController: self!,completionHandler: {
                            self?.dismiss(animated: true, completion: nil)
                        })

Avem prin urmare cu ajutorul funcției anonime trecerea de la VC2 la VC1 atunci când utilizatorul face click pe butonul de OK.

Share Button

XCODE 9 – Main Thread Checker

Tocmai ce am receptționat următoarea avertizare în XCode:

XCode- Main Thread only

si am considerat oportun momentul de a explica un pic ce înseamnă aceste avertizări noi de culoare violet care apar în XCode.

În XCode 9, Apple aduce un nou tool numit Main Thread Checker, specificând faptul că “Main Thread Checker Detects invalid use of AppKit, UIKit, and other APIs from a background thread.

Să detaliem un pic acest nou tool din XCode 9. Apple a proiectat câteva din framework-urile sale pentru a funcționa doar în main thread, utilizarea acestora în background thread fiind foarte delicată. Mai pe românește va îngheța ecranul aplicației. Dacă până la versiunea 9 a XCode-ului această problemă trebuia gestionată exclusiv de developer, de la versiunea 9 în sus, XCode-ul ne dă un mic bobărnac în acest sens.

Exemplul celor de la Apple este similar cu problema pe care o am eu:

 

let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
   if let data = data {      
      self.label.text = "\(data.count) bytes downloaded"
      // Error: label updated on background thread   
   }
}
task.resume()

Ideea este că vreau să modific o etichetă în UI asteptând după completion handler. Dacă răspunsul de la server întârzie, implicit voi avea o problemă cu UI deoarece ea nu este funcțională decât după ce termin completion handler-ul. Acțiunea de networking din background îmi provoacă probleme!

Main Thread Checker din XCode 9 vine tocmai cu o mică avertizare în acest sens:  runtime: UI API called from background thread: UILabel.text must be used from main thread only, sugerându-ne și ceea ce trebuie făcut: să mutăm în main thread acțiunea de UI, fără a compromite integritatea aplicației:

 

let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
   if let data = data {    
      DispatchQueue.main.async {  
      self.label.text = "\(data.count) bytes downloaded"
      }
   }
}
task.resume()

 

Concluzia este următoarea: dacă avem activități de UI care asteaptă un mesaj din o activitate de background thread, e recomandată trecerea activității de UI în main thread folosind DispatchQueue.main.async

Share Button

Swift 4 – Serializare

Aplicațiile IOS au permanentă nevoie de comunicare peste rețea, fie către un server local sau internet.
Dacă până la SWIFT 4 lucrurile erau pre puțin standardizate, surpiza făcută de Apple este pe măsura așteptărilor.

Să începem discuția.

O aplicație IOS va trimite peste rețea date în format binar sau text. Pentru a realiza acest lucru are nevoie de convertirea instanțelor din aplicația locală într-un format diferit care este acceptat de mașina destinație. Acest proces de conversie a informațiilor locale în format inteligibil de către un server se numește SERIALIZARE. Apple propune un mecanism de encoding pentru a realiza această conversie și un mecanism invers de conversie a răspunsului venit de la server în informație inteligibilă în aplicație, numit decoding.

Protocoale necesare: Encodable & Decodable & Codable

Pentru a conforma o clasă la Codable trebuie ca toate proprietățile sale să fie în prealabil Codable.
Tipurile standard: Float, Int, String, Data, Date, URL sunt conforme cu Codable, prin urmare structura următoare este explicit Codable, suportând metodele init(from:) și encode(to:).

struct Pescar:Codable {
    var nume: String
    var numarPermis: Int
}

Trebuie să specificăm faptul că tipurile Array, Dictionary sau Optional sunt Codable dacă ele conțin tipuri de date Codable.
Un mic exemplu în acest sens:

let pescarIncepator = Pescar(nume: "Danut", numarPermis: 1)
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(pescarIncepator)
print("JSON venit din Codable: /(jsonData)")
// JSON venit din Codable: 10 bytes

jsonData reprezintă formatul binar pe care-l va primi serverul și pe care-l va transforma ulterior în JSON-ul așteptat.
Dacă încercăm să afișăm conținutul jsonData vom obține de fapt doar o dimensiune a binarului care conține JSON-ul.
“Miezul” jsonData-ului poate fi afișat sub forma:

let jsonString = String(data: jsonData, encoding: .utf8)
print("JSON venit din Codable: /(jsonString)")
// JSON venit din Codable: {"nume":"Danut","numarPermis":1}

Raspunsul serverului trebuie decodificat pe cale inversă, folosind decode(_:from:)

let jsonDecoder = JSONDecoder()
let pescarAvansat = try jsonDecoder.decode(Pescar.self, from: jsonDataResponse)
// JSON venit din Codable: {"nume":"Dorulet","numarPermis":2}

Observații
1.jsonDataResponse este raspunsul furnizat de server ca urmare a unui request furnizat anterior.
2. decoder-ul are nevoie să cunoască spre ce tip va converti JSON-ul prin jsonDataResponse

Situație critică – în backend s-a modificat structura tabelei asociată pescarilor noștri astfel încât în acest moment câmpul numarPermis a fost redenumit în idPermis. Cum se poate gestiona o astfel de situație?

Swift 4 vine cu un protocol nou CodingKey și cu un enumeration special CodingKeys.
Ce avem de făcut astfel încât să serializăm instanța noastră de tip Pescar pentru ca pe server JSON-ul să conțină idPermis?
Vom modela un pic struct-ul nostru:

struct Pescar:Codable {
    var nume: String
    var numarPermis: Int
    
    enum CodingKeys: String, CodingKey {
        case nume 
        case numarPermis = "idPermis"
        
    }
}

Prin urmare,

let pescarIncepator = Pescar(nume: "Danut", numarPermis: 1)
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(pescarIncepator)

let jsonString = String(data: jsonData, encoding: .utf8)
print("JSON venit din Codable: /(jsonString)")
// JSON venit din Codable: {"nume":"Danut","idPermis":1}

Share Button