the power of Delegate design pattern
29 May 2019
Last week before WWDC and everybody so excited about new features which we will have just in a few days. No entanto, vamos manter os postos relacionados com a WWDC para a próxima semana. Esta semana vamos falar sobre o meu delegado padrão de design favorito. O delegado é o padrão mais simples e poderoso.
em Engenharia de software, o padrão de delegação é um padrão de projeto orientado a objetos que permite a composição de objetos para alcançar a mesma reutilização de código como uma herança. Na delegação, um objeto lida com um pedido delegando em um segundo objeto (o delegado). Um delegado é um objeto auxiliar, mas com o contexto original.
protocolos
usamos padrão de Delegado todos os dias, e iOS SDK usa-o em muitos lugares. Por exemplo, delegados UITableView para Uitableviewdat como fonte populando a tabela com células, ele também delega seleção de células e outras ações para UITableViewDelegate. Outro excelente exemplo de patters delegados é o Fluxcontrolador ou coordenadores. Os visualizadores delegam a lógica de navegação ao Coordenador. Separei o post sobre extrair a lógica de navegação em Controladores de fluxo.Vamos mergulhar em amostras de código. Assume que estás a trabalhar num jogo. Você extraiu a lógica do jogo para um jogo de classes separado, e deseja delegar as alterações do Estado do jogo para o UIViewController, o que torna este jogo.
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 }}
Aqui está o código-fonte de um jogo simples que gera valores aleatórios. O motor de jogo gera estado baseado em valores aleatórios. Todos os delegados de mudança de Estado pedem para passar pelos antigos e novos Estados. Definimos o protocolo de delegado estendido de qualquer objeto, o que significa que a única instância de classe pode aceitá-lo. Também uso palavra-chave fraca para definir delegado variável. Ele precisava quebrar o ciclo de retenção entre delegado e classe de jogo. Vamos dar uma olhada no controle de Gamevew agora.
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) }}
aqui temos uma classe Gamevewcontroller que alimenta o jogo com as acções do utilizador e desenha alterações de Estado. Gamevewcontroller se conforma com GameDelegate e implementa todas as renderização necessárias em extensão. Como resultado, temos uma base de código composível com a ajuda do padrão de design Delegado.
Encerramentos
por vezes, quando se tem apenas um método no delegado, pode-se substituí-lo por Encerramento. A idéia é a mesma, mas agora você chama o fechamento e passa o estado em vez de chamar o método pelo protocolo. Vamos dar uma olhada no exemplo com encerramento.
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) } }}
como você pode ver, passamos o encerramento para a instância de classe de jogo que lida com mudanças de Estado. Usamos o weak para quebrar o ciclo de retenção durante a captura de contexto do fechamento. Outra opção aqui pode ser o uso do fato de que qualquer função Swift é um fechamento. Então, em vez de criar fechamento separado, podemos passar o nome da função. No entanto, tenha cuidado este método cria manter círculo. Aqui está um exemplo de como podemos fazer isso.
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() } }}
Conclusion
Today we discussed the most powerful and straightless design pattern in iOS development. Aprecio o quão simples é e o quão útil pode ser na composição de peças para tornar a base de código dissociada. Sinta-se livre para me seguir no Twitter e fazer suas perguntas relacionadas a este post. Obrigado por ler e até para a semana!