Swift - Delegate: Unterschied zwischen den Versionen

Aus Wikizone
Wechseln zu: Navigation, Suche
 
(22 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 12: Zeile 12:
 
Das Textfeld - '''der Delegierende gibt dafür dem Delegat bescheid wenn etwas passiert''' (z.b. der User die Eingabe beendet) und der Delegat kann dann reagieren (z.B. die Tastatur schließen)
 
Das Textfeld - '''der Delegierende gibt dafür dem Delegat bescheid wenn etwas passiert''' (z.b. der User die Eingabe beendet) und der Delegat kann dann reagieren (z.B. die Tastatur schließen)
  
Um dies zu realisieren werden Protocols verwendet.
+
=== Delegate und Protocol ===
 +
Aber wie kann man dieses Pattern nun realisieren, und wie kann man ein Objekt zum Delegat machen?
 +
 
 +
Um dies zu realisieren werden Protocols verwendet.  
 +
 
 +
# '''Definiere ein delegate Protocol''' mit den Funktionen, die die ausführenden Delegaten untstützen sollen.
 +
## Konvention: Definiere das '''Protocol im File der delegierenden Klasse''' (siehe Schritt 2)
 +
## ''protocol MyDelegateProtocol{ func myTask() }''
 +
# '''Definiere eine Klasse die delegieren soll'''
 +
## Sie hat eine '''Variable vom Typ des Protokolls'''. Über diese kann sie später den aktuellen Delegaten ansprechen.
 +
### '''MyDelegatorClass{ var delegate: MyDelegateProtocol? //... }'''
 +
## Füge '''Funktionen''' ein, die den späteren '''Delegat anweisen''' etwas aus dem Protocol auszuführen
 +
### ''func heyDelegateDoSomething(){delegate?.myTask}''
 +
# Mache beliebige '''Objekte zu Delegaten''' indem du
 +
## Das '''Protocol''' implementierst
 +
### ''class myClass: MyDelegateProtocol''
 +
## Die '''Funktionen aus dem Protocol''' implementierst
 +
### ''func myTask(){//...}''
 +
## Das '''Delegat aufnimmst''' (z.B. in der init Funktion)
 +
###    ''init(delegationHandler: MyDelegatorClass) { delegationHandler.delegate = self }''
 +
 
 +
 
 +
'''Beispiel für die Implementierung eines vollständigen Delegate Pattern'''
 +
 
 +
<syntaxhighlight lang="swift">
 +
 
 +
/**
 +
Protocol says: who wants to do ADL must know hot to perform CPR (Wiederbelebung)
 +
*/
 +
protocol AdvancedLifeSupport{
 +
    func performCPR()
 +
}
 +
 
 +
/**
 +
If a emergency call comes in the EmergencyCallHandler is responsible to assess the situation an inform the delegate
 +
*/
 +
class EmergencyCallHandler {
 +
   
 +
    var delegate: AdvancedLifeSupport? // my delegate should have a ADL cdertificate
 +
   
 +
    func assessSituation() {
 +
        print("Can you tell me what happended?")
 +
    }
 +
 
 +
    func triggerMedicalEmergency() {
 +
        delegate?.performCPR() // call the delegate (? if available) and tell him to perform CPR
 +
    }
 +
}
 +
 
 +
/**
 +
When the paramedic goes on shift and picks up the emergency pager (meaning he sets the delegate to himself)
 +
*/
 +
struct Paramedic: AdvancedLifeSupport{
 +
   
 +
    init(handler: EmergencyCallHandler) {
 +
        handler.delegate = self
 +
    }
 +
   
 +
    func performCPR() {
 +
        print("The paramedic does chest compressions 30 per second.")
 +
    }
 +
}
 +
 
 +
class Doctor: AdvancedLifeSupport{
 +
   
 +
    init(handler: EmergencyCallHandler){
 +
        handler.delegate = self
 +
    }
 +
    func performCPR() {
 +
        print("The doctor does chest compressions 30 per second.")
 +
    }
 +
    func useStethoscope(){
 +
        print("Doctor listening for heart sounds.")
 +
    }
 +
}
 +
 
 +
class Surgeon: Doctor{
 +
    override func performCPR() {
 +
        super.performCPR()
 +
        print("Singing Staying alive by the BeeGees")
 +
    }
 +
    func useElectricDrill(){
 +
        print("Whirr...")
 +
    }
 +
}
 +
 
 +
 
 +
let emma = EmergencyCallHandler()
 +
let pete = Paramedic(handler: emma)
 +
let angela = Surgeon(handler: emma)
 +
/**
 +
note that pete will not get informed because the dalegate property (the pager) has changed to angela
 +
if you uncomment angela pete will do CPR
 +
*/
 +
 
 +
emma.assessSituation()
 +
emma.triggerMedicalEmergency() // CALL THE DELEGATE
 +
</syntaxhighlight>
 +
 
 +
Im Beispiel mit den Textfields gibt es also ein Protocol, welches vorgibt welche Funktionalität ein Delegat erfüllen muss.
 +
Sobald unsere Klasse das Delegat erhält müssen wir die zum Protokoll zugehörigen Funktionen implementieren.
 +
 
 +
<syntaxhighlight lang="swift">
 +
 
 +
class myClass{
 +
  /...
 +
  let textField = UITextField
 +
  textField.delegate = self // now textFieldDidBeginEditing() is delegated to self
 +
  //...
 +
</syntaxhighlight>
 +
 
 +
== Delegate Step by Step ==
 +
=== Steps to  be done in the SENDER side ===
 +
 
 +
step1:
 +
 
 +
Erstelle ein Protokoll mit den Funktionen die du delegieren möchtest.
 +
 
 +
syntax:
 +
<syntaxhighlight lang="swift">
 +
protocol ProtocolName{
 +
    func functionName(parameters:Type)
 +
}
 +
</syntaxhighlight>
 +
eg:
 +
<syntaxhighlight lang="swift">
 +
protocol WeatherManagerDelegate{ 
 +
    func didUpdateWeather(weather:WeatherModel)
 +
     }
 +
</syntaxhighlight>
 +
 
 +
Step2:
 +
 
 +
Erstelle eine Delegat Variable mit dem Optional Type des Protocol
 +
 
 +
syntax:
 +
<syntaxhighlight lang="swift">
 +
var delegate:ProtocolName?
 +
</syntaxhighlight>
 +
eg:
 +
<syntaxhighlight lang="swift">
 +
    var delegate:WeatherManagerDelegate?
 +
</syntaxhighlight>
 +
 
 +
step3:
 +
 
 +
Über die Delegate Variable rufst du die Protocol-Funktionen auf und übergibst die Daten an den späteren Delegate Empfänger.
 +
 
 +
syntax:
 +
<syntaxhighlight lang="swift">
 +
delegate.MethodNameInProtocol(parameterName:args)
 +
</syntaxhighlight>
 +
 
 +
eg:
 +
<syntaxhighlight lang="swift">
 +
delegate.didUpdateWeather(weather:weather)
 +
</syntaxhighlight>
 +
 
 +
=== Steps to be done in RECIEVER side ===
 +
 
 +
step1:
 +
 
 +
Implementiere das Protocol im Empfänger ViewController.
 +
 
 +
syntax:
 +
<syntaxhighlight lang="swift">
 +
class ViewControllerName : protocolName
 +
</syntaxhighlight>
 +
 
 +
eg:
 +
<syntaxhighlight lang="swift">
 +
class  WeatherViewController:WeatherManagerDelegate
 +
</syntaxhighlight>
 +
 
 +
step 2:
 +
 
 +
Initialisiere eine Instanz der Senderklasse...
 +
 
 +
syntax:
 +
<syntaxhighlight lang="swift">
 +
var RecieverObjectName = SenderClassName()
 +
</syntaxhighlight>
 +
 
 +
eg:
 +
<syntaxhighlight lang="swift">
 +
var  weatherManager= WeatherManager()
 +
</syntaxhighlight>
 +
 
 +
step3:
 +
 
 +
... und erlaube Delegate Nachrichten zun empfangen, indem du das Empfängerobjekt (self) in der delegate Variablen des Senderobjekts speicherst.
 +
 
 +
syntax:
 +
<syntaxhighlight lang="swift">
 +
RecieverObjectName.delegate = self
 +
</syntaxhighlight>
 +
 
 +
eg
 +
<syntaxhighlight lang="swift">
 +
weatherManager.delegate = self
 +
</syntaxhighlight>
 +
 
 +
step 4:
 +
 
 +
Jetzt kannst du die Methoden des Protocol implementieren
 +
 
 +
syntax:
 +
<syntaxhighlight lang="swift">
 +
func functionName(parameterName: DataType) {
 +
    print(weather)
 +
}
 +
</syntaxhighlight>
 +
eg :
 +
<syntaxhighlight lang="swift">
 +
    func didUpdateWeather(weather: WeatherModel) {
 +
            print(weather)
 +
        }
 +
</syntaxhighlight>
 +
 
 +
== Konventionen für Delegate Patterns ==
 +
Apple nutzt bestimmte Naming Konventionen für die eigenen Delegates. Diese sollte man übernehmen, wenn man Stilkonform bleiben möchte.
 +
 
 +
# Die Identity des Objekts, das die Delegate Methode aufruft, wird als erster Parameter übergeben.
 +
# Der erste Parametername wird unterdrückt (_) muss also nicht mit angegeben werden
 +
 
 +
=== Beispiel ===
 +
WeatherManager.swift
 +
<syntaxhighlight lang="swift">
 +
protocol WeatherManagerDelegate{
 +
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel)
 +
}
 +
 
 +
struct WeatherManager{
 +
    // ...
 +
    var delegate: WeatherManagerDelegate?
 +
    // ...
 +
    self.delegate?.didUpdateWeather(self, weather: weather) // die eigene Instanz wird mit übergeben
 +
}
 +
</syntaxhighlight>
 +
 
 +
WeatherViewController.swift
 +
<syntaxhighlight lang="swift">
 +
class WeatherViewController: UIViewController, UITextFieldDelegate, WeatherManagerDelegate {
 +
    // ...
 +
    var weatherManager = WeatherManager()
 +
    // ...
 +
    func didUpdateWeather(_ weatherManager:WeatherManager, weather: WeatherModel){ // Instanz als weatherManager verfügbar
 +
        print( weather.temperature)
 +
    }
 +
}
 +
</syntaxhighlight>
 +
== Error Handling & Delegates ==
 +
Delegates kann man auch gut nutzen um im Fehlerfall eine Delegate Funktion beim Receiver auszulösen. So kann man gut auf Fehler reagieren die in verschiedenen Objekten vorkommen.
 +
 
 +
Zusätzlich verwendet man oft optional bindings um nil Werte abzufangen.
 +
 
 +
=== Beispiel ===
 +
<syntaxhighlight lang="swift">
 +
protocol WeatherManagerDelegate{
 +
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel)
 +
    func didFailWithError(error: Error)
 +
}
 +
 
 +
