Dacă doriți să deveniți dezvoltator iOS, există câteva abilități fundamentale demne de cunoscut. În primul rând, este important să vă familiarizați cu crearea de vizualizări de tabel. În al doilea rând, ar trebui să știți cum să completați acele vizualizări de tabel cu date. În al treilea rând, este minunat dacă puteți prelua date dintr-un API și să le utilizați în vizualizarea în tabel.

Al treilea punct este ceea ce vom acoperi în acest articol. De la introducerea Codable în Swift 4, efectuarea apelurilor API este mult mai ușoară. Anterior, majoritatea oamenilor foloseau păstăi precum Alamofire și SwiftyJson (puteți citi despre cum să faceți acest lucru Aici). Acum, modul Swift este mult mai frumos, așa că nu există niciun motiv pentru a descărca un pod.

Să trecem prin câteva blocuri care sunt adesea folosite pentru a efectua un apel API. Vom acoperi mai întâi aceste concepte, deoarece acestea sunt părți importante pentru a înțelege cum să efectuați un apel API.

  • Manipulatori de finalizare
  • URLSession
  • DispatchQueue
  • Păstrează ciclurile

În cele din urmă, vom pune totul împreună. Voi folosi open source API Star Wars pentru a construi acest proiect. Puteți vedea codul meu complet de proiect pe GitHub.

Alertă de declinare a responsabilității: sunt nou în materie de codificare și sunt în mare parte autodidact. Îmi cer scuze dacă denaturez unele concepte.

Manipulatori de finalizare

Cum sa efectuati un apel API in Swift
Bietul pacient Pheobe

Vă amintiți episodul Prieteni în care Pheobe este lipit de telefon zile în șir pentru a vorbi cu serviciul clienți? Imaginați-vă dacă chiar la începutul acelui telefon, o persoană minunată numită Pip a spus: „Vă mulțumim că ați sunat. Nu am idee cât timp veți avea nevoie să așteptați în așteptare, dar vă sun înapoi când suntem gata Pentru dumneavoastră.” Nu ar fi fost la fel de amuzant, dar Pip se oferă să fie un manager de finalizare pentru Pheobe.

Folosiți un handler de finalizare într-o funcție când știți că funcția va dura ceva timp până la finalizare. Nu știi cât timp și nu vrei să-ți oprești viața așteptând să se termine. Așa că îi ceri lui Pip să te bată pe umăr când ea este gata să-ți dea răspunsul. În acest fel poți să-ți faci viața, să faci niște comisioane, să citești o carte și să te uiți la televizor. Când Pip te apasă pe umăr cu răspunsul, poți să iei răspunsul și să îl folosești.

Aceasta se întâmplă cu apelurile API. Trimiteți o solicitare URL către un server, cerându-i anumite date. Sperăm că serverul returnează datele rapid, dar nu știți cât va dura. În loc să faceți utilizatorul să aștepte cu răbdare serverul pentru a vă oferi datele, utilizați un handler de finalizare. Aceasta înseamnă că îi puteți spune aplicației să plece și să facă alte lucruri, cum ar fi încărcarea restului paginii.

Îi spuneți handlerului de finalizare să atingă aplicația pe umăr odată ce are informațiile dorite. Puteți specifica care sunt aceste informații. În acest fel, atunci când aplicația dvs. este atinsă pe umăr, poate prelua informațiile din handler-ul de finalizare și poate face ceva cu ea. De obicei, ceea ce veți face este să reîncărcați vizualizarea tabelului, astfel încât datele să apară utilizatorului.

Iată un exemplu de cum arată un handler de finalizare. Primul exemplu este configurarea apelului API în sine:

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
  // Setup the variable lotsOfFilms
  var lotsOfFilms: [Film]
  
  // Call the API with some code
  
  // Using data from the API, assign a value to lotsOfFilms  
  
  // Give the completion handler the variable, lotsOfFilms
  completionHandler(lotsOfFilms)
}
O funcție care folosește un handler de finalizare

Acum vrem să apelăm funcția fetchFilms. Câteva lucruri de remarcat:

  • Nu trebuie să faceți referință completionHandler când apelați funcția. Singura dată când te referi completionHandler se află în declarația de funcție.
  • Handlerul de finalizare ne va restitui câteva date de utilizat. Pe baza funcției pe care am scris-o mai sus, știm să ne așteptăm la date de tip [Film]. Trebuie să denumim datele, astfel încât să ne putem referi la ele. Mai jos folosesc numele films, dar ar putea fi randomData, sau orice alt nume de variabilă aș dori.

Codul va arăta cam așa:

fetchFilms() { (films) in
  // Do something with the data the completion handler returns 
  print(films)
}
Implementarea unei funcții cu un handler de finalizare

URLSession

URLSession este ca managerul unei echipe. Managerul nu face nimic de unul singur. Slujba ei este să împartă munca cu oamenii din echipa ei, iar ei vor face treaba. Echipa ei sunt dataTasks. De fiecare dată când ai nevoie de niște date, scrie-i șefului și folosește-le URLSession.shared.dataTask.

