Swift cu Majid

puterea modelului de design delegat

29 Mai 2019

săptămâna trecută înainte de WWDC și toată lumea atât de încântată de noile caracteristici pe care le vom avea doar în câteva zile. Cu toate acestea, să păstrăm postările legate de WWDC pentru săptămâna viitoare. Săptămâna aceasta vom vorbi despre delegatul meu preferat de design. Delegatul este modelul cel mai simplu și puternic.

în ingineria software, modelul de delegare este un model de proiectare orientat pe obiecte care permite compoziției obiectului să obțină aceeași reutilizare a codului ca moștenire. În delegare, un obiect gestionează o cerere prin delegarea unui al doilea obiect (delegatul). Un delegat este un obiect de ajutor, dar cu contextul original.

protocoale

folosim modelul delegat în fiecare zi, iar iOS SDK îl folosește în multe locuri. De exemplu, UITableView delegați la UITableViewDataSource populând tabelul cu celule, delegă, de asemenea, selecția celulelor și alte acțiuni la UITableViewDelegate. Un alt exemplu excelent de tipare delegate este FlowController sau coordonatori. ViewControllers delegă logica de navigare coordonatorului. Am separat post despre extragerea logica de navigare în Flowcontrolers.

să ne scufundăm în mostre de cod. Să presupunem că lucrați la un joc. Ai extras logica joc în joc de clasă separat, și doriți să delege modificări de Stat joc la UIViewController care face acest joc.

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

aici este codul sursă al unui joc simplu, care generează valori aleatoare. Motorul de joc generează starea bazată pe valori aleatorii. Fiecare delegat apel schimbare de stat pentru a trece state vechi și noi. Definim protocolul nostru delegat extins de la AnyObject, ceea ce înseamnă că singura instanță de clasă o poate accepta. De asemenea, folosesc cuvinte cheie slabe pentru a defini delegatul de exploatație variabilă. Trebuia să rupă ciclul de reținere între delegat și clasa de joc. Să aruncăm o privire la GameViewController acum.

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

aici avem o clasă GameViewController care alimentează joc cu acțiuni de utilizator și face modificări de stat. GameViewController este conform cu GameDelegate și pune în aplicare toate randare necesare în extensie. Ca rezultat, avem un codebase composable cu ajutorul modelului de design delegat.

închideri

uneori, când aveți o singură metodă în delegat, o puteți înlocui cu închidere. Ideea este aceeași, dar acum apelați închiderea și treceți starea în loc să apelați metoda prin protocol. Să aruncăm o privire la exemplul cu închidere.

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

după cum puteți vedea, trecem închiderea la instanța de clasă de joc care gestionează modificările de stat. Folosim slab pentru a rupe ciclul de reținere în timpul captării contextului închiderii. O altă opțiune aici poate fi o utilizare a faptului că orice funcție Swift este o închidere. Deci, în loc să creăm închidere separată, putem trece numele funcției. Cu toate acestea, fii atent această metodă creează Reține cerc. Iată un exemplu despre cum putem face acest lucru.

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

concluzie

astăzi am discutat despre cel mai puternic și mai simplu model de design în dezvoltarea iOS. Îmi place cât de simplu este și cât de util poate fi în compunerea pieselor pentru a face codebase decuplat. Simțiți-vă liber să mă urmați pe Twitter și să vă puneți întrebările legate de această postare. Vă mulțumim pentru lectură și ne vedem săptămâna viitoare!



+