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

XCode nu suportă versiunea actuală de IOS. Ce este de făcut?

Tocmai ce-am făcut update la ultima versiune de IOS – 11.3 (15E216) și bineînțeles că versiunea mea de XCode – 9.2 (9C40b) nu mai îmi poate “vedea” telefonul pentru teste.Din ce motiv se întâmplă acest lucru?

Ori de câte ori este lansată o nouă versiune semnificativă de iOS, Apple lansează și o nouă versiune de XCode. La sfârșitul lunii martie 2018 a fost lansată versiunea 11.3 de IOS simultan cu XCode 9.3, prin urmare dacă sunt developer și îmi actualizez telefonul la ultima versiune de IOS, musai trebuie să actualizez și XCode-ul. Însă developer-ii nu-și actualizează XCode-ul imediat după apariția sa, acest lucru însemnând, de cele mai multe ori, upgrade de proiect.
Revenind, nouă versiune de XCode – 9.3 conține DeveloperDiskImage, un folder având numele versiunii, în cazul nostru – 11.3 (15E216), necesară pentru a rula proiectele pe cele mai recente versiuni de iOS. Prin urmare, pentru a rula o aplicație pe un IOS 11.3 ar trebui neapărat să actualizăm XCode-ul la 9.3.

Cum putem ieși din acest impas?
Momentan, țin XCode-ul la 9.2 fiind limitat de faptul că 9.3 îmi cere și upgrade la sistemul de operare, fiind necesar High Sierra. Soluția utilizată în aceste situații este folosirea unui disk DeveloperDiskImage de IOS 11.3 de la un XCode 9.3 pe care să-l importăm în Xcode-ul nostru 9.2. Practic, este un folder care va ști să “asculte” device-urile cu IOS 11.3 pe versiuni inferioare de XCode.

Această arhivă conține DeveloperDiskImage de IOS – 11.3 (15E216). Conținutul arhivei, folderul numit 11.3 (15E216) trebuie copiat în /Application/XCode/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/. Restart la XCode-ul nostru și acum putem rula aplicațiile noastre din XCode 9.2 în IOS 11.3

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

SWIFT – VALUE TYPE vs REFERENCE TYPE

Conform Apple “Structures and Enumerations Are Value Types” iar mai jos că “Classes Are Reference Types”.

Explicatie pe scurt!

struct Informatii {
      var info: Int; 
}
var firstStruct = Informatii(info:3)
var secondStruct = firstStruct						
firstStruct.info = 10						
print("\(firstStruct.info), \(secondStruct.info)")	// prints "10, 3"

Struct-ul, la fel ca si ENUM si TUPLU în SWIFT, fiind de tip valoare, atunci cand este copiata va crea o instanță independentă care va avea propriul său conținut independent de cel al sursei din care s-a copiat. Un fel de clonă independentă am putea spune.

class Informatii {
      var info: Int; 
}
var firstObject = Informatii(info:3)
var secondObject = firstObject						
firstObject.info = 10						
print("\(firstObject.info), \(secondObject.info)")	// prints "10, 10"

Deși în acest caz vorbim doar de înlocuirea unui cuvânt din STRUCT în CLASS, deosebirile sunt remarcabile.

firstObject este o instanță de tip Informatii. Are o zonă de memorie alocată și practic este un pointer către acea zonă.
Atunci când executăm secondObject = firstObject, ambele fiind deci de instanțe de clasă adică de tip referință, secondObject va pointa de fapt către aceeași zonă de memorie în care pointa și firstObject. Orice modificare efectuată de firstObject se va oglindi în secondObject.

Share Button

UPDATE – Protocoale & extensii Swift

Vreau să exemplific puterea unui protocol în Swift.
Avem două clase, aparent fără nimic în comun:

class Pescar {
    var nume : String
    var numarPermis: String
}
class Color {
    var rosu : Int
    var verde : Int
    var albastru : Int
}

Vreau să le convertim pe fiecare la String pentru a avea un output decent și corect pentru situații de genul:

print(Pescar(...)) 
print(Color(...))

Swift implementează un protocol exact pentru acest caz numit CustomStringConvertible. Prin urmare, vom extinde clasele noastre cu ajutorul acestui protocol CustomStringConvertible astfel încât print-ul pe clase sa fie custom:

extension Pescar : CustomStringConvertible {
    var description : String { get { return "Nume pescar: \(nume) - numar de permis: \(numarPermis)" } }
}
extension Color : CustomStringConvertible {
    var description : String { get { return "Color: \(rosu) -  \(verde) - \(albastru)" } }
}

Prin urmare, vom avea:

let pescarulGica = Pescar(nume: "Gica", numarPermis: "1234")

print(pescarulGica) // Nume pescar: Gica - numar de permis: 1234

și

let codCuloareCustom = Color(rosu: 23, verde: 45, albastru: 52)

print(codCuloareCustom) // Color: 23 -  45 - 52
Share Button