Puteți da dataTask diferite tipuri de informații pentru a vă ajuta să vă atingeți obiectivul. Oferirea de informații către dataTask se numește inițializare. Mă inițializez dataTasks cu adrese URL. dataTasks utilizați, de asemenea, handlerele de finalizare ca parte a inițializării lor. Iată un exemplu:

let url = URL(string: "https://www.swapi.co/api/films")

let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in 
  // your code here
})

task.resume()
Cum se utilizează URLSession pentru a prelua unele date

dataTasks utilizați handlerele de finalizare și returnează întotdeauna aceleași tipuri de informații: data, response și error. Puteți da acestor tipuri de date nume diferite, cum ar fi (data, res, err) sau (someData, someResponse, someError). Din motive de convenție, cel mai bine este să te ții de ceva evident, mai degrabă decât să devii necinstit cu noi nume de variabile.

Sa incepem cu error. Dacă dataTask returnează un error, veți dori să știți acest lucru în avans. Înseamnă că vă puteți direcționa codul pentru a gestiona eroarea cu grație. De asemenea, înseamnă că nu vă veți deranja să încercați să citiți datele și să faceți ceva cu ele, deoarece există o eroare la returnarea datelor.

Mai jos, gestionez eroarea într-adevăr, pur și simplu imprimând o eroare pe consolă și ieșind din funcție. Există multe alte modalități prin care puteți rezolva eroarea dacă doriți. Gândiți-vă la cât de fundamentale sunt aceste date pentru aplicația dvs. De exemplu, dacă aveți o aplicație bancară și acest apel API arată utilizatorilor soldul lor, atunci vă recomandăm să gestionați eroarea prezentând utilizatorului un mod care spune „Ne pare rău, avem o problemă chiar acum. Încercați din nou mai tarziu.”

if let error = error {
  print("Error accessing swapi.co: /(error)")
  return
}
Manipulați eroarea

În continuare ne uităm la răspuns. Puteți arunca răspunsul pentru a fi un httpResponse. În acest fel, puteți privi codurile de stare și puteți lua unele decizii pe baza codului. De exemplu, dacă codul de stare este 404, atunci știți că pagina nu a fost găsită.

Codul de mai jos folosește un guard pentru a verifica dacă există două lucruri. Dacă ambele există, atunci permite codului să treacă la următoarea instrucțiune după guard clauză. Dacă oricare dintre afirmații eșuează, atunci ieșim din funcție. Acesta este un caz de utilizare tipic al unui guard clauză. Vă așteptați ca codul după o clauză de gardă să fie fluxul zilelor fericite (adică fluxul ușor fără erori).

  guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
    print("Error with the response, unexpected status code: (response)")
    return
  }

În cele din urmă, vă ocupați de datele în sine. Observați că nu am folosit handlerul de finalizare pentru error sau response. Asta pentru că gestionarul de finalizare așteaptă date din API. Dacă nu ajunge la partea de date a codului, nu este necesar să invocați handlerul.

Pentru date, folosim JSONDecoder pentru a analiza datele într-un mod frumos. Acest lucru este destul de ingenios, dar necesită stabilirea unui model. Modelul nostru se numește FilmSummary. Dacă JSONDecoder este nou pentru dvs., apoi aruncați o privire online despre cum să îl utilizați și cum să îl utilizați Codable. Este foarte simplu în Swift 4 și versiuni superioare, comparativ cu Swift 3 zile.

În codul de mai jos, verificăm mai întâi dacă datele există. Suntem destul de siguri că ar trebui să existe, deoarece nu există erori și nu există răspunsuri HTTP ciudate. În al doilea rând, verificăm dacă putem analiza datele pe care le primim în modul în care ne așteptăm. Dacă putem, vom returna rezumatul filmului către dispozitivul de finalizare. Doar în cazul în care nu există date de returnat din API, avem un plan de rezervă al matricei goale.

if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }

Deci, codul complet pentru apelul API arată astfel:

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
    let url = URL(string: domainUrlString + "films/")!

    let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
      if let error = error {
        print("Error with fetching films: (error)")
        return
      }
      
      guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
        print("Error with the response, unexpected status code: (response)")
        return
      }

      if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }
    })
    task.resume()
  }

Păstrează ciclurile

NB: Sunt extrem de nou în ceea ce privește înțelegerea ciclurilor de reținere! Iată esența a ceea ce am cercetat online.

Ciclurile de reținere sunt importante pentru a înțelege pentru gestionarea memoriei. Practic, doriți ca aplicația dvs. să curețe bucăți de memorie de care nu mai are nevoie. Presupun că acest lucru face ca aplicația să fie mai performantă.

Există multe modalități prin care Swift vă ajută să faceți acest lucru automat. Cu toate acestea, există multe modalități prin care puteți codifica accidental cicluri de păstrare în aplicație. Un ciclu de reținere înseamnă că aplicația dvs. va păstra întotdeauna memoria pentru o anumită bucată de cod. În general, se întâmplă atunci când aveți două lucruri care au indicii puternice unul față de celălalt.

Pentru a evita acest lucru, oamenii folosesc adesea weak. Când o parte a codului este weak, nu aveți un ciclu de reținere și aplicația dvs. va putea elibera memoria.