class WeatherViewController: UIViewController, UITextFieldDelegate, WeatherManagerDelegate {
 +
    // ...
 +
    var weatherManager = WeatherManager()
 +
    // ...
 +
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 { // OPTIONAL BINDING
 +
                    delegate?.didFailWithError(error: error!) // SEND ERROR TO DELEGATE
 +
                    return
 +
                }
 +
                if let safeData = data { // OPTIONAL BINDING
 +
                    if let weather = self.parseJSON(safeData){
 +
                        self.delegate?.didUpdateWeather(self, weather: weather)
 +
                    }
 +
                }
 +
            }
 +
            // 4. start the task
 +
            task.resume()
 +
        }
 +
    }
 +
 
 +
    func parseJSON(_ weatherData: Data)->WeatherModel? {
 +
        let decoder = JSONDecoder()
 +
        // decode needs a type not an object as parameter. We refer to the type with .self i.e. WeatherData.self
 +
        do {
 +
            let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
 +
            let id = decodedData.weather[0].id
 +
            let temp = decodedData.main.temp
 +
            let name = decodedData.name
 +
            let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
 +
            return weather
 +
           
 +
        } catch {
 +
            //print("Error parsing JSON: \(error)")
 +
            delegate?.didFailWithError(error: error) // SEND ERROR TO DELEGATE
 +
            return nil
 +
        }
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
Im View Controller
 +
<syntaxhighlight lang="swift">
 +
    func didFailWithError(error: Error) {
 +
        print(error)
 +
    }
 +
</syntaxhighlight>
 +
 
 +
== Swift - DispatchQueue ==
 +
Ist eigentlich ein eigenes Thema aber in diesem Zusammenhang wichtig. Wenn der View über ein Delegate ausgelöst verändert wird. Findet das oft über einen Completion Handler statt. Dann kann nicht garantiert werden, dass der View im Main Thread sein Update bekommt. Das ist aber notwendig. Deshalb nutzt man einen DispatchQueue:
 +
 
 +
<syntaxhighlight lang="swift">
 +
        DispatchQueue.main.async {
 +
            self.bitcoinLabel.text = String(format: "%.2f", lastPrice)
 +
        }
 +
</syntaxhighlight>
 +
Der DispatchQueue ist eine Warteschlange die synchron (First in First out) aber auch asynchron Aufgaben abarbeiten kann. Im Beispiel wird die Aktualisierung des Textlabels in die Warteschlange (den Queue) des Main Threads gestellt.
 +
 
 +
== Komplettes Beispiel mit Delegates und Networking ==
 +
<syntaxhighlight lang="swift">
 +
//
 +
//  ViewController.swift
 +
//  ByteCoin
 +
//
 +
//  Created by Angela Yu on 11/09/2019.
 +
//  Copyright © 2019 The App Brewery. All rights reserved.
 +
//
 +
 
 +
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)
 +
    }
 +
   
 +
}
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="swift">
 +
