Swift - URLSession

Aus Wikizone
Wechseln zu: Navigation, Suche

Links[Bearbeiten]

Swift (Programmiersprache)

Einführung[Bearbeiten]

Mit URLSession kannst du Anfragen an einen Webserver generieren und Antworten verarbeiten.

Schritte[Bearbeiten]

  • Create a URL
  • Create a URLSession (kind of Browser Object)
  • Give URLSession a Task (give the browser a url to fetch)
  • Start the task

Beispiel[Bearbeiten]

struct WeatherManager{
    
    let weatherURL = "https://api.openweathermap.org/data/2.5/weather?units=metric&appid=63981dbcfa548021dd77394d24f34674"
    
    func fetchWeather(cityName: String){
        let urlString = "\(weatherURL)&q=\(cityName)"
        performRequest(urlString: urlString)
    }
    
    func performRequest(urlString: String){
        // 1. create URL
        if let url = URL(string: urlString) {
            // 2. create URLSession
            let session = URLSession(configuration: .default)
            // 3. give session a task
            let task = session.dataTask(with: url, completionHandler: handle(data:response:error:))
            // 4. start the task
            task.resume()
        }
    }
    
    func handle(data: Data?, response: URLResponse?, error: Error?){
        if error != nil {
            print(error!)
            return
        }
        if let safeData = data {
            let dataString = String(data: safeData, encoding: .utf8)
            print(dataString!)
        }
    }
}

Oder besser mit Closure statt separater Funktion:

struct WeatherManager{
    
    let weatherURL = "https://api.openweathermap.org/data/2.5/weather?units=metric&appid=63981dbcfa548021dd77394d24f34674"
    
    func fetchWeather(cityName: String){
        let urlString = "\(weatherURL)&q=\(cityName)"
        performRequest(urlString: urlString)
    }
    
    func performRequest(urlString: String){
        // 1. create URL
        if let url = URL(string: urlString) {
            // 2. create URLSession
            let session = URLSession(configuration: .default)
            // 3. give session a task
            let task = session.dataTask(with: url) { data, response, error in
                if error != nil {
                    print(error!)
                    return
                }
                if let safeData = data {
                    let dataString = String(data: safeData, encoding: .utf8)
                    print(dataString!)
                }
            }
            // 4. start the task
            task.resume()
        }
    }
    
    func handle(data: Data?, response: URLResponse?, error: Error?){
        if error != nil {
            print(error!)
            return
        }
        if let safeData = data {
            let dataString = String(data: safeData, encoding: .utf8)
            print(dataString!)
        }
    }
}

Best Practice[Bearbeiten]

Da Netzwerkzugriffe eine gewisse Zeit benötigen, nutzt man in der Praxis oft das Delegate Pattern. Wenn der Request abgeschlossen ist sendet man die Daten via Delegate an den View, der dann die Ausgabe im UI macht.

Vorsicht wenn das Senden aus einem Completion Handler kommt (wie im Beispiel unten), dann musst du im ViewController dafür sorgen, dass das UI im Main Thread upgedatet wird. Sonst gibt es einen Fehler.

Kommentiertes Beispiel:

//  ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var bitcoinLabel: UILabel!
    @IBOutlet weak var currencyLabel: UILabel!
    @IBOutlet weak var currencyPicker: UIPickerView!
    
    var coinManager = CoinManager()
   
    override func viewDidLoad() {
        super.viewDidLoad()
        // set the ViewController as the datasource for the picker
        currencyPicker.dataSource = self
        // set the ViewController as the delegate for the picker
        currencyPicker.delegate = self
        // set the ViewController as the delegate for the CoinManager
        coinManager.delegate = self
    }
}

//MARK: - UIPickerViewDataSource protocol
extension ViewController:UIPickerViewDataSource{

    // return number of columns of the picker
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    // return number of rows
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return coinManager.currencyArray.count
    }
}

//MARK: - UIPickerViewDelegate
extension ViewController: UIPickerViewDelegate{
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        // executed for every row
        return coinManager.currencyArray[row]
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        let selectedCurrency = coinManager.currencyArray[row]
        coinManager.getCoinPrice(for: selectedCurrency)
        self.currencyLabel.text = selectedCurrency
    }
}

//MARK: - CoinManagerDelegate
extension ViewController: CoinManagerDelegate{
    func didUpdatePrice(_ coinManager: CoinManager, lastPrice: Double) {
        // use DispatchQueue as this is not called via main thread
        DispatchQueue.main.async {
            self.bitcoinLabel.text = String(format: "%.2f", lastPrice)
        }
    }
    
    func didFailWithError(error: Error) {
        print(error)
    }
}
//  CoinManager.swift

import Foundation

protocol CoinManagerDelegate{
    func didUpdatePrice( _ coinManager: CoinManager, lastPrice: Double)
    func didFailWithError( error: Error)
}

struct CoinManager {
    var delegate: CoinManagerDelegate?
    let baseURL = "https://rest.coinapi.io/v1/exchangerate/BTC"
    let apiKey = "E7E77171-F196-4E85-BC01-5D260ECB96E4"
    
    let currencyArray = ["AUD", "BRL","CAD","CNY","EUR","GBP","HKD","IDR","ILS","INR","JPY","MXN","NOK","NZD","PLN","RON","RUB","SEK","SGD","USD","ZAR"]

    func getCoinPrice (for currency: String){
        fetchCoinData(currency: currency)
    }

    // we could override fetch to create different url strings depending on the input
    func fetchCoinData(currency: String){
        let urlString = String( "\(baseURL)/\(currency)?apikey=\(apiKey)" )
        performRequest(with: urlString)
    }
    
    func performRequest(with urlString: String){
        // 1. create URL
        let allowedCharacters = CharacterSet.urlQueryAllowed
        let encodedString = urlString.addingPercentEncoding(withAllowedCharacters: allowedCharacters) ?? ""
        if let url = URL(string: encodedString) {
            // 2. create URLSession
            let session = URLSession(configuration: .default)
            // 3. give session a task
            let task = session.dataTask(with: url) { data, response, error in
                if error != nil {
                    delegate?.didFailWithError(error: error!)
                    return
                }
                if let safeData = data {
                    if let lastPrice = parseJSON( safeData ){
                        delegate?.didUpdatePrice(self, lastPrice: lastPrice)
                    }
                }
            }
            // 4. start the task
            task.resume()
        }
    }
    
    func parseJSON( _ data: Data) -> Double?{
        let decoder = JSONDecoder()
        do{
            let decodedData = try decoder.decode(CoinData.self, from: data)
            return decodedData.rate
        }catch{
            return nil
        }   
    }
}
//  CoinData.swift
//  This is used by the JSONEncoder

import Foundation

struct CoinData: Codable {
    let time: String
    let asset_id_base: String
    let asset_id_quote: String
    let rate: Double
}