În scopul nostru, un model comun este de a folosi [weak self] la apelarea API-ului. Acest lucru asigură că odată ce handlerul de finalizare returnează un anumit cod, aplicația poate elibera memoria.

fetchFilms { [weak self] (films) in
  // code in here
}

DispatchQueue

Xcode folosește fire diferite pentru a executa cod în paralel. Avantajul mai multor fire înseamnă că nu sunteți blocați în așteptarea unui singur lucru pentru a finaliza înainte de a trece la următorul. Sperăm că puteți începe să vedeți linkurile către handlerele de finalizare aici.

Aceste fire par a fi numite și cozi de expediere. Apelurile API sunt gestionate pe o singură coadă, de obicei o coadă în fundal. După ce ați obținut datele din apelul API, cel mai probabil veți dori să le arătați utilizatorului. Aceasta înseamnă că veți dori să reîmprospătați vizualizarea tabelului.

Vizualizările de tabel fac parte din interfața de utilizare și toate manipulările interfeței de utilizare ar trebui făcute în coada de expediere principală. Aceasta înseamnă undeva în fișierul controlerului de vizualizare, de obicei ca parte a fișierului viewDidLoad funcție, ar trebui să aveți un pic de cod care să indice vizualizarea tabelului să se actualizeze.

Vrem ca vizualizarea tabelă să se reîmprospăteze numai după ce are câteva date noi din API. Aceasta înseamnă că vom folosi un handler de finalizare pentru a ne atinge pe umăr și pentru a ne spune când se termină acel apel API. Vom aștepta până la atingerea respectivă înainte de a reîmprospăta masa.

Codul va arăta ca:

fetchFilms { [weak self] (films) in
  self.films = films

  // Reload the table view using the main dispatch queue
  DispatchQueue.main.async {
    tableView.reloadData()
  }
}

viewDidLoad vs viewDidAppear

În cele din urmă, trebuie să decideți unde să vă apelați fetchfilms funcţie. Va fi în interiorul unui controler de vizualizare care va utiliza datele din API. Există două locuri evidente în care ați putea efectua acest apel API. Unul este înăuntru viewDidLoad iar cealaltă este înăuntru viewDidAppear.

Acestea sunt două stări diferite pentru aplicația dvs. Înțelegerea mea este viewDidLoad se numește prima dată când încărcați acea vizualizare în prim-plan. viewDidAppear este apelat de fiecare dată când reveniți la vizualizarea respectivă, de exemplu atunci când apăsați butonul Înapoi pentru a reveni la vizualizare.

Dacă vă așteptați ca datele dvs. să se schimbe între momentele în care utilizatorul va naviga către și din vizualizarea respectivă, atunci vă recomandăm să introduceți apelul API în viewDidAppear. Cu toate acestea, cred că pentru aproape toate aplicațiile, viewDidLoad e suficient. Apple recomandă viewDidAppear pentru toate apelurile API, dar asta pare a fi exagerat. Îmi imaginez că ar face aplicația dvs. mai puțin performantă, deoarece face mai multe apeluri API de care are nevoie.

Combinând toți pașii

Mai întâi: scrieți funcția care apelează API-ul. Mai sus, acesta este fetchFilms. Acesta va avea un handler de finalizare, care va returna datele care vă interesează. În exemplul meu, handlerul de finalizare returnează o serie de filme.

În al doilea rând: apelați această funcție în controlerul de vizualizare. Faceți acest lucru aici, deoarece doriți să actualizați vizualizarea pe baza datelor din API. În exemplul meu, reîmprospătez o vizualizare de tabel după ce API-ul returnează datele.

În al treilea rând: decideți unde doriți să apelați funcția în controlerul dvs. de vizualizare. În exemplul meu, îl numesc viewDidLoad.

În al patrulea rând: decideți ce să faceți cu datele din API. În exemplul meu, reîmprospătez o vizualizare în tabel.

Interior NetworkManager.swift (această funcție poate fi definită în controlerul de vizualizare, dacă doriți, dar folosesc modelul MVVM).

func fetchFilms(completionHandler: @escaping ([Film]) -> Void) {
    let url = URL(string: domainUrlString + "films/")!

    let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
      if let error = error {
        print("Error with fetching films: (error)")
        return
      }
      
      guard let httpResponse = response as? HTTPURLResponse,
            (200...299).contains(httpResponse.statusCode) else {
        print("Error with the response, unexpected status code: (response)")
        return
      }

      if let data = data,
        let filmSummary = try? JSONDecoder().decode(FilmSummary.self, from: data) {
        completionHandler(filmSummary.results ?? [])
      }
    })
    task.resume()
  }

Interior FilmsViewController.swift:

final class FilmsViewController: UIViewController {
  private var films: [Film]?

  override func viewDidLoad() {
    super.viewDidLoad()
    
    NetworkManager().fetchFilms { [weak self] (films) in
      self?.films = films
      DispatchQueue.main.async {
        self?.tableView.reloadData()
      }
    }
  }
  
  // other code for the view controller
}

Doamne, am reușit! Vă mulțumesc că ați rămas cu mine.