//
 +
//  CoinManager.swift
 +
//  ByteCoin
 +
//
 +
//  Created by Angela Yu on 11/09/2019.
 +
//  Copyright © 2019 The App Brewery. All rights reserved.
 +
//
 +
 
 +
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){
 +
        // create url
 +
        fetchCoinData(currency: currency)
 +
        // fetch
 +
        // set via delegate
 +
       
 +
    }
 +
    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
 +
        }
 +
    }
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="swift">
 +
//
 +
//  CoinData.swift
 +
//  ByteCoin
 +
//
 +
//  Created by Stephan Schlegel on 21.01.23.
 +
//  Copyright © 2023 The App Brewery. All rights reserved.
 +
//
 +
 
 +
import Foundation
 +
 
 +
struct CoinData: Codable {
 +
    let time: String
 +
    let asset_id_base: String
 +
    let asset_id_quote: String
 +
    let rate: Double
 +
}
 +
 
 +
</syntaxhighlight>
 +
<syntaxhighlight lang="swift">
 +
</syntaxhighlight>

Aktuelle Version vom 24. Januar 2023, 18:09 Uhr

Links[Bearbeiten]

Swift (Programmiersprache)
Swift - Protocols

Das Delegate Design Pattern[Bearbeiten]

Das Delegate Design Pattern ist eine Möglichkeit, wie man bestimmte Aufgaben oder Verhaltensweisen von einem Objekt an ein anderes Objekt delegieren kann. Stell dir vor, du bist der Lehrer einer Schulklasse und du möchtest, dass ein Schüler als Klassenvertreter fungiert und bestimmte Aufgaben für die Klasse übernimmt. Der Schüler wäre in diesem Fall der Delegat und du wärst der Delegierende. Du gibst bestimmte Aufgaben an den Schüler ab, anstatt sie selbst zu erledigen. Auf diese Weise kannst du die Verantwortung für bestimmte Aufgaben teilen und dich auf andere wichtige Dinge konzentrieren.

