Ontwerppatronen delegatie

Het delegatiepatroon is een van de meest voorkomende patronen in iOS- en OS X-ontwikkeling. Het is een eenvoudig patroon dat sterk wordt gebruikt door de kaders van Apple en zelfs de eenvoudigste iOS-applicatie maakt gebruik van delegatie om zijn werk te doen. Laten we beginnen met te kijken naar de definitie van delegatie.

1. Wat is delegatie?

Definitie

De definitie van het delegatiepatroon is kort en eenvoudig. Dit is hoe Apple het patroon definieert.

Een gedelegeerde is een object dat optreedt namens of in coördinatie met een ander object wanneer dat object een gebeurtenis in een programma tegenkomt.

Laten we dat onderbreken. Het delegatiepatroon omvat twee objecten, de gedelegeerde en het delegerende object. De UITableView klasse definieert bijvoorbeeld a delegeren eigendom waaraan het evenementen delegeert. De eigenschap van de gedelegeerde moet voldoen aan de UITableViewDelegate protocol, dat is gedefinieerd in het headerbestand van de UITableView klasse.

In dit voorbeeld is de tabelweergaveinstantie het delegerende object. De gedelegeerde is meestal een view-controller, maar het kan elk object zijn dat voldoet aan de UITableViewDelegate protocol. Als u niet vertrouwd bent met protocollen, voldoet een klasse aan een protocol als het de vereiste methoden van het protocol implementeert. We zullen een beetje later naar een voorbeeld kijken.

Wanneer de gebruiker op een rij tikt in de tabelweergave, meldt de tabelweergave zijn gemachtigde door deze een bericht te sturen tableView (_: didSelectRowAtIndexPath :). Het eerste argument van deze methode is de tabelweergave die het bericht verzendt. Het tweede argument is het indexpad van de rij waarop de gebruiker tikte.

De tabelweergave geeft alleen zijn afgevaardigde van deze gebeurtenis op de hoogte. Het is aan de afgevaardigde om te beslissen wat er moet gebeuren wanneer een dergelijke gebeurtenis zich voordoet. Deze scheiding van verantwoordelijkheden, zoals u in een oogwenk zult leren, is een van de belangrijkste voordelen van het delegatiepatroon.

voordelen

herbruikbaarheid

Delegatie heeft verschillende voordelen, de eerste is herbruikbaarheid. Omdat de tabelweergave gebruikersinteractie delegeert aan zijn gemachtigde, hoeft de tabelweergave niet te weten wat er moet gebeuren wanneer een van de rijen wordt aangeboord.

Anders gezegd, de tabelweergave kan onwetend blijven van de implementatiedetails van hoe de gebruikersinteractie wordt afgehandeld door de toepassing. Deze verantwoordelijkheid wordt gedelegeerd aan de afgevaardigde, bijvoorbeeld een view-controller.

Het directe voordeel is dat de UITableView klasse kan worden gebruikt zoals in de meeste situaties. Meestal is subklasse niet nodig UITableView om het aan te passen aan de behoeften van uw toepassing.

Losse koppeling

Een ander belangrijk voordeel van delegeren is losse koppeling. In mijn artikel over singletons benadruk ik dat een strakke koppeling zoveel mogelijk moet worden vermeden. Delegatie is een ontwerppatroon dat actief losse koppeling bevordert. Wat bedoel ik daarmee?

De UITableView klas is gekoppeld aan zijn afgevaardigde om zijn werk te doen. Als er geen deelnemer aan de tabelweergave is gekoppeld, kan de tabelweergave de gebruikersinteractie niet verwerken of erop reageren. Dit betekent dat er een bepaald niveau van koppeling moet zijn. De tabelweergave en zijn deelnemer zijn echter los gekoppeld, omdat elke klasse die de. Implementeert UITableViewDelegate protocol kan fungeren als de gedelegeerde van de tabelweergave. Het resultaat is een flexibele en losjes gekoppelde objectgrafiek.

Scheiding van verantwoordelijkheden

Een minder bekend voordeel van delegatie is de scheiding van verantwoordelijkheden. Wanneer u een objectgrafiek maakt, is het belangrijk om te weten welke objecten verantwoordelijk zijn voor welke taken. Het delegatiepatroon maakt dit heel duidelijk.

