Kraften I Delegate design pattern
29 Mai 2019
Forrige uke FØR WWDC og alle så glade for nye funksjoner som vi vil ha bare om noen dager. MEN la oss holde innlegg relatert TIL WWDC for neste uke. Denne uken skal vi snakke om min favoritt design mønster Delegat. Delegat er det mest enkle og kraftige mønsteret.
i software engineering er delegasjonsmønsteret et objektorientert designmønster som gjør det mulig for objektkomposisjon å oppnå samme kodegjenbruk som en arv. I delegering håndterer et objekt en forespørsel ved å delegere til et annet objekt (representanten). En representant er et hjelpeobjekt, men med den opprinnelige konteksten.
Protokoller
vi bruker Delegatmønster hver dag, og iOS SDK bruker Det mange steder. For Eksempel delegerer uitableviewdatasource til uitableviewdatasource som fyller tabellen med celler, det delegerer også cellevalg og andre handlinger Til UITableViewDelegate. Et annet utmerket eksempel på delegatmønstre er FlowController eller Koordinatorer. ViewControllers delegerer navigasjonslogikk Til Koordinator. Jeg har skilt innlegg om å trekke ut navigasjonslogikk i FlowControllers.
La oss dykke inn i kodeeksempler. Anta at du jobber med et spill. Du hentet spilllogikk inn i separert klassespill, og du vil delegere spilltilstandsendringer Til UIViewController som gjør dette spillet.
protocol GameDelegate: AnyObject { func stateChanged(from oldState: Game.State, to newState: Game.State)}class Game { private var state: State = .notStarted { didSet { delegate?.stateChanged(from: oldValue, to: state) } } weak var delegate: GameDelegate? private(set) var value: Int = 0 func start() { state = .started } func generateNextValue() { value = Int.random(in: 0..<1000) state = generateState(using: value) }}extension Game { enum State { case notStarted case started case right case win case lost }}
her er kildekoden til et enkelt spill som genererer tilfeldige verdier. Spillmotoren genererer tilstand basert på tilfeldige verdier. Hver stat endring ringe delegat til å passere gamle og nye stater. Vi definerer vår delegatprotokoll utvidet Fra AnyObject, det betyr at den eneste klasseforekomsten kan godta den. Jeg bruker også svakt søkeord for å definere variabel holdingsrepresentant. Det trengte a bryte beholde syklusen mellom delegat og spillklasse. La Oss ta En titt på GameViewController nå.
class GameViewController: UIViewController { private let game: Game init(game: Game) { self.game = game super.init(nibName: nil, bundle: nil) } @IBAction func play() { game.start() } @IBAction func next() { game.generateNextValue() } override func viewDidLoad() { super.viewDidLoad() game.delegate = self }}extension GameViewController: GameDelegate { func render(_ state: Game.State) { switch state { case .lost: renderLost() case .right: renderRight() case .win: renderWin() case .started: renderStart() case .notStarted: renderNotStarted() } } func stateChanged(from oldState: Game.State, to newState: Game.State) { render(newState) }}
Her har Vi En GameViewController-klasse som mater spill med brukerhandlinger og gjengir tilstandsendringer. GameViewController overholder GameDelegate og implementerer all nødvendig gjengivelse i forlengelse. Som et resultat, har vi en composable kodebase ved Hjelp Av Delegat design mønster.
Nedleggelser
noen ganger når du bare har en metode i representanten, kan du erstatte den med lukning. Ideen er den samme, men nå kaller du lukkingen og passerer staten i stedet for å ringe metoden etter protokoll. La oss ta en titt på eksemplet med nedleggelse.
class Game { typealias StateHandler = (State) -> Void var handler: StateHandler? private var state: State = .notStarted { didSet { handler?(state) } }}class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() game.handler = { state in self?.render(state) } }}
Som du kan se, passerer vi nedleggelsen til game class-forekomsten som håndterer tilstandsendringer. Vi bruker svak til å bryte beholdningssyklusen under lukningens kontekstfangst. Et annet alternativ her kan være en bruk av Det faktum at Enhver Swift-funksjon er en nedleggelse. Så i stedet for å skape separert lukning, kan vi passere funksjonsnavnet. Men vær forsiktig denne metoden skaper beholde sirkel. Her er et eksempel på hvordan vi kan gjøre det.
class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() game.handler = render }}extension GameViewController { func render(_ state: Game.State) { switch state { case .lost: renderLost() case .right: renderRight() case .win: renderWin() case .started: renderStart() case .notStarted: renderNotStarted() } }}
Konklusjon
i Dag diskuterte Vi det kraftigste og enkleste designmønsteret i iOS-utviklingen. Jeg liker hvor enkelt det er og hvor nyttig det kan være å komponere stykker for å lage kodebase avkoblet. Føl deg fri til å følge Meg På Twitter og stille spørsmål knyttet til dette innlegget. Takk for at du leser og ser deg neste uke!