Das Delegate Design Pattern wird auch in der Softwareentwicklung verwendet, um die Kopplung zwischen verschiedenen Objekten zu verringern und den Code wieder verwenden zu können. Stell dir vor, du hast ein Programm, das einen Timer hat. Du möchtest, dass das Programm etwas tut, wenn der Timer abgelaufen ist. Statt den Code für das, was passieren soll, direkt in den Timer zu schreiben, könntest du das Delegate Design Pattern verwenden. Du könntest ein anderes Objekt als Delegat festlegen und es verantwortlich machen, was passieren soll, wenn der Timer abgelaufen ist. Auf diese Weise wäre der Timer unabhängig von dem, was passiert, wenn er abgelaufen ist, und der Code für das, was passieren soll, könnte an anderer Stelle im Programm geschrieben werden.

In Swift werden Delegates an vielen Stellen verwendet. Ein Beispiel sind Texteingabefelder (TextViews). Bevor ein Programm geschrieben ist weiß man nicht wie es auf Texteingaben des Benutzer reagieren soll. Deshalb können TextViews diese Aufgabe delegieren. Der Programmierer kann dann später selbst entscheiden wie sein Programm auf aktuelle Texteingaben reagiert.

Das Textfeld - der Delegierende gibt dafür dem Delegat bescheid wenn etwas passiert (z.b. der User die Eingabe beendet) und der Delegat kann dann reagieren (z.B. die Tastatur schließen)

