Ako uskutočniť volanie API v aplikácii Swift

Ak sa chcete stať vývojárom pre iOS, je treba poznať niekoľko základných zručností. Najskôr je dôležité sa oboznámiť s vytváraním zobrazení tabuľky. Po druhé, mali by ste vedieť, ako vyplniť tieto zobrazenia tabuľky údajmi. Po tretie, je skvelé, ak môžete načítať údaje z rozhrania API a použiť ich v zobrazení tabuľky.

Tretím bodom je to, čomu sa budeme v tomto článku venovať. Od uvedenia systému CodableSwift 4 je uskutočňovanie hovorov API oveľa jednoduchšie. Predtým väčšina ľudí používala pody ako Alamofire a SwiftyJson (o tom, ako to urobiť, sa dočítate tu). Teraz je spôsob Swift omnoho príjemnejší po vybalení z krabice, takže nie je dôvod sťahovať si modul.

Prejdime si niekoľko stavebných blokov, ktoré sa často používajú na uskutočnenie hovoru API. Najskôr sa budeme zaoberať týmito konceptmi, pretože sú dôležitou súčasťou porozumenia spôsobu uskutočnenia volania API.

  • Spracovatelia dokončenia
  • URLSession
  • DispatchQueue
  • Zachovať cykly

Nakoniec to všetko spojíme. Na zostavenie tohto projektu použijem open source Star Wars API. Celý môj kód projektu si môžete pozrieť na GitHub.

Upozornenie na vylúčenie zodpovednosti: V programovaní som nováčikom a do veľkej miery som samouk. Ospravedlňujem sa, ak zavádzam niektoré pojmy.

Spracovatelia dokončenia

Pamätáte si epizódu Priatelia, keď je Pheobe prilepená k telefónu niekoľko dní čakajúcich na rozhovor so zákazníckym servisom? Predstavte si, že na samom začiatku tohto telefonátu hovoril milý človek menom Pip: „Ďakujem, že si zavolal. Netuším, ako dlho budete musieť čakať na počkanie, ale zavolám vám, keď budeme pripravení. pre teba." Nebolo by to také vtipné, ale Pip sa ponúka ako vybavovač Pheobe.

Obslužný program dokončovania používate vo funkcii, keď viete, že dokončenie tejto funkcie bude chvíľu trvať. Neviete, ako dlho, a nechcete pozastaviť svoj život čakaním na dokončenie. Preto požiadate Pip, aby vám klepla po pleci, keď bude pripravená odpovedať. Takto môžete ísť o svoj život, vybavovať nejaké záležitosti, čítať knihy a pozerať televíziu. Keď ťa Pip klepne s odpoveďou na rameno, môžeš vziať jej odpoveď a použiť ju.

To sa deje pri volaniach API. Pošlete žiadosť o adresu URL na server a požiadate ho o nejaké údaje. Dúfate, že server vráti dáta rýchlo, ale neviete, ako dlho to bude trvať. Namiesto toho, aby ste svojho používateľa nechali trpezlivo čakať, kým vám server dá údaje, použijete obslužný program dokončovania. To znamená, že môžete svojej aplikácii povedať, aby odchádzala a robila iné veci, napríklad načítanie zvyšku stránky.

Povedzte obsluhovateľovi dokončenia, aby klepol na vašu aplikáciu po pleci, keď má požadované informácie. Môžete určiť, o aké informácie ide. Týmto spôsobom, keď vaša aplikácia klepne na rameno, môže vziať informácie od obslužného programu dokončenia a niečo s nimi urobiť. Spravidla stačí načítať tabuľkové zobrazenie, aby sa údaje zobrazili používateľovi.

Tu je príklad toho, ako vyzerá obslužný program dokončovania. Prvým príkladom je nastavenie samotného volania API:

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) }

