Tengo un problema al implementar el patrón MVC en iOS. He buscado en Internet pero parece no encontrar una buena solución para este problema.
Muchas UITableViewController
implementaciones parecen ser bastante grandes. La mayoría de los ejemplos que he visto permiten UITableViewController
implementar <UITableViewDelegate>
y <UITableViewDataSource>
. Estas implementaciones son una gran razón por la cual se UITableViewController
está haciendo grande. Una solución sería crear clases separadas que implementen <UITableViewDelegate>
y <UITableViewDataSource>
. Por supuesto, estas clases tendrían que tener una referencia a la UITableViewController
. ¿Hay algún inconveniente al usar esta solución? En general, creo que debería delegar la funcionalidad a otras clases "Helper" o similares, utilizando el patrón de delegado. ¿Hay alguna forma bien establecida de resolver este problema?
No quiero que el modelo contenga demasiada funcionalidad, ni la vista. Creo que la lógica realmente debería estar en la clase de controlador, ya que esta es una de las piedras angulares del patrón MVC. Pero la gran pregunta es:
¿Cómo debe dividir el controlador de una implementación MVC en piezas manejables más pequeñas? (Se aplica a MVC en iOS en este caso)
Puede haber un patrón general para resolver esto, aunque estoy buscando específicamente una solución para iOS. Dé un ejemplo de un buen patrón para resolver este problema. Proporcione un argumento por el cual su solución es increíble.
UITableViewController
mecánica me parece bastante extraña, así que puedo relacionarme con el problema. De hecho, me alegro de usarloMonoTouch
, porqueMonoTouch.Dialog
específicamente hace que sea mucho más fácil trabajar con tablas en iOS. Mientras tanto, tengo curiosidad por saber qué otras personas más informadas podrían sugerir aquí ...Respuestas:
Evito usar
UITableViewController
, ya que pone muchas responsabilidades en un solo objeto. Por lo tanto, separo laUIViewController
subclase del origen de datos y delego. La responsabilidad del controlador de vista es preparar la vista de tabla, crear una fuente de datos con datos y unir esas cosas. Se puede cambiar la forma en que se representa la vista de tabla sin cambiar el controlador de vista y, de hecho, se puede usar el mismo controlador de vista para múltiples fuentes de datos que siguen este patrón. Del mismo modo, cambiar el flujo de trabajo de la aplicación significa cambios en el controlador de vista sin preocuparse por lo que sucede en la tabla.Intenté separar los protocolos
UITableViewDataSource
yUITableViewDelegate
en diferentes objetos, pero eso generalmente termina siendo una división falsa ya que casi todos los métodos en el delegado necesitan profundizar en la fuente de datos (por ejemplo, en la selección, el delegado necesita saber qué objeto está representado por el fila seleccionada). Así que termino con un solo objeto que es tanto el origen de datos como el delegado. Este objeto siempre proporciona un método en el-(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
que tanto la fuente de datos como los aspectos delegados necesitan saber en qué están trabajando.Esa es mi separación de preocupaciones de "nivel 0". El nivel 1 se compromete si tengo que representar objetos de diferentes tipos en la misma vista de tabla. Como ejemplo, imagine que tenía que escribir la aplicación Contactos: para un solo contacto, podría tener filas que representan números de teléfono, otras filas que representan direcciones, otras que representan direcciones de correo electrónico, etc. Quiero evitar este enfoque:
Dos soluciones se han presentado hasta ahora. Una es construir dinámicamente un selector:
En este enfoque, no necesita editar el
if()
árbol épico para admitir un nuevo tipo, solo agregue el método que admite la nueva clase. Este es un gran enfoque si esta vista de tabla es la única que necesita representar estos objetos, o si necesita presentarlos de una manera especial. Si los mismos objetos se representarán en diferentes tablas con diferentes fuentes de datos, este enfoque se desglosa ya que los métodos de creación de celdas deben compartirse entre las fuentes de datos: puede definir una superclase común que proporcione estos métodos, o puede hacer esto:Luego, en su clase de fuente de datos:
Esto significa que cualquier fuente de datos que necesite mostrar números de teléfono, direcciones, etc. simplemente puede preguntar cualquier objeto representado para una celda de vista de tabla. La fuente de datos en sí ya no necesita saber nada sobre el objeto que se muestra.
"Pero espera", escucho una interposición hipotética del interlocutor, "¿eso no rompe MVC? ¿No estás poniendo los detalles de la vista en una clase de modelo?"
No, no rompe MVC. Puede pensar en las categorías en este caso como una implementación de Decorator ; así que
PhoneNumber
es una clase de modelo peroPhoneNumber(TableViewRepresentation)
es una categoría de vista. La fuente de datos (un objeto controlador) media entre el modelo y la vista, por lo que la arquitectura MVC aún se mantiene.También puede ver este uso de categorías como decoración en los marcos de Apple.
NSAttributedString
es una clase modelo, que contiene texto y atributos. AppKit proporcionaNSAttributedString(AppKitAdditions)
y UIKit proporcionaNSAttributedString(NSStringDrawing)
categorías de decorador que agregan comportamiento de dibujo a estas clases de modelos.fuente
cellForPhotoAtIndexPath
método de la fuente de datos, luego llama a un método de fábrica apropiado. Lo cual, por supuesto, solo es posible si clases particulares ocupan de manera predecible filas particulares. Creo que su sistema de generación de vistas en categorías en modelos es mucho más elegante en la práctica, ¡aunque tal vez sea un enfoque poco ortodoxo para MVC! :)Las personas tienden a empacar mucho en el UIViewController / UITableViewController.
La delegación a otra clase (no al controlador de vista) generalmente funciona bien. Los delegados no necesariamente necesitan una referencia de vuelta al controlador de vista, ya que todos los métodos de delegado pasan una referencia al
UITableView
, pero necesitarán acceder de alguna manera a los datos para los que delegan .Algunas ideas para la reorganización para reducir la longitud:
Si está construyendo las celdas de vista de tabla en el código, considere cargarlas desde un archivo plumín o desde un guión gráfico. Los guiones gráficos permiten celdas de tabla estáticas y de prototipo: consulte esas características si no está familiarizado
si sus métodos de delegado contienen muchas declaraciones 'if' (o declaraciones de cambio) esa es una señal clásica de que puede refactorizar
Siempre me pareció un poco extraño que
UITableViewDataSource
fuera responsable de controlar el bit de datos correcto y configurar una vista para mostrarlo. Un buen punto de refactorización podría ser cambiar sucellForRowAtIndexPath
para obtener un control de los datos que deben mostrarse en una celda, luego delegar la creación de la vista de celda a otro delegado (por ejemplo, hacer unaCellViewDelegate
o similar) que se pasa en el elemento de datos apropiado.fuente
Esto es más o menos lo que estoy haciendo actualmente cuando me enfrento a un problema similar:
Mueva operaciones relacionadas con datos a la clase XXXDataSource (que hereda de BaseDataSource: NSObject). BaseDataSource proporciona algunos métodos convenientes como
- (NSUInteger)rowsInSection:(NSUInteger)sectionNum;
, la subclase anula el método de carga de datos (ya que las aplicaciones generalmente tienen algún tipo de método de carga de caché externo- (void)loadDataWithUpdateBlock:(LoadProgressBlock)dataLoadBlock completion:(LoadCompletionBlock)completionBlock;
para que podamos actualizar la interfaz de usuario con los datos en caché recibidos en LoadProgressBlock mientras estamos actualizando la información de la red y en el bloque de finalización Actualizamos la interfaz de usuario con nuevos datos y eliminamos los indicadores de progreso, si los hay). Esas clases NO se ajustan alUITableViewDataSource
protocolo.En BaseTableViewController (que se ajusta a
UITableViewDataSource
yUITableViewDelegate
protocolos) Tengo referencia a BaseDataSource, que se crea durante init controlador. EnUITableViewDataSource
parte del controlador, simplemente devuelvo valores de dataSource (like- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.tableViewDataSource sectionsCount]; }
).Aquí está mi cellForRow en la clase base (no es necesario anular en las subclases):
configureCell debe ser anulado por subclases y createCell devuelve UITableViewCell, por lo que si desea una celda personalizada, anúlela también.
Después de configurar las cosas base (en realidad, en el primer proyecto que usa dicho esquema, después de que esta parte se puede reutilizar), lo que queda para las
BaseTableViewController
subclases es:Anule configureCell (esto generalmente se transforma en pedir dataSource para el objeto para la ruta de índice y alimentarlo al método configureWithXXX de la celda u obtener la representación UITableViewCell del objeto como en la respuesta del usuario 4051)
Override didSelectRowAtIndexPath: (obviamente)
Escriba la subclase BaseDataSource que se encarga de trabajar con la parte necesaria del Modelo (supongamos que hay 2 clases
Account
yLanguage
, por lo tanto, las subclases serán AccountDataSource y LanguageDataSource).Y eso es todo para la vista de tabla. Puedo publicar algún código en GitHub si es necesario.
Editar: algunas recomendaciones se pueden encontrar en http://www.objc.io/issue-1/lighter-view-controllers.html (que tiene un enlace a esta pregunta) y un artículo complementario sobre tableviewcontrollers.
fuente
Mi opinión sobre esto es que el modelo necesita dar una matriz de objetos que se llaman ViewModel o viewData encapsulados en un CellConfigurator. CellConfigurator contiene el CellInfo necesario para eliminarlo y configurar la celda. le da a la celda algunos datos para que la celda pueda configurarse a sí misma. esto también funciona con la sección si agrega algún objeto SectionConfigurator que contenga los CellConfigurators. Comencé a usar esto hace un tiempo inicialmente solo dándole a la celda un viewData y el ViewController se ocupó de retirar la celda. pero leí un artículo que señalaba este repositorio de gitHub.
https://github.com/fastred/ConfigurableTableViewController
Esto puede cambiar la forma en que te acercas a esto.
fuente
Recientemente escribí un artículo sobre cómo implementar delegados y fuentes de datos para UITableView: http://gosuwachu.gitlab.io/2014/01/12/uitableview-controller/
La idea principal es dividir las responsabilidades en clases separadas, como fábrica de células, fábrica de secciones, y proporcionar una interfaz genérica para el modelo que UITableView va a mostrar. El siguiente diagrama lo explica todo:
fuente
Seguir los principios SOLID resolverá cualquier tipo de problemas como estos.
Si usted quiere a sus clases para tener una sola responsabilidad, debe definir por separado
DataSource
yDelegate
clases y simplemente inyectar a latableView
propietario (que podría serUITableViewController
oUIViewController
o cualquier otra cosa). Así es como se supera la separación de la preocupación .Pero si solo desea tener un código limpio y legible y desea deshacerse de ese archivo masivo viewController y está en Swif , puede usar
extension
s para eso. Las extensiones de la clase única se pueden escribir en diferentes archivos y todas ellas tienen acceso entre sí. Pero esto es nit realmente resuelve el problema de SoC como mencioné.fuente