objective c - smart - Selectores o bloques para devoluciones de llamada en una biblioteca Objective-C
smart contracts ethereum (4)
Creo que lo correcto es implementar ambos, usarlos como clientes y ver qué se siente más natural. Hay ventajas para ambos enfoques, y realmente depende del contexto y de cómo espera que se use el SDK.
La principal ventaja de los selectores es la simple administración de la memoria: mientras el cliente se registre y se cancele el registro correctamente, no tendrá que preocuparse por las pérdidas de memoria. Con los bloques, la administración de la memoria puede complicarse, dependiendo de lo que el cliente haga dentro del bloque. También es más fácil probar el método de devolución de llamada por unidad. Los bloques pueden ser escritos para ser verificables , pero no es una práctica común de lo que he visto.
La principal ventaja de los bloques es la flexibilidad: el cliente puede hacer referencia fácilmente a las variables locales sin tener que crearlas.
Así que creo que solo depende del caso de uso, no hay una "respuesta correcta objetiva" para una pregunta de diseño tan general.
Pregunta
Estamos desarrollando un sistema de mensajes personalizado inspirado en EventEmitter en Objective-C. Para que los oyentes proporcionen devoluciones de llamada, ¿deberíamos requerir blocks o selectors y por qué?
¿Cuál preferirías usar como desarrollador que consume una biblioteca de terceros? ¿Cuál parece estar más en línea con la trayectoria, las pautas y las prácticas de Apple?
Fondo
Estamos desarrollando un nuevo SDK de iOS en Objective-C que otros terceros utilizarán para integrar la funcionalidad en su aplicación. Una gran parte de nuestro SDK requerirá la comunicación de eventos a los oyentes.
Conozco cinco patrones para realizar devoluciones de llamada en Objective-C, tres de los cuales no encajan:
- NSNotificationCenter : no se puede usar porque no garantiza que se notificará a los observadores del pedido y porque no hay forma de que los observadores eviten que otros observadores reciban el evento (como lo
stopPropagation()
en JavaScript). - Observación de valor clave : no parece un buen ajuste arquitectónico, ya que lo que realmente tenemos es el paso de mensajes, no siempre el "estado" vinculado.
- Delegados y fuentes de datos : en nuestro caso, generalmente habrá muchos oyentes, ni uno solo que, con razón, podría llamarse el delegado.
Y dos de los cuales son contendientes:
- selectors : bajo este modelo, los llamadores proporcionan un selector y un objetivo que se invocan colectivamente para manejar un evento.
- blocks : introducidos en iOS 4, los bloques permiten pasar la funcionalidad sin estar vinculados a un objeto como el patrón observador / selector.
Esto puede parecer una pregunta de opinión esotérica, pero creo que hay una respuesta "correcta" y objetiva que simplemente no tengo experiencia en Objective-C para determinar. Si hay un mejor sitio de StackExchange para esta pregunta, ayúdeme moviéndolo allí.
ACTUALIZACIÓN # 1 - Abril 2013
Elegimos los bloques como el medio para especificar devoluciones de llamada para nuestros manejadores de eventos. Estamos muy satisfechos con esta opción y no planeamos eliminar el soporte de la escucha basada en bloque. Tenía dos inconvenientes notables: la gestión de memoria y la impedancia de diseño.
Gestión de la memoria
Los bloques se usan más fácilmente en la pila. La creación de bloques de larga duración al copiarlos en el montón presenta interesantes problemas de administración de memoria.
Los bloques que hacen llamadas a los métodos en el objeto que contiene aumentan implícitamente el conteo de referencias del self
. Supongamos que tiene un setter para la propiedad de name
de su clase, si llama a name = @"foo"
dentro de un bloque, el compilador lo trata como [self setName:@"foo"]
y se conserva a self
para que no sea desasignado mientras el bloque sigue existiendo.
Implementar un EventEmitter significa tener bloques de larga duración. Para evitar la retención implícita, el usuario del emisor debe crear una __block
referencia a self
fuera del bloque, por ejemplo:
__block *YourClass this = self;
[emitter on:@"eventName" callBlock:...
[this setName:@"foo"];...
}];
El único problema con este enfoque es que se puede desasignar antes de invocar el controlador. Por lo tanto, los usuarios deben anular el registro de sus oyentes cuando se les asigna un cargo.
Impedancia de diseño
Los experimentados desarrolladores de Objective-C esperan interactuar con las bibliotecas utilizando patrones familiares. Los delegados son un patrón tremendamente familiar, y por eso los desarrolladores canónicos esperan usarlo.
Afortunadamente, el patrón de delegado y los oyentes basados en bloques no se excluyen mutuamente. Aunque nuestro emisor debe ser capaz de manejar escuchas desde muchos lugares (tener un solo delegado no funciona), aún podemos exponer una interfaz que permitiría a los desarrolladores interactuar con el emisor como si su clase fuera el delegado.
No hemos implementado esto todavía, pero probablemente lo haremos en base a las solicitudes de los usuarios.
ACTUALIZACIÓN # 2 - Octubre 2013
Ya no estoy trabajando en el proyecto que generó esta pregunta, después de haber regresado felizmente a mi tierra natal de JavaScript.
Los desarrolladores inteligentes que se hicieron cargo de este proyecto decidieron retirar por completo nuestro EventEmitter basado en bloques personalizados. El próximo lanzamiento ha cambiado a ReactiveCocoa .
Esto les da un patrón de señalización de nivel más alto que el que nuestra biblioteca EventEmitter brindó previamente, y les permite encapsular el estado dentro de los manejadores de señales mejor que los manejadores de eventos basados en bloques o los métodos de nivel de clase.
Gran escritura!
Desde que escribo mucho JavaScript, la programación dirigida por eventos se siente mucho más limpia que tener delegados de un lado a otro, en mi opinión personal.
Con respecto al aspecto de manejo de la memoria de los oyentes, mi intento de resolver esto (basándose en gran medida en el MAKVONotificationCenter de Mike Ash), frena tanto la implementación del dealloc
del llamador como la del emisor ( como se ve aquí ) para eliminar a los oyentes de forma segura en ambos sentidos.
No estoy completamente seguro de qué tan seguro es este enfoque, pero la idea es intentarlo hasta que se rompa.
Personalmente, odio usar delegados. Debido a la forma en que está estructurado el objetivo-C, realmente se desordena el código Si tengo que crear un objeto separado / agregar un protocolo solo para recibir una notificación de uno de sus eventos, y tengo que implementar 5/6. Por este motivo, prefiero los bloques.
Mientras que ellos (los bloques) tienen sus desventajas (la administración de memoria ex puede ser complicada). Son fácilmente extensibles, fáciles de implementar y solo tienen sentido en la mayoría de las situaciones.
Si bien las estructuras de diseño de Apple pueden usar el método delegado-remitente, esto es solo por compatibilidad con versiones anteriores. Las API de Apple más recientes han estado usando bloques (ex CoreData), porque son el futuro de object-c. Si bien pueden saturar el código cuando se usa por la borda, también permite "delegados anónimos" más simples, lo que no es posible en el objetivo C.
Sin embargo, al final, todo se reduce a esto: ¿estás dispuesto a abandonar algunas plataformas más antiguas y anticuadas a cambio de usar bloques en lugar de un delegado? Una de las principales ventajas de un delegado es que está garantizado que funciona en cualquier versión de objc-runtime, mientras que los bloques son una adición más reciente al lenguaje.
En lo que respecta a NSNotificationCenter
/ KVO
, ambos son útiles y tienen sus propósitos, pero como delegados, no están destinados a ser utilizados. Tampoco puede enviar un resultado al remitente, y para algunas situaciones, esto es vital (por ejemplo, -webView:shouldLoadRequest:
).
Una cosa sobre una biblioteca es que solo se puede anticipar de alguna manera, cómo se usará. Por lo tanto, debe proporcionar una solución tan simple y abierta como sea posible, y familiar para los usuarios.
- Para mí todo esto se ajusta mejor a la delegación. Aunque tiene razón, eso solo puede tenerlo en el oyente (delegado), esto significa que no hay limitación, ya que el usuario puede escribir una clase como delegado, que conoce todos los oyentes deseados y les informa. Por supuesto que puede proporcionar una clase de registro. que llamará a los métodos delegados en todos los objetos registrados.
- Los bloques son tan buenos.
- lo que nombran selectores se llama objetivo / acción y simple pero poderoso.
- KVO parece no ser una solución óptima para mí, ya que posiblemente debilitaría la encapsulación, o conduciría a un modelo mental erróneo de cómo usar las clases de su biblioteca.
- NSNotifications es bueno informar sobre ciertos eventos, pero los usuarios no deben ser obligados a usarlos, ya que son bastante informales. y sus clases no podrán saberlo si hay alguien sintonizado.
Algunas ideas útiles sobre el diseño de la API: http://mattgemmell.com/2012/05/24/api-design/