Delegate und Protocol[Bearbeiten]

Aber wie kann man dieses Pattern nun realisieren, und wie kann man ein Objekt zum Delegat machen?

Um dies zu realisieren werden Protocols verwendet.

  1. Definiere ein delegate Protocol mit den Funktionen, die die ausführenden Delegaten untstützen sollen.
    1. Konvention: Definiere das Protocol im File der delegierenden Klasse (siehe Schritt 2)
    2. protocol MyDelegateProtocol{ func myTask() }
  2. Definiere eine Klasse die delegieren soll
    1. Sie hat eine Variable vom Typ des Protokolls. Über diese kann sie später den aktuellen Delegaten ansprechen.
      1. MyDelegatorClass{ var delegate: MyDelegateProtocol? //... }
    2. Füge Funktionen ein, die den späteren Delegat anweisen etwas aus dem Protocol auszuführen
      1. func heyDelegateDoSomething(){delegate?.myTask}
  3. Mache beliebige Objekte zu Delegaten indem du
    1. Das Protocol implementierst
      1. class myClass: MyDelegateProtocol
    2. Die Funktionen aus dem Protocol implementierst
      1. func myTask(){//...}
    3. Das Delegat aufnimmst (z.B. in der init Funktion)
      1. init(delegationHandler: MyDelegatorClass) { delegationHandler.delegate = self }


Beispiel für die Implementierung eines vollständigen Delegate Pattern

/**
 Protocol says: who wants to do ADL must know hot to perform CPR (Wiederbelebung)
 */
protocol AdvancedLifeSupport{
    func performCPR()
}

/**
 If a emergency call comes in the EmergencyCallHandler is responsible to assess the situation an inform the delegate
 */
class EmergencyCallHandler {
    
    var delegate: AdvancedLifeSupport? // my delegate should have a ADL cdertificate
    
    func assessSituation() {
        print("Can you tell me what happended?")
    }

    func triggerMedicalEmergency() {
        delegate?.performCPR() // call the delegate (? if available) and tell him to perform CPR 
    }
}

/**
 When the paramedic goes on shift and picks up the emergency pager (meaning he sets the delegate to himself)
 */
struct Paramedic: AdvancedLifeSupport{
    
    init(handler: EmergencyCallHandler) {
        handler.delegate = self
    }
    
    func performCPR() {
        print("The paramedic does chest compressions 30 per second.")
    }
}

class Doctor: AdvancedLifeSupport{
    
    init(handler: EmergencyCallHandler){
        handler.delegate = self
    }
    func performCPR() {
        print("The doctor does chest compressions 30 per second.")
    }
    func useStethoscope(){
        print("Doctor listening for heart sounds.")
    }
}

class Surgeon: Doctor{
    override func performCPR() {
        super.performCPR()
        print("Singing Staying alive by the BeeGees")
    }
    func useElectricDrill(){
        print("Whirr...")
    }
}


let emma = EmergencyCallHandler()
let pete = Paramedic(handler: emma)
let angela = Surgeon(handler: emma)
/**
 note that pete will not get informed because the dalegate property (the pager) has changed to angela
 if you uncomment angela pete will do CPR
 */

emma.assessSituation()
emma.triggerMedicalEmergency() // CALL THE DELEGATE

Im Beispiel mit den Textfields gibt es also ein Protocol, welches vorgibt welche Funktionalität ein Delegat erfüllen muss. Sobald unsere Klasse das Delegat erhält müssen wir die zum Protokoll zugehörigen Funktionen implementieren.

class myClass{
  /...
  let textField = UITextField
  textField.delegate = self // now textFieldDidBeginEditing() is delegated to self
  //...

Delegate Step by Step[Bearbeiten]

Steps to be done in the SENDER side[Bearbeiten]

step1:

Erstelle ein Protokoll mit den Funktionen die du delegieren möchtest.

syntax:

protocol ProtocolName{
    func functionName(parameters:Type)
}

eg:

protocol WeatherManagerDelegate{   
    func didUpdateWeather(weather:WeatherModel)
     }

Step2:

Erstelle eine Delegat Variable mit dem Optional Type des Protocol

syntax:

var delegate:ProtocolName?

eg:

    var delegate:WeatherManagerDelegate?

step3:

Über die Delegate Variable rufst du die Protocol-Funktionen auf und übergibst die Daten an den späteren Delegate Empfänger.

syntax:

delegate.MethodNameInProtocol(parameterName:args)

eg:

delegate.didUpdateWeather(weather:weather)

Steps to be done in RECIEVER side[Bearbeiten]

step1:

Implementiere das Protocol im Empfänger ViewController.

syntax:

class ViewControllerName : protocolName

eg:

class  WeatherViewController:WeatherManagerDelegate

step 2:

Initialisiere eine Instanz der Senderklasse...

syntax:

var RecieverObjectName = SenderClassName()

eg:

var  weatherManager= WeatherManager()

step3:

... und erlaube Delegate Nachrichten zun empfangen, indem du das Empfängerobjekt (self) in der delegate Variablen des Senderobjekts speicherst.

syntax:

RecieverObjectName.delegate = self

eg

weatherManager.delegate = self

step 4:

Jetzt kannst du die Methoden des Protocol implementieren

syntax:

func functionName(parameterName: DataType) {
    print(weather)
}

eg :

     func didUpdateWeather(weather: WeatherModel) {
            print(weather)
         }

Konventionen für Delegate Patterns[Bearbeiten]

Apple nutzt bestimmte Naming Konventionen für die eigenen Delegates. Diese sollte man übernehmen, wenn man Stilkonform bleiben möchte.

  1. Die Identity des Objekts, das die Delegate Methode aufruft, wird als erster Parameter übergeben.
  2. Der erste Parametername wird unterdrückt (_) muss also nicht mit angegeben werden

Beispiel[Bearbeiten]

WeatherManager.swift

protocol WeatherManagerDelegate{
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel)
}

struct WeatherManager{
    // ...
    var delegate: WeatherManagerDelegate?
    // ...
    self.delegate?.didUpdateWeather(self, weather: weather) // die eigene Instanz wird mit übergeben
}

WeatherViewController.swift

class WeatherViewController: UIViewController, UITextFieldDelegate, WeatherManagerDelegate {
    // ...
    var weatherManager = WeatherManager()
    // ...
    func didUpdateWeather(_ weatherManager:WeatherManager, weather: WeatherModel){ // Instanz als weatherManager verfügbar
        print( weather.temperature)
    }
}

Error Handling & Delegates[Bearbeiten]

Delegates kann man auch gut nutzen um im Fehlerfall eine Delegate Funktion beim Receiver auszulösen. So kann man gut auf Fehler reagieren die in verschiedenen Objekten vorkommen.

Zusätzlich verwendet man oft optional bindings um nil Werte abzufangen.

Beispiel[Bearbeiten]

protocol WeatherManagerDelegate{
    func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel)
    func didFailWithError(error: Error)
}

class WeatherViewController: UIViewController, UITextFieldDelegate, WeatherManagerDelegate {
    // ...
    var weatherManager = WeatherManager()
    // ...
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 { // OPTIONAL BINDING
                    delegate?.didFailWithError(error: error!) // SEND ERROR TO DELEGATE
                    return
                }
                if let safeData = data { // OPTIONAL BINDING
                    if let weather = self.parseJSON(safeData){
                        self.delegate?.didUpdateWeather(self, weather: weather)
                    }
                }
            }
            // 4. start the task
            task.resume()
        }
    }

    func parseJSON(_ weatherData: Data)->WeatherModel? {
        let decoder = JSONDecoder()
        // decode needs a type not an object as parameter. We refer to the type with .self i.e. WeatherData.self
        do {
            let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
            let id = decodedData.weather[0].id
            let temp = decodedData.main.temp
            let name = decodedData.name
            let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
            return weather
            
        } catch {
            //print("Error parsing JSON: \(error)")
            delegate?.didFailWithError(error: error) // SEND ERROR TO DELEGATE
            return nil
        }
    }
}