In het geval van de UITableView klasse, de gedelegeerde van de tabelweergave is verantwoordelijk voor de omgang met gebruikersinteractie. De tabelweergave zelf is verantwoordelijk voor het detecteren van gebruikersinteractie. Dit is een duidelijke scheiding van verantwoordelijkheden. Zo'n scheiding maakt je baan als ontwikkelaar veel eenvoudiger en duidelijker.

2. Voorbeeld

Er zijn een paar smaken van het delegatiepatroon. Laten we doorgaan met het verder verkennen van de UITableViewDelegate protocol.

Delegatie

De UITableViewDelegate protocol moet worden geïmplementeerd door de gedelegeerde van de tabelweergave. De tabelweergave meldt zijn gemachtigde via de UITableViewDelegate protocol over gebruikersinteractie, maar het gebruikt ook de gedelegeerde voor de lay-out.

Een belangrijk verschil tussen Swift en Objective-C is de mogelijkheid om protocolmethoden als optioneel te markeren. In Objective-C zijn de methoden van een protocol standaard vereist. De methoden van de UITableViewDelegate protocol zijn echter optioneel. Met andere woorden, het is mogelijk dat een klasse zich conformeert aan de UITableViewDelegate protocol zonder de methoden van het protocol te implementeren.

In Swift is echter een klasse vereist die voldoet aan een bepaald protocol om elke methode die door het protocol wordt gedefinieerd, te implementeren. Dit is veel veiliger omdat het delegerende object niet hoeft te verifiëren of de gedelegeerde een protocolmethode implementeert. Dit subtiele, maar belangrijke verschil wordt verderop in deze tutorial geïllustreerd wanneer we het delegatiepatroon implementeren.

Databron

Er is een ander patroon dat nauw verwant is aan het delegatiepatroon, de gegevensbronpatroon. De UITableViewDataSource protocol is een voorbeeld van dit patroon. De UITableView klasse onthult een databron eigenschap die van het type is UITableViewDataSource (ID kaart in Objective-C). Dit betekent dat de gegevensbron van de tabelweergave elk object kan zijn dat de UITableViewDataSource protocol.

Het gegevensbronobject is verantwoordelijk voor het beheer van de gegevensbron van het object waarvan het de gegevensbron is. Het is belangrijk om op te merken dat het gegevensbronobject verantwoordelijk is voor het bijhouden van een verwijzing naar de items die worden blootgesteld aan het doelobject, zoals een tabelweergave of een verzamelweergave.

Een tabelweergave vraagt ​​bijvoorbeeld zijn gegevensbron om de gegevens die hij moet weergeven. Het tabelaanzicht is niet verantwoordelijk voor het bijhouden van de gegevensobjecten die het moet weergeven. Die rol wordt overgedragen aan het gegevensbronobject.

Het gegevensbronpatroon past mooi in de Model-View-Controller of MVC patroon. Waarom is dat? Een tabelweergave maakt bijvoorbeeld deel uit van de weergavelaag. Het hoeft niet te weten van de modellaag en moet dit niet doen en is niet verantwoordelijk voor het verwerken van de gegevens die afkomstig zijn van de modellaag. Dit impliceert dat de gegevensbron van een tabelweergave of een andere weergavecomponent die het gegevensbronpatroon implementeert, vaak een of andere controller is. Op iOS is dit meestal een UIViewController subklasse.

De methodehandtekeningen van een gegevensbronprotocol volgen hetzelfde patroon als die van een gedelegeerd protocol. Het object dat de berichten naar de gegevensbron stuurt, wordt doorgegeven als het eerste argument. Het gegevensbronprotocol moet alleen methoden definiëren die betrekking hebben op de gegevens die door het verzoekende object worden gebruikt.

In een tabelweergave wordt bijvoorbeeld de gegevensbron gevraagd voor het aantal secties en rijen dat moet worden weergegeven. Maar het meldt ook de gegevensbron dat een rij of sectie is ingevoegd of verwijderd. Dit laatste is belangrijk omdat de gegevensbron zichzelf moet bijwerken om de wijzigingen weer te geven die zichtbaar zijn in de tabelweergave. Als het tafelbeeld en de gegevensbron niet synchroon lopen, gebeuren er slechte dingen.

3. Implementatie

Doelstelling C

