patron mvc explicado ios objective-c swift mvvm unsafemutablepointer

ios - explicado - mvvm vs mvc



¿Dónde colocar la creación de vistas en patrones MVVM y MVC? (1)

Disculpe si es un tema repetido.

Normalmente escribo mis aplicaciones sin guiones gráficos y pongo la creación de vistas en "viewDidLoad", como:

class LoginVC: UIViewController { var view1: UIView! var label1: UILabel! override func viewDidLoad() { super.viewDidLoad() loadStaticViews() } func loadStaticViews() { view1 = UIView() label1 = UILabel() view.addSubview(view1) view1.addSubview(label1) // constraints... } }

Y ahora quiero probar el patrón MVVM en mi próxima aplicación, y simplemente no estoy seguro de dónde poner la creación de vistas. Ahora pienso en algo así:

class LoginVCViews { static func loadViews<T, T1, T2>(superview: UnsafeMutablePointer<T>, view: UnsafeMutablePointer<T1>, label: UnsafeMutablePointer<T2>) { guard let superview = superview.pointee as? UIView else { return } let v = UIView() let l = UILabel() superview.addSubview(v) v.addSubview(l) // constraints... view.pointee = v as! T1 label.pointee = l as! T2 } } class LoginVC: UIViewController { private var view1: UIView! private var label1: UILabel! override func viewDidLoad() { super.viewDidLoad() LoginVCViews.loadViews(superview: &view, view: &view1, label: &label1) } }

Qué piensas ? No estoy muy familiarizado con UnsafeMutablePointer y no estoy seguro de que haya algunos problemas. ¿Y cuánto es feo?


Tal vez deberías probar la ruta totalmente orientada a objetos. Una composición de vista se ve algo así:

// conjunto de protocolos reutilizables

protocol OOString: class { var value: String { get } } protocol Executable: class { func execute() } protocol Screen: class { var ui: UIViewController { get } } protocol ViewRepresentation: class { var ui: UIView { get } }

// funcionalidad reutilizable (sin dependencia de uikit)

final class ConstString: OOString { init(_ value: String) { self.value = value } let value: String } final class ExDoNothing: Executable { func execute() { /* do nothing */ } } final class ExObjCCompatibility: NSObject, Executable { init(decorated: Executable) { self.decorated = decorated } func execute() { decorated.execute() } private let decorated: Executable }

// UI reutilizable (dependencia de uikit)

final class VrLabel: ViewRepresentation { init(text: OOString) { self.text = text } var ui: UIView { get { let label = UILabel() label.text = text.value label.textColor = UIColor.blue return label } } private let text: OOString } final class VrButton: ViewRepresentation { init(text: OOString, action: Executable) { self.text = text self.action = ExObjCCompatibility(decorated: action) } var ui: UIView { get { let button = UIButton() button.setTitle(text.value, for: .normal) button.addTarget(action, action: #selector(ExObjCCompatibility.execute), for: .touchUpInside) return button } } private let text: OOString private let action: ExObjCCompatibility } final class VrComposedView: ViewRepresentation { init(first: ViewRepresentation, second: ViewRepresentation) { self.first = first self.second = second } var ui: UIView { get { let view = UIView() view.backgroundColor = UIColor.lightGray let firstUI = first.ui view.addSubview(firstUI) firstUI.translatesAutoresizingMaskIntoConstraints = false firstUI.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true firstUI.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true firstUI.widthAnchor.constraint(equalToConstant: 100).isActive = true firstUI.heightAnchor.constraint(equalToConstant: 40).isActive = true let secondUI = second.ui view.addSubview(secondUI) secondUI.translatesAutoresizingMaskIntoConstraints = false secondUI.topAnchor.constraint(equalTo: firstUI.topAnchor).isActive = true secondUI.leadingAnchor.constraint(equalTo: firstUI.trailingAnchor, constant: 20).isActive = true secondUI.widthAnchor.constraint(equalToConstant: 80).isActive = true secondUI.heightAnchor.constraint(equalToConstant: 40).isActive = true return view } } private let first: ViewRepresentation private let second: ViewRepresentation }

// un viewcontroller

final class ContentViewController: UIViewController { convenience override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { self.init() } convenience required init(coder aDecoder: NSCoder) { self.init() } convenience init() { fatalError("Not supported!") } init(content: ViewRepresentation) { self.content = content super.init(nibName: nil, bundle: nil) } override func loadView() { view = content.ui } private let content: ViewRepresentation }

// y ahora la lógica comercial de una pantalla (no reutilizable)

final class ScStartScreen: Screen { var ui: UIViewController { get { return ContentViewController( content: VrComposedView( first: VrLabel( text: ConstString("Please tap:") ), second: VrButton( text: ConstString("OK"), action: ExDoNothing() ) ) ) } } }

Uso en AppDelegate:

window?.rootViewController = ScStartScreen().ui

Nota:

  • sigue las reglas de codificación orientada a objetos (codificación limpia, objetos elegantes, patrón decorador, ...)
  • cada clase es muy simple construida
  • las clases se comunican por protocolos entre sí
  • todas las dependencias están dadas por inyección de dependencia en la medida de lo posible
  • todo (excepto la pantalla comercial al final) es reutilizable -> de hecho: la cartera de código reutilizable crece cada día que codifica
  • la lógica comercial de su aplicación se concentra en implementaciones de objetos Screen
  • la prueba unitaria es muy simple cuando se utilizan implementaciones falsas para los protocolos (incluso la burla no es necesaria en la mayoría de los casos)
  • problemas menores con ciclos de retención
  • evitando Null, nil y Optionals (contaminan su código)
  • ...

En mi opinión, es la mejor forma de codificar, pero la mayoría de la gente no lo hace así.