Im View Controller

    func didFailWithError(error: Error) {
        print(error)
    }

Swift - DispatchQueue[Bearbeiten]

Ist eigentlich ein eigenes Thema aber in diesem Zusammenhang wichtig. Wenn der View über ein Delegate ausgelöst verändert wird. Findet das oft über einen Completion Handler statt. Dann kann nicht garantiert werden, dass der View im Main Thread sein Update bekommt. Das ist aber notwendig. Deshalb nutzt man einen DispatchQueue:

        DispatchQueue.main.async {
            self.bitcoinLabel.text = String(format: "%.2f", lastPrice)
        }

Der DispatchQueue ist eine Warteschlange die synchron (First in First out) aber auch asynchron Aufgaben abarbeiten kann. Im Beispiel wird die Aktualisierung des Textlabels in die Warteschlange (den Queue) des Main Threads gestellt.

Komplettes Beispiel mit Delegates und Networking[Bearbeiten]

//
//  ViewController.swift
//  ByteCoin
//
//  Created by Angela Yu on 11/09/2019.
//  Copyright © 2019 The App Brewery. All rights reserved.
//

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
//  ByteCoin
//
//  Created by Angela Yu on 11/09/2019.
//  Copyright © 2019 The App Brewery. All rights reserved.
//

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){
        // create url
        fetchCoinData(currency: currency)
        // fetch
        // set via delegate
        
    }
    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
//  ByteCoin
//
//  Created by Stephan Schlegel on 21.01.23.
//  Copyright © 2023 The App Brewery. All rights reserved.
//

import Foundation

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