Het implementeren van het gedelegeerde patroon is vrij eenvoudig nu we begrijpen hoe het werkt. Bekijk het volgende Objective-C-voorbeeld.

#importeren  @protocol AddItemViewControllerDelegate; @interface AddItemViewController: UIViewController @property (weak, nonatomic) id delegeren; @end @protocol AddItemViewControllerDelegate  - (void) viewControllerDidCancel: (AddItemViewController *) viewController; - (void) viewController: (AddItemViewController *) viewController didAddItem: (NSString *) item; @optional - (BOOL) viewController: (AddItemViewController *) viewController validateItem: (NSString *) item; @einde

We verklaren een klas, AddItemViewController, welke zich uitstrekt UIViewController. De klasse verklaart een eigendom, delegeren, van type ID kaart. Merk op dat de eigenschap is gemarkeerd als zwak, wat betekent dat een AddItemViewController instantie houdt een zwakke verwijzing naar zijn afgevaardigde.

Merk ook op dat ik een forward protocolverklaring heb toegevoegd onder de importinstructie van het UIKit-framework. Dit is nodig om een ​​waarschuwing voor compileren te voorkomen. We zouden de protocolverklaring onder de importinstructie kunnen verplaatsen, maar ik geef er de voorkeur aan deze onder de klasse-interface te plaatsen. Dit is niets meer dan een persoonlijke voorkeur.

De protocolverklaring is ook vrij eenvoudig. De AddItemViewControllerDelegate protocol breidt het NSObject protocol. Dit is niet verplicht, maar het zal zeer nuttig blijken te zijn. We zullen ontdekken waarom dat een beetje later is.

De AddItemViewControllerDelegate protocol verklaart twee vereiste methoden en één optionele methode. Zoals ik eerder al zei, is het een goede gewoonte om het delegerende object als de eerste parameter van elke gedelegeerde methode door te geven om de gedelegeerde te laten weten welk object het bericht verzendt.

De vereiste methoden brengen de deelnemer op de hoogte van een gebeurtenis, een annulering of een toevoeging. De optionele methode vraagt ​​de gedelegeerde om feedback. Het verwacht dat de afgevaardigde terugkomt JA of NEE.

Dit is het eerste deel van de delegatiepuzzel. We hebben een klasse opgegeven die a verklaart delegeren eigendom en we hebben een gedelegeerd protocol gedeclareerd. Het tweede stukje van de puzzel roept de gedelegeerde methoden in de AddItemViewController klasse. Laten we kijken hoe dat werkt.

Bij de implementatie van de AddItemViewController klasse, implementeren we een annuleren: actie. Deze actie kan worden gekoppeld aan een knop in de gebruikersinterface. Als de gebruiker op de knop tikt, wordt de afgevaardigde op de hoogte gesteld van deze gebeurtenis en als gevolg hiervan kan de afgevaardigde de afspraak afwijzen AddItemViewController aanleg.

- (IBAction) cancel: (id) afzender if (self.delegate && [self.delegate respondsToSelector: @selector (viewControllerDidCancel :)]) [self.delegate viewControllerDidCancel: self]; 

Het wordt aanbevolen om te controleren of het delegatieobject dat niet is nul en dat het de gedelegeerde methode implementeert die we op het punt staan ​​aan te roepen, viewControllerDidCancel:. Dit is gemakkelijk dankzij de respondsToSelector: methode, gedeclareerd in de NSObject protocol. Dit is de reden waarom de AddItemViewControllerDelegate protocol breidt het NSObject protocol. Door het uitbreiden van de NSObject protocol krijgen we deze functionaliteit gratis.

U kunt de controle voor de eigenschap van de gedelegeerde weglaten nul, sinds respondsToSelector: zal terugkeren nul als de gedelegeerde eigenschap is nul. Ik voeg deze controle meestal toe omdat deze duidelijk laat zien wat we testen.

Het derde en laatste stukje van de puzzel is de implementatie van het gedelegeerde protocol door het gedelegeerde object. Het volgende codefragment geeft het maken van een weer AddItemViewController voorbeeld en de implementatie van een van de gedelegeerde methoden.