Teraz chceme zavolať funkciu fetchFilms. Niekoľko vecí, ktoré je potrebné poznamenať:

  • completionHandlerPri volaní funkcie sa nemusíte odvolávať . Jediný odkaz, na ktorý completionHandlersa odvolávate, je vo vnútri deklarácie funkcie.
  • Obslužný program dokončenia nám vráti niektoré údaje, ktoré môžeme použiť. Na základe funkcie, ktorú sme napísali vyššie, vieme očakávať údaje, ktoré sú typu [Film]. Musíme pomenovať údaje, aby sme sa na ne mohli odvolávať. Ďalej používam meno films, ale mohlo by to byť randomData, alebo akýkoľvek iný názov premennej, ktorý by som chcel.

Kód bude vyzerať asi takto:

fetchFilms() { (films) in // Do something with the data the completion handler returns print(films) }

URLSession

URLSessionje ako manažér tímu. Vedúca nerobí nič sama. Jej úlohou je zdieľať prácu s ľuďmi v jej tíme a tí túto prácu dokončia. Jej tím je dataTasks. Zakaždým, keď potrebujete nejaké údaje, napíšte šéfovi a použite URLSession.shared.dataTask.  

Môžete poskytnúť dataTaskrôzne typy informácií, ktoré vám pomôžu dosiahnuť váš cieľ. Poskytovanie informácií dataTasksa nazýva inicializácia. Inicializujem svoje dataTasksadresy URL. dataTasksv rámci svojej inicializácie tiež použite obslužné rutiny dokončovania. Tu je príklad:

let url = URL(string: "//www.swapi.co/api/films") let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in // your code here }) task.resume()

dataTaskspoužite dokončenie rutiny, a vždy sa vráti rovnaké typy informácií: data, responsea error. Týmto dátovým typom môžete dať rôzne názvy, napríklad (data, res, err)alebo (someData, someResponse, someError). Z dôvodu konvencie je lepšie držať sa niečoho zrejmého, ako sa správať nečestne s novými názvami premenných.

Začnime s error. Ak sa dataTaskvráti error, budete to vedieť vopred. Znamená to, že môžete nasmerovať svoj kód na elegantné riešenie chyby. Znamená to tiež, že sa nebudete obťažovať čítať údaje a niečo s nimi robiť, pretože pri vrátení údajov sa vyskytla chyba.

Ďalej sa s chybou vyrovnávam naozaj jednoducho tak, že vytlačím chybu na konzolu a opustím funkciu. Existuje mnoho ďalších spôsobov, ako môžete chybu vyriešiť, ak by ste chceli. Popremýšľajte, aké zásadné sú tieto údaje pre vašu aplikáciu. Napríklad ak máte bankovú aplikáciu a toto volanie API ukazuje používateľom ich zostatok, možno budete chcieť chybu vyriešiť tak, že používateľovi predstavíte modál so slovami: „Je nám ľúto, momentálne máme problém. Vyskúšajte opäť neskôr. “

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

Ďalej sa pozrieme na odpoveď. Odpoveď môžete odovzdať ako httpResponse. Takto sa môžete pozrieť na stavové kódy a na základe tohto kódu urobiť nejaké rozhodnutia. Napríklad ak je stavový kód 404, viete, že stránka sa nenašla.

Nasledujúci kód slúži guardna kontrolu toho, či existujú dve veci. Ak existujú, potom to umožňuje kódu pokračovať na ďalší príkaz po guardklauzule. Ak niektorý z príkazov zlyhá, ukončíme funkciu. Toto je typický prípad použitia guardklauzuly. Očakávate, že kód po ochrannej doložke bude tok šťastných dní (tj. Ľahký tok bez chýb).

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

Nakoniec spracujete samotné údaje. Všimnite si, že sme nepoužili obslužný program dokončovania pre erroralebo response. Je to preto, že obslužný program dokončovania čaká na údaje z API. Ak sa nedostane k dátovej časti kódu, nie je potrebné vyvolať obslužnú rutinu.

Pokiaľ ide o údaje, pomocou nich JSONDecoderanalyzujeme údaje pekným spôsobom. Je to celkom šikovné, ale vyžaduje to, aby ste vytvorili model. Náš model sa volá FilmSummary. Ak JSONDecoderje pre vás novinka, pozrite sa, ako ju používať a ako ju používať Codable. V Swift 4 a vyšších je to v porovnaní so Swift 3 dni naozaj jednoduché.

V nižšie uvedenom kóde najskôr skontrolujeme, či údaje existujú. Sme si celkom istí, že by to malo existovať, pretože tu nie sú žiadne chyby ani podivné odpovede HTTP. Po druhé, skontrolujeme, či môžeme analyzovať údaje, ktoré dostávame, spôsobom, aký očakávame. Ak môžeme, potom vrátime súhrn filmu spracovateľovi dokončenia. Len v prípade, že z API nie je možné vrátiť žiadne údaje, máme záložný plán prázdneho poľa.

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

Celý kód pre volanie API teda vyzerá takto:

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() }

Zachovať cykly

NB: Som veľmi nový v porozumení cyklov zadržania! Tu je podstata toho, čo som skúmal online.

Pre správu pamäte je dôležité pochopiť cykly uchovania. V zásade chcete, aby vaša aplikácia vyčistila kúsky pamäte, ktoré už nepotrebujú. Predpokladám, že to robí aplikáciu výkonnejšou.

Existuje veľa spôsobov, ako vám s tým Swift pomáha automaticky. Existuje však veľa spôsobov, ako môžete omylom kódovať cykly zadržania vo svojej aplikácii. Cyklus uchovania znamená, že vaša aplikácia bude vždy držať určitú časť kódu v pamäti. Spravidla sa to stane, keď máte dve veci, ktoré majú na seba silné odkazy.

Ľudia to často obchádzajú, aby to obišli weak. Ak je jedna strana kódu weak, nemáte cyklus uchovávania a vaša aplikácia bude schopná uvoľniť pamäť.

Pre náš účel je bežným vzorom použitie [weak self]pri volaní API. To zaisťuje, že akonáhle obslužný program dokončenia vráti nejaký kód, aplikácia môže uvoľniť pamäť.

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

DispatchQueue

Xcode uses different threads to execute code in parallel. The advantage of multiple threads means you aren't stuck waiting on one thing to finish before you can move on to the next. Hopefully you can start to see the links to completion handlers here.

These threads seem to be also called dispatch queues. API calls are handled on one queue, typically a queue in the background. Once you have the data from your API call, most likely you'll want to show that data to the user. That means you'll want to refresh your table view.

Table views are part of the UI, and all UI manipulations should be done in the main dispatch queue. This means somewhere in your view controller file, usually as part of the viewDidLoad function, you should have a bit of code that tells your table view to refresh.

We only want the table view to refresh once it has some new data from the API. This means we'll use a completion handler to tap us on the shoulder and tell us when that API call is finished. We'll wait until that tap before we refresh the table.

The code will look something like:

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

Finally you need to decide where to call your fetchfilms function. It will be inside a view controller that will use the data from the API. There are two obvious places you could make this API call. One is inside viewDidLoad and the other is inside viewDidAppear.

These are two different states for your app. My understanding is viewDidLoad is called the first time you load up that view in the foreground. viewDidAppear is called every time you come back to that view, for example when you press the back button to come back to the view.

If you expect your data to change in between the times that the user will navigate to and from that view, then you may want to put your API call in viewDidAppear. However I think for almost all apps, viewDidLoad is sufficient. Apple recommends viewDidAppear for all API calls, but that seems like overkill. I imagine it would make your app less performant as it's making many more API calls that it needs to.

Combining all the steps

First: write the function that calls the API. Above, this is fetchFilms. This will have a completion handler, which will return the data you are interested in. In my example, the completion handler returns an array of films.

Second: call this function in your view controller. You do this here because you want to update the view based on the data from the API. In my example, I am refreshing a table view once the API returns the data.

Third: decide where in your view controller you would like to call the function. In my example, I call it in viewDidLoad.

Fourth: decide what to do with the data from the API. In my example, I am refreshing a table view.

Inside NetworkManager.swift (this function can be defined in your view controller if you'd like, but I am using the MVVM pattern).

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() }

Inside 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 }

Bože, zvládli sme to! Ďakujeme, že ste zostali pri mne.