- (IBAction) addItem: (id) afzender // Initialize View Controller AddItemViewController * viewController = [[AddItemViewController alloc] init]; // Configureer View Controller [viewController setDelegate: self]; // Present View Controller [self presentViewController: viewController animated: YES completion: nil]; 
- (void) viewControllerDidCancel: (AddItemViewController *) viewController // Controller regel toevoegen beëindigen ...

Vergeet niet om te voldoen aan de klasse die optreedt als de afgevaardigde naar de AddItemViewControllerDelegate protocol zoals hieronder getoond. U kunt dit toevoegen in de klasse-interface of in een privéklasse-extensie.

#import "AddItemViewController.h" @interface ViewController ()  @einde

Snel

In Swift is het delegatiepatroon net zo eenvoudig uit te voeren en je zult merken dat Swift de delegatie iets eleganter maakt. Laten we het bovenstaande voorbeeld in Swift implementeren. Dit is wat de AddItemViewController klasse ziet eruit als in Swift.

import UIKit-protocol AddItemViewControllerDelegate: NSObjectProtocol func viewControllerDidCancel (viewController: AddItemViewController) func viewController (viewController: AddItemViewController, didAddItem: String) func viewController (viewController: AddItemViewController, validateItem: String) -> Bool class AddItemViewController: UIViewController var delegate: AddItemViewControllerDelegate? func cancel (verzender: AnyObject) delegate? .viewControllerDidCancel (self)

De protocolverklaring ziet er in Swift een beetje anders uit. Merk op dat de AddItemViewControllerDelegate protocol breidt het NSObjectProtocol in plaats van de NSObject protocol. In Swift kunnen klassen en protocollen niet dezelfde naam hebben, vandaar de NSObject protocol wordt in Swift anders genoemd.

De delegeren eigenschap is een variabele van het type AddItemViewControllerDelegate?. Let op het vraagteken aan het einde van de protocolnaam. De eigenschap gedelegeerde is een optioneel.

In de annuleren(_:) methode, roepen we de viewControllerDidCancel (_ :) gedelegeerde methode. Die ene regel laat zien hoe elegant Swift kan zijn. We pakken het veilig uit delegeren eigenschap voordat de gedelegeerde methode wordt aangeroepen. Het is niet nodig om te controleren of de gedelegeerde de viewControllerDidCancel (_ :) methode omdat elke methode van een protocol vereist is in Swift.

Laten we nu kijken naar de ViewController klasse, die het AddItemViewControllerDelegate protocol. De interface laat ons zien dat de ViewController class breidt het uit UIViewController klasse en keurt het goed AddItemViewControllerDelegate protocol.

import UIKit class ViewController: UIViewController, AddItemViewControllerDelegate func addItem (send: AnyObject) // Initialize View controller laat viewController = AddItemViewController () // Configureer View Controller viewController.delegate = self // Huidige weergave Controller presentViewController (viewController, geanimeerd: true , completion: nil) func viewControllerDidCancel (viewController: AddItemViewController) // Stuurregelitem weergeven negeren ... func viewController (viewController: AddItemViewController, didAddItem: String)  func viewController (viewController: AddItemViewController, validateItem: String) -> Bool 

In de Voeg item toe(_:) methode, initialiseren we een exemplaar van de AddItemViewController klasse, stel het in delegeren eigendom en presenteer het aan de gebruiker. Merk op dat we elke gedelegeerde methode van de AddItemViewControllerDelegate protocol. Als we dat niet doen, zal de compiler ons vertellen dat het ViewController klasse voldoet niet aan de AddItemViewControllerDelegate protocol. Probeer dit uit door een van de gedelegeerde methoden te becommentariëren.

Conclusie

Delegatie is een patroon dat u vaak tegenkomt bij het ontwikkelen van iOS- en OS X-applicaties. Cocoa is sterk afhankelijk van dit ontwerppatroon, dus het is belangrijk om ermee vertrouwd te raken.

Sinds de introductie van blokken, een paar jaar geleden, heeft Apple langzaam een ​​alternatieve op blokken gebaseerde API aangeboden aan een aantal delegatie-implementaties. Sommige ontwikkelaars hebben de leiding van Apple gevolgd door hun eigen op blokken gebaseerde alternatieven aan te bieden. De populaire AFNetworking-bibliotheek is bijvoorbeeld sterk afhankelijk van blokken in plaats van overdracht, wat resulteert in een elegante, intuïtieve API.