¿Cuáles son las mejores prácticas que utiliza al escribir Objective-C y Cocoa? [cerrado]

346

Sé sobre HIG (¡lo cual es bastante útil!), Pero qué prácticas de programación utilizas cuando escribes Objective-C, y más específicamente cuando usas Cocoa (o CocoaTouch).

píxeles
fuente
Mira esta publicación de blog, muy agradable. ironwolf.dangerousgames.com/blog/archives/913
user392412

Respuestas:

398

Hay algunas cosas que he comenzado a hacer que no creo que sean estándar:

1) Con el advenimiento de las propiedades, ya no uso "_" para prefijar las variables de clase "privadas". Después de todo, si otras clases pueden acceder a una variable, ¿no debería haber una propiedad para ella? Siempre me disgustó el prefijo "_" para hacer que el código sea más feo, y ahora puedo omitirlo.

2) Hablando de cosas privadas, prefiero colocar definiciones de métodos privados dentro del archivo .m en una extensión de clase como esta:

#import "MyClass.h"

@interface MyClass ()
- (void) someMethod;
- (void) someOtherMethod;
@end

@implementation MyClass

¿Por qué desordenar el archivo .h con cosas que a los extraños no les debería importar? Empty () funciona para categorías privadas en el archivo .m y emite advertencias de compilación si no implementa los métodos declarados.

3) Me puse a poner dealloc en la parte superior del archivo .m, justo debajo de las directivas @synthesize. ¿No debería estar en la parte superior de la lista de cosas en las que quiere pensar en una clase? Eso es especialmente cierto en un entorno como el iPhone.

3.5) En las celdas de la tabla, haga que cada elemento (incluida la celda misma) sea opaco para el rendimiento. Eso significa establecer el color de fondo apropiado en todo.

3.6) Al usar una NSURLConnection, como regla, es posible que desee implementar el método delegado:

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
      return nil;
}

Creo que la mayoría de las llamadas web son muy singulares y es más la excepción que la regla que querrá que las respuestas se almacenen en caché, especialmente para las llamadas de servicio web. La implementación del método como se muestra deshabilita el almacenamiento en caché de las respuestas.

También son interesantes algunos buenos consejos específicos para iPhone de Joseph Mattiello (recibidos en una lista de correo de iPhone). Hay más, pero estos fueron los más útiles en general, pensé (tenga en cuenta que ahora se han editado algunos bits del original para incluir los detalles ofrecidos en las respuestas):

4) Solo use doble precisión si es necesario, como cuando trabaja con CoreLocation. Asegúrese de terminar sus constantes en 'f' para que gcc las almacene como flotantes.

float val = someFloat * 2.2f;

Esto es principalmente importante cuando en someFloatrealidad puede ser un doble, no necesita las matemáticas de modo mixto, ya que está perdiendo precisión en 'val' en el almacenamiento. Si bien los números de punto flotante son compatibles con el hardware en los iPhones, aún puede llevar más tiempo hacer aritmética de doble precisión en lugar de precisión simple. Referencias

En los teléfonos más antiguos, los cálculos supuestamente operan a la misma velocidad, pero puede tener más componentes de precisión individuales en los registros que los dobles, por lo que para muchos cálculos, la precisión simple terminará siendo más rápida.

5) Establezca sus propiedades como nonatomic. Son atomicpor defecto y luego de la síntesis, se creará un código de semáforo para evitar problemas de subprocesos múltiples. Probablemente el 99% de ustedes no necesiten preocuparse por esto y el código es mucho menos hinchado y más eficiente en memoria cuando se configura como no atómico.

6) SQLite puede ser una forma muy, muy rápida de almacenar en caché grandes conjuntos de datos. Una aplicación de mapas, por ejemplo, puede almacenar en caché sus mosaicos en archivos SQLite. La parte más cara es la E / S de disco. Evite muchas escrituras pequeñas enviando BEGIN;y COMMIT;entre bloques grandes. Usamos un temporizador de 2 segundos, por ejemplo, que se restablece en cada envío nuevo. Cuando caduca, enviamos COMMIT; , lo que hace que todas sus escrituras vayan en una gran parte. SQLite almacena los datos de las transacciones en el disco y al hacer este ajuste de inicio / fin evita la creación de muchos archivos de transacciones, agrupando todas las transacciones en un solo archivo.

Además, SQL bloqueará su GUI si está en su hilo principal. Si tiene una consulta muy larga, es una buena idea almacenar sus consultas como objetos estáticos y ejecutar su SQL en un hilo separado. Asegúrese de ajustar todo lo que modifique la base de datos para las cadenas de consulta en @synchronize() {}bloques. Para consultas cortas, simplemente deje las cosas en el hilo principal para mayor comodidad.

Aquí encontrará más sugerencias de optimización de SQLite, aunque el documento parece desactualizado, muchos de los puntos probablemente todavía sean buenos;

http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html

Kendall Helmstetter Gelner
fuente
3
Buen aviso sobre la doble aritmética.
Adam Ernst
8
Las extensiones de clase ahora son la forma preferida para métodos privados: developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/…
Casebash
99
Su consejo sobre los dobles en el iPhone está desactualizado stackoverflow.com/questions/1622729/…
Casebash
3
No está desactualizado; completamente equivocado: el iPhone original admite flotadores y se duplica en hardware a aproximadamente la misma velocidad. SQLite tampoco guarda las transacciones en la memoria; están registrados en el disco. Solo las consultas largas bloquean su IU; Es menos complicado ejecutar todo en el hilo principal y utilizar consultas más rápidas.
tc.
1
@tc: corrigí el elemento SQL sobre las transacciones, tenga en cuenta que yo mismo no escribí esos últimos cuatro elementos. También aclaré que la parte sobre mover las consultas al fondo era solo para consultas muy largas (a veces no se pueden acortar). Pero llamar a todo el asunto "incorrecto" por algunos puntos es que me siento bastante extremo. Además, la respuesta anterior ya decía: "En los teléfonos más antiguos, los cálculos supuestamente funcionan a la misma velocidad", pero tenga en cuenta la parte sobre el mayor número de registros de precisión individuales que los hacen aún preferibles.
Kendall Helmstetter Gelner el
109

No use cadenas desconocidas como cadenas de formato

Cuando los métodos o funciones toman un argumento de cadena de formato, debe asegurarse de tener control sobre el contenido de la cadena de formato.

Por ejemplo, al registrar cadenas, es tentador pasar la variable de cadena como único argumento para NSLog:

    NSString *aString = // get a string from somewhere;
    NSLog(aString);

El problema con esto es que la cadena puede contener caracteres que se interpretan como cadenas de formato. Esto puede conducir a resultados erróneos, bloqueos y problemas de seguridad. En su lugar, debe sustituir la variable de cadena en una cadena de formato:

    NSLog(@"%@", aString);
mmalc
fuente
44
He sido mordido por este antes.
Adam Ernst
Este es un buen consejo para cualquier lenguaje de programación
Tom Fobear,
107

Use convenciones y terminología estándar de nomenclatura y formato de Cocoa en lugar de lo que sea que esté acostumbrado de otro entorno. No son muchos desarrolladores de cacao por ahí, y cuando otro de ellos comienza a trabajar con su código, que será mucho más accesible si se ve y se siente similar a otro código de cacao.

Ejemplos de qué hacer y qué no hacer:

  • No declare id m_something;en la interfaz de un objeto ni lo llame variable o campo miembro ; use somethingo _somethingpor su nombre y llámelo variable de instancia .
  • No nombre a un captador -getSomething; el nombre correcto de Cocoa es justo -something.
  • No nombre a un setter -something:; debería ser-setSomething:
  • El nombre del método se intercala con los argumentos e incluye dos puntos; es -[NSObject performSelector:withObject:], no NSObject::performSelector.
  • Use inter-mayúsculas (CamelCase) en los nombres de métodos, parámetros, variables, nombres de clase, etc., en lugar de guiones bajos (guiones bajos).
  • Los nombres de clase comienzan con letras mayúsculas, variables y nombres de métodos con minúsculas.

Hagas lo que hagas, no uses la notación húngara estilo Win16 / Win32. Incluso Microsoft renunció a eso con el cambio a la plataforma .NET.

Chris Hanson
fuente
55
Yo diría que no use setSomething: / something en absoluto, en su lugar use las propiedades. En este punto, hay pocas personas que realmente necesiten apuntar a Tiger (la única razón para no usar propiedades)
Kendall Helmstetter Gelner
18
Las propiedades aún generan métodos de acceso para usted, y los atributos getter = / setter = en la propiedad le permiten especificar los nombres de los métodos. Además, puede usar la sintaxis [foo something] en lugar de la sintaxis foo.sthingthing con propiedades. Por lo tanto, el nombre del descriptor de acceso sigue siendo relevante.
Chris Hanson
3
Esta es una gran referencia para alguien que viene de C ++, donde hice la mayoría de las cosas que desaconsejas.
Clinton Blackmore
44
Un setter no debería estar causando que algo se guarde en la base de datos. Hay una razón por la que Core Data tiene un método -save: en NSManagedObjectContext, en lugar de que los configuradores generen actualizaciones inmediatas.
Chris Hanson
2
Dudo que no fuera una opción, sin embargo, puede haber requerido volver a visitar la arquitectura de su aplicación. (Para ser claros: no estoy diciendo "Deberías haber usado Core Data". Estoy diciendo "Los setters no deberían guardar en la base de datos"). Tener un contexto para administrar un gráfico de objetos, en lugar de guardar objetos individuales en él. , es prácticamente siempre posible y una mejor solución.
Chris Hanson el
106

IBOutlets

Históricamente, la gestión de la memoria de los puntos de venta ha sido deficiente. La mejor práctica actual es declarar salidas como propiedades:

@interface MyClass :NSObject {
    NSTextField *textField;
}
@property (nonatomic, retain) IBOutlet NSTextField *textField;
@end

El uso de propiedades aclara la semántica de administración de memoria; También proporciona un patrón consistente si utiliza la síntesis de variables de instancia.

mmalc
fuente
1
¿no se retendría dos veces la carga de la punta? (una vez en plumín, segundo por asignación a la propiedad). ¿Se supone que debo liberar a los que están en Dealloc?
Kornel
66
Debe cerrar las salidas en viewDidUnload (iPhone OS 3.0+) o en un setView personalizado: método para evitar fugas. Obviamente, también deberías liberarlo en Dealloc.
Frank Szczerba
2
Tenga en cuenta que no todos están de acuerdo con este estilo: weblog.bignerdranch.com/?p=95
Michael
Esta es la forma en que Apple también hace las cosas. "Inicio del desarrollo del iPhone 3" también menciona este cambio respecto a las versiones anteriores.
ustun
Mencioné esto en otro comentario, pero debería haberlo colocado aquí: una vez que la síntesis dinámica de ivar comience a suceder para las aplicaciones de iOS (¿si / cuándo?), ¡Se alegrará de poner IBOutlet en la propiedad frente al ivar!
Joe D'Andrea
97

Utilice el analizador estático LLVM / Clang

NOTA: En Xcode 4, esto ahora está integrado en el IDE.

Utiliza el Analizador estático de Clang para, como era de esperar, analizar su código C y Objective-C (todavía no C ++) en Mac OS X 10.5. Es trivial instalar y usar:

  1. Descargue la última versión de esta página .
  2. Desde la línea de comandos, cdal directorio de su proyecto.
  3. Ejecutar scan-build -k -V xcodebuild.

(Existen algunas restricciones adicionales, etc., en particular, debe analizar un proyecto en su configuración "Depurar"; consulte http://clang.llvm.org/StaticAnalysisUsage.html para obtener más detalles, pero eso es más o menos a lo que se reduce.)

Luego, el analizador produce un conjunto de páginas web para usted que muestra la administración probable de la memoria y otros problemas básicos que el compilador no puede detectar.

mmalc
fuente
1
Tuve algunos problemas para que esto funcione hasta que seguí estas instrucciones: oiledmachine.com/posts/2009/01/06/…
bbrown
15
En XCode 3.2.1 en Snow Leopard, ya está integrado. Puede ejecutarlo manualmente, usando Ejecutar -> Construir y analizar , o puede habilitarlo para todas las compilaciones a través de la configuración de compilación "Ejecutar analizador estático". Tenga en cuenta que actualmente esta herramienta solo admite C y Objective-C, pero no C ++ / Objective-C ++.
oefe
94

Este es uno sutil pero práctico. Si te estás pasando como delegado a otro objeto, reinicia el delegado de ese objeto antes que tú dealloc.

- (void)dealloc
{
self.someObject.delegate = NULL;
self.someObject = NULL;
//
[super dealloc];
}

Al hacer esto, se asegura de que no se envíen más métodos de delegado. Cuando esté a punto de deallocdesaparecer en el éter, debe asegurarse de que nada pueda enviarle más mensajes por accidente. Recuerde self.someObject podría ser retenido por otro objeto (podría ser un singleton o en el grupo de liberación automática o lo que sea) y hasta que le diga "¡deja de enviarme mensajes!", Piensa que es un objeto a punto de ser desalojado Es un juego justo.

Entrar en este hábito lo salvará de muchos accidentes extraños que son difíciles de depurar.

El mismo principio se aplica a la Observación del valor clave y a las Notificaciones NS también.

Editar:

Aún más defensivo, cambio:

self.someObject.delegate = NULL;

dentro:

if (self.someObject.delegate == self)
    self.someObject.delegate = NULL;
schwa
fuente
8
No hay nada sutil en esto, la documentación dice claramente que debe hacerlo. A partir de Memory Management Programming Guide for Cocoa: Additional cases of weak references in Cocoa include, but are not restricted to, table data sources, outline view items, notification observers, and miscellaneous targets and delegates. In most cases, the weak-referenced object is aware of the other object’s weak reference to it, as is the case for circular references, and is responsible for notifying the other object when it deallocates.
Johne
Es mejor usar nil en lugar de NULL, porque NULL no liberará la memoria.
Naveen Shan
@NaveenShan nil == NULL. Son exactamente iguales excepto que niles un idy NULLes un void *. Tu afirmación no es cierta.
@WTP sip, nil == NULL, pero usar nil es claramente la forma preferida, si miras a través de fragmentos de código de ejemplo de manzanas, están usando nil en todas partes, y como dijiste, nil es un id, lo que lo hace preferible sobre el vacío * , en los casos en que envía identificadores, es decir.
Ahti
1
@Ahti exactamente, y Nil(mayúscula) es de tipo Class*. Aunque todos son iguales, usar el incorrecto puede introducir pequeños errores desagradables, especialmente en Objective-C ++.
86

@kendell

En vez de:

@interface MyClass (private)
- (void) someMethod
- (void) someOtherMethod
@end

Utilizar:

@interface MyClass ()
- (void) someMethod
- (void) someOtherMethod
@end

Nuevo en Objective-C 2.0.

Las extensiones de clase se describen en la Referencia de Objective-C 2.0 de Apple.

"Las extensiones de clase le permiten declarar API adicionales requeridas para una clase en ubicaciones que no estén dentro del bloque de clase primaria @interface"

Entonces son parte de la clase real, y NO una categoría (privada) además de la clase. Sutil pero importante diferencia.

schwa
fuente
Podría hacerlo, pero me gusta etiquetarlo explícitamente como una sección "privada" (más documentación que funcional) aunque, por supuesto, eso ya es bastante obvio por estar ubicado en el archivo .m ...
Kendall Helmstetter Gelner
2
Excepto que es una diferencia entre las categorías y extensiones privadas de clase: "extensiones de clase le permiten declarar API requerida para una clase adicional en lugares distintos dentro del bloque @interface clase primaria, como se ilustra en el siguiente ejemplo:" ver enlace en edición.
schwa
Estoy de acuerdo en que hay una diferencia en la que el compilador le avisará cuando no haya implementado métodos CE, pero no considero que ese aspecto sea muy importante cuando todos los métodos están en el mismo archivo, y todos son privados. Todavía prefiero el aspecto de mantenibilidad de marcar el bloque de referencia directa como privado
Kendall Helmstetter Gelner
3
Realmente no veo (Privado) como más fácil de mantener que (). Si está tan preocupado, una buena dosis de comentarios podría ayudar. Pero obviamente vive y deja vivir. YMMV etc.
schwa
17
Hay una ventaja bastante importante para usar en ()lugar de (Private)(o algún otro nombre de categoría): puede volver a declarar propiedades como readwrite mientras que para el público solo son de lectura. :)
Pascal
75

Evite la liberación automática

Dado que normalmente (1) no tiene control directo sobre su vida útil, los objetos liberados automáticamente pueden persistir durante un tiempo relativamente largo y aumentar innecesariamente la huella de memoria de su aplicación. Mientras que en el escritorio esto puede tener pocas consecuencias, en plataformas más restringidas puede ser un problema importante. Por lo tanto, en todas las plataformas, y especialmente en las plataformas más restringidas, se considera una mejor práctica evitar el uso de métodos que conduzcan a objetos que se liberen automáticamente y, en su lugar, se le recomienda usar el patrón alloc / init.

Por lo tanto, en lugar de:

aVariable = [AClass convenienceMethod];

donde sea posible, en su lugar debería usar:

aVariable = [[AClass alloc] init];
// do things with aVariable
[aVariable release];

Cuando escribe sus propios métodos que devuelven un objeto recién creado, puede aprovechar la convención de nomenclatura de Cocoa para indicarle al receptor que debe liberarse anteponiendo el nombre del método con "nuevo".

Por lo tanto, en lugar de:

- (MyClass *)convenienceMethod {
    MyClass *instance = [[[self alloc] init] autorelease];
    // configure instance
    return instance;
}

podrías escribir:

- (MyClass *)newInstance {
    MyClass *instance = [[self alloc] init];
    // configure instance
    return instance;
}

Como el nombre del método comienza con "nuevo", los consumidores de su API saben que son responsables de liberar el objeto recibido (ver, por ejemplo, el newObjectmétodo NSObjectController ).

(1) Puede tomar el control utilizando sus propios grupos locales de liberación automática. Para obtener más información al respecto, consulte Grupos de lanzamiento automático .

mmalc
fuente
66
Encuentro que los beneficios de no usar la liberación automática superan sus costos (es decir, más errores de pérdida de memoria). El código en el subproceso principal debe ser bastante corto de todos modos (o de lo contrario se congelará la interfaz de usuario) y para un código de fondo de mayor duración y uso intensivo de memoria, siempre puede envolver las porciones intensivas en memoria en grupos locales de liberación automática.
adib
56
Estoy en desacuerdo. Debe usar objetos con autorrelegación siempre que sea posible. Si aumentan demasiado la huella de la memoria, debe usar otro NSAutoreleasePool. Pero solo después de confirmar que esto realmente es un problema. Optimización prematura y todo eso ...
Sven
3
Paso menos de 40 segundos un día escribiendo [lanzamiento de algún objeto] y leyendo la "línea extra" al crear instancias de un nuevo objeto, pero una vez quemé durante 17 horas para encontrar un error de liberación automática que solo aparecería en casos especiales y no daba ningún error coherente en la consola. Así que estoy de acuerdo con adib cuando lo expresa como "Encuentro que los beneficios de no usar la liberación automática superan sus costos".
RickiG
77
Estoy de acuerdo con Sven. El objetivo principal debe ser la claridad del código y reducir los errores de codificación, con la optimización de la memoria solo donde se necesita. Escribir un [[[Foo alloc] init] autorelease] es rápido e inmediatamente lidias con el problema de liberar este nuevo objeto. Al leer el código, no tiene que buscar la versión correspondiente para asegurarse de que no se filtre.
Mike Weller
3
El ciclo de vida de los objetos lanzados automáticamente está bien definido y puede determinarse a un nivel suficiente.
Eonil
69

Algunos de estos ya se han mencionado, pero esto es lo que puedo pensar de la parte superior de mi cabeza:

  • Siga las reglas de nombres de KVO. Incluso si no usa KVO ahora, en mi experiencia muchas veces sigue siendo beneficioso en el futuro. Y si está utilizando KVO o enlaces, necesita saber que las cosas van a funcionar de la manera en que se supone que deben hacerlo. Esto cubre no solo los métodos de acceso y las variables de instancia, sino también muchas relaciones, validación, claves dependientes de notificación automática, etc.
  • Poner métodos privados en una categoría. No solo la interfaz, sino también la implementación. Es bueno tener cierta distancia conceptual entre métodos privados y no privados. Incluyo todo en mi archivo .m.
  • Coloque los métodos de subprocesos de fondo en una categoría. Lo mismo que arriba. Descubrí que es bueno mantener una barrera conceptual clara cuando estás pensando en lo que está en el hilo principal y en lo que no.
  • Uso #pragma mark [section]. Por lo general, me agrupo por mis propios métodos, las anulaciones de cada subclase y cualquier información o protocolos formales. Esto hace que sea mucho más fácil saltar a exactamente lo que estoy buscando. Sobre el mismo tema, agrupe métodos similares (como los métodos delegados de una vista de tabla), no los pegue en ningún lado.
  • Prefije métodos privados y ivars con _. Me gusta cómo se ve, y es menos probable que use un ivar cuando me refiero a una propiedad por accidente.
  • No use métodos / propiedades mutantes en init y dealloc. Nunca he tenido nada malo debido a eso, pero puedo ver la lógica si cambias el método para hacer algo que depende del estado de tu objeto.
  • Ponga IBOutlets en propiedades. De hecho, acabo de leer este aquí, pero voy a comenzar a hacerlo. Independientemente de los beneficios de memoria, parece mejor estilísticamente (al menos para mí).
  • Evite escribir código que no necesita absolutamente. Esto realmente cubre muchas cosas, como hacer ivars cuando #definesea ​​necesario o almacenar en caché una matriz en lugar de ordenarla cada vez que se necesitan los datos. Hay muchas cosas que podría decir sobre esto, pero la conclusión es que no escriba código hasta que lo necesite, o el perfilador le diga que lo haga. Hace que las cosas sean mucho más fáciles de mantener a largo plazo.
  • Termina lo que comienzas. Tener una gran cantidad de códigos a medio terminar es la forma más rápida de matar un proyecto muerto. Si necesita un método de código auxiliar que esté bien, simplemente indíquelo poniéndolo NSLog( @"stub" )dentro, o como quiera hacer un seguimiento de las cosas.
Marc Charbonneau
fuente
3
Te sugiero que coloques métodos privados en una continuación de clase. (es decir, @interface MyClass () ... @end en tu .m)
Jason Medeiros
3
En lugar de #PRAGMA, puede usar un comentario // Marcar: [Sección], que es más portátil y funciona de manera idéntica.
aleemb
A menos que haya una sintaxis especial que me falta, // Mark: no agrega una etiqueta en el menú desplegable de funciones de Xcode, lo cual es realmente la mitad de la razón para usarlo.
Marc Charbonneau
66
Debe usar mayúsculas, "// MARK: ...", para que aparezca en el menú desplegable.
Rhult
3
En lo que respecta a Finish what you start, también puede usar // TODO:para marcar el código para completar que se mostrará en el menú desplegable.
Fui robado el
56

Escribir pruebas unitarias. Puede probar muchas cosas en Cocoa que podrían ser más difíciles en otros marcos. Por ejemplo, con el código de la interfaz de usuario, generalmente puede verificar que las cosas estén conectadas como deberían y confiar en que funcionarán cuando se usen. Y puede configurar métodos de delegado de estado e invocar fácilmente para probarlos.

Tampoco tiene visibilidad de método público versus protegido versus privado que se interpone en el camino de escribir pruebas para sus internos.

Chris Hanson
fuente
¿Qué marcos de prueba me recomiendan?
melfar
13
Xcode incluye OCUnit, un marco de prueba de unidad de Objective-C y soporte para ejecutar paquetes de pruebas de unidad como parte de su proceso de construcción.
Chris Hanson el
55

Regla de oro: ¡Si tú, allocentonces tú release!

ACTUALIZACIÓN: a menos que esté utilizando ARC

logancautrell
fuente
26
Además, si copy, mutableCopy, newo retain.
Sven
54

No escriba Objective-C como si fuera Java / C # / C ++ / etc.

Una vez vi a un equipo acostumbrado a escribir aplicaciones web Java EE que intentaban escribir una aplicación de escritorio Cocoa. Como si fuera una aplicación web Java EE. Había un montón de AbstractFooFactory y FooFactory e IFoo y Foo volando cuando todo lo que realmente necesitaban era una clase Foo y posiblemente un protocolo Fooable.

Parte de garantizar que no hagas esto es comprender realmente las diferencias en el idioma. Por ejemplo, no necesita la fábrica abstracta y las clases de fábrica anteriores porque los métodos de clase Objective-C se envían tan dinámicamente como los métodos de instancia, y pueden anularse en subclases.

Chris Hanson
fuente
10
Como desarrollador de Java que ha escrito una fábrica abstracta en Objective-C, esto me parece intrigante. ¿Te importaría explicar un poco más cómo funciona esto, tal vez con un ejemplo?
teabot
2
¿Todavía crees que no necesitamos clases abstractas de fábrica después de todo el tiempo transcurrido desde que publicaste esta respuesta?
kirk.burleson
50

Asegúrese de marcar la página Debugging Magic . Esta debería ser tu primera parada cuando te golpees la cabeza contra la pared mientras intentas encontrar la fuente de un error de cacao.

Por ejemplo, le dirá cómo encontrar el método en el que asignó la memoria por primera vez que luego causa bloqueos (como durante la finalización de la aplicación).

mj1531
fuente
1
Ahora hay una versión específica de iOS de la página Debugging Magic .
Jeethu
38

Trate de evitar lo que ahora he decidido llamar adicción a la categoría de Newbie. Cuando los recién llegados a Objective-C descubren categorías, a menudo se vuelven locos, agregando pequeñas categorías útiles a todas las clases existentes ( "¿Qué? ¡Puedo agregar un método para convertir un número a números romanos en NSNumber rock on!" ).

No hagas esto.

Su código será más portátil y más fácil de entender sin docenas de pequeños métodos de categoría distribuidos sobre dos docenas de clases básicas.

La mayoría de las veces, cuando realmente cree que necesita un método de categoría para ayudar a simplificar el código, encontrará que nunca termina reutilizando el método.

También hay otros peligros, a menos que esté espaciando los nombres de sus métodos de categoría (¿y quién además del ddribin completamente loco es?) Existe la posibilidad de que Apple, o un complemento, o algo más que se ejecute en su espacio de direcciones también definan la misma categoría método con el mismo nombre con un efecto secundario ligeramente diferente ...

OKAY. Ahora que has sido advertido, ignora el "no hagas esta parte". Pero ejerza una moderación extrema.

schwa
fuente
Me gusta su respuesta, mi consejo es que no use una categoría para almacenar el código de utilidad a menos que esté a punto de replicar algún código en más de un lugar y el código claramente pertenece a la clase que está a punto de categorizar ...
Kendall Helmstetter Gelner
Me gustaría canalizar y expresar mi apoyo a los métodos de categoría de espacios de nombres. Simplemente parece lo correcto.
Michael Buckley
+1 solo para los números romanos. ¡Lo haría totalmente!
Brian Postow
14
Contrapunto: Durante el último año y medio, he seguido exactamente la política opuesta: "Si puede implementarse en una categoría, hágalo". Como resultado, mi código es mucho más conciso, más expresivo y más fácil de leer que el código de muestra detallado que proporciona Apple. He perdido un total de aproximadamente 10 minutos por un conflicto de espacio de nombres, y probablemente he ganado meses hombre por las eficiencias que he creado para mí. A cada uno lo suyo, pero adopté esta política conociendo los riesgos, y estoy extremadamente contento de haberlo hecho.
cduhn
77
No estoy de acuerdo Si va a ser una función y se aplica a un objeto Foundation, y puede pensar en un buen nombre, péguelo en una categoría. Su código será más legible. Creo que realmente el punto más destacado aquí es: hacer todo con moderación.
mxcl
37

Resiste subclasificar el mundo. En Cocoa se hace mucho a través de la delegación y el uso del tiempo de ejecución subyacente que en otros marcos se realiza a través de la subclasificación.

Por ejemplo, en Java usa *Listenermuchas instancias de subclases anónimas y en .NET usa mucho sus EventArgssubclases. En Cocoa, tampoco lo haces; en su lugar, se utiliza la acción objetivo.

Chris Hanson
fuente
66
También conocido como "Composición sobre herencia".
Andrew Ebling
37

Ordenar cadenas como el usuario quiera

Cuando ordena las cadenas para presentarlas al usuario, no debe usar el compare:método simple . En su lugar, siempre debe usar métodos de comparación localizados como localizedCompare:o localizedCaseInsensitiveCompare:.

Para obtener más detalles, consulte Búsqueda, comparación y clasificación de cadenas .

mmalc
fuente
31

Propiedades declaradas

Por lo general, debe usar la característica Propiedades declaradas de Objective-C 2.0 para todas sus propiedades. Si no son públicos, agréguelos en una extensión de clase. El uso de propiedades declaradas hace que la semántica de administración de memoria sea clara de inmediato, y le facilita verificar su método dealloc; si agrupa sus declaraciones de propiedad, puede escanearlas rápidamente y compararlas con la implementación de su método dealloc.

Debe pensar mucho antes de no marcar las propiedades como 'no atómicas'. Como señala The Objective C Programming Language Guide , las propiedades son atómicas por defecto y generan una sobrecarga considerable. Además, simplemente hacer que todas sus propiedades sean atómicas no hace que su aplicación sea segura para subprocesos. También tenga en cuenta, por supuesto, que si no especifica 'no atómico' e implementa sus propios métodos de acceso (en lugar de sintetizarlos), debe implementarlos de manera atómica.

mmalc
fuente
26

Piensa en valores nulos

Como señala esta pregunta , los mensajes a nilson válidos en Objective-C. Si bien esto es con frecuencia una ventaja, lo que lleva a un código más limpio y natural, la característica ocasionalmente puede generar errores peculiares y difíciles de rastrear si obtiene un nilvalor cuando no lo esperaba.

mmalc
fuente
Tengo esto: #define SXRelease(o); o = nily lo mismo para CFReleasey free. Esto simplifica todo.
23

Simple pero a menudo olvidado. De acuerdo a las especificaciones:

En general, los métodos en diferentes clases que tienen el mismo selector (el mismo nombre) también deben compartir los mismos tipos de retorno y argumento. El compilador impone esta restricción para permitir el enlace dinámico.

en cuyo caso todos los selectores con el mismo nombre, incluso si están en clases diferentes , se considerará que tienen tipos idénticos de retorno / argumento. Aquí hay un ejemplo simple.

@interface FooInt:NSObject{}
-(int) print;
@end

@implementation FooInt
-(int) print{
    return 5;
}
@end

@interface FooFloat:NSObject{}
-(float) print;
@end

@implementation FooFloat
-(float) print{
    return 3.3;
}
@end

int main (int argc, const char * argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];    
    id f1=[[FooFloat alloc]init];
    //prints 0, runtime considers [f1 print] to return int, as f1's type is "id" and FooInt precedes FooBar
    NSLog(@"%f",[f1 print]);

    FooFloat* f2=[[FooFloat alloc]init];
    //prints 3.3 expectedly as the static type is FooFloat
    NSLog(@"%f",[f2 print]);

    [f1 release];
    [f2 release]
    [pool drain];

    return 0;
}   
Comptrol
fuente
Es fácil de olvidar. Sin embargo importante
Brock Woolf
3
Esto es solo una preocupación cuando se abstiene de escribir estática. Si el compilador conoce el tipo, los tipos de argumento y retorno pueden diferir sin problemas. Personalmente, creo que esto no suele ser un problema. Apple también tiene muchos métodos que tienen el mismo nombre pero difieren en los tipos de retorno. Finalmente, hay una bandera del compilador para advertirle en casos ambiguos.
Nikolai Ruhe
Si seguimos las pautas de la convención de nomenclatura de Apple, esta situación no sucederá :)
Eonil
22

Si usa Leopard (Mac OS X 10.5) o posterior, puede usar la aplicación Instrumentos para buscar y rastrear pérdidas de memoria. Después de compilar su programa en Xcode, seleccione Ejecutar> Comenzar con la herramienta de rendimiento> Fugas.

Incluso si su aplicación no muestra ninguna fuga, es posible que tenga objetos alrededor demasiado tiempo. En Instrumentos, puede usar el instrumento ObjectAlloc para esto. Seleccione el instrumento ObjectAlloc en su documento Instrumentos y muestre los detalles del instrumento (si aún no se muestra) seleccionando Ver> Detalle (debe tener una marca de verificación al lado). En "Duración de la asignación" en los detalles de ObjectAlloc, asegúrese de elegir el botón de opción junto a "Creado y aún vivo".

Ahora, cuando deje de grabar su aplicación, al seleccionar la herramienta ObjectAlloc le mostrará cuántas referencias hay para cada objeto vivo en su aplicación en la columna "# Net". Asegúrese de no solo mirar sus propias clases, sino también las clases de los objetos de nivel superior de sus archivos NIB. Por ejemplo, si no tiene ventanas en la pantalla y ve referencias a una NSWindow que aún está viva, es posible que no la haya publicado en su código.

mj1531
fuente
21

Limpiar en Dealloc.

Esta es una de las cosas más fáciles de olvidar, especialmente. cuando codifica a 150 mph. Siempre, siempre, siempre limpia tus atributos / variables miembro en dealloc.

Me gusta usar los atributos de Objc 2, con la nueva notación de puntos, por lo que esto hace que la limpieza sea indolora. A menudo tan simple como:

- (void)dealloc
{
    self.someAttribute = NULL;
    [super dealloc];
}

Esto se encargará de la publicación y establecerá el atributo en NULL (lo que considero programación defensiva, en caso de que otro método más abajo en dealloc acceda nuevamente a la variable miembro, raro pero podría suceder).

Con GC activado en 10.5, esto ya no es necesario, pero es posible que aún necesite limpiar otros recursos que cree, puede hacerlo en el método de finalización.

schwa
fuente
12
En general, usted debe no utilizar métodos de acceso en dealloc (o init).
mmalc
1
Además de las razones de rendimiento (los accesos son un poco más lentos que el acceso directo), ¿por qué no debo usar los accesos en dealloc o init?
schwa
1
(a) Las razones de rendimiento son una razón perfectamente adecuada en sí mismas (especialmente si sus accesores son atómicos). (b) Debe evitar cualquier efecto secundario que puedan tener los accesorios. Esto último es particularmente un problema si su clase puede estar subclaseada.
mmalc
3
Notaré que si está ejecutando en el tiempo de ejecución moderno con ivars sintetizados, debe usar los accesos en dealloc. Una gran cantidad de código de tiempo de ejecución moderno es GC, pero no todo.
Louis Gerbarg
1
Puede encontrar una vista más extensa sobre si usar o no métodos / propiedades de acceso en -inity -deallocmétodos aquí: mikeash.com/?page=pyblog/…
Johan Kool
17

Todos estos comentarios son geniales, pero estoy realmente sorprendido de que nadie haya mencionado la Guía de estilo Objective-C de Google que se publicó hace un tiempo. Creo que han hecho un trabajo muy completo.

slf
fuente
77
Hmm, el primer ejemplo ya está lleno de mentiras. Nunca documente modismos idiomáticos. Si encontrara ese tipo de comentarios en un archivo de encabezado, no me molestaría en seguir leyendo.
Stephan Eggermont
55
¡Oh mis ojos! No puedo creer lo que vi.
Eonil
13

No olvide que NSWindowController y NSViewController liberarán los objetos de nivel superior de los archivos NIB que gobiernan.

Si carga manualmente un archivo NIB, usted es responsable de liberar los objetos de nivel superior de NIB cuando haya terminado con ellos.

mj1531
fuente
12

Uno bastante obvio para un principiante: utilice la función de sangría automática de Xcode para su código. Incluso si está copiando / pegando desde otra fuente, una vez que haya pegado el código, puede seleccionar todo el bloque de código, hacer clic derecho sobre él y luego elegir la opción para volver a sangrar todo dentro de ese bloque.

Xcode realmente analizará esa sección y aplicará sangría en función de corchetes, bucles, etc. Es mucho más eficiente que presionar la barra espaciadora o la tecla de tabulación para cada línea.

iWasRobbed
fuente
Incluso puede configurar Tab para sangrar y luego hacer Cmd-A y Tab.
Plumenator
10

Sé que pasé por alto esto cuando me metí por primera vez en la programación de Cocoa.

Asegúrese de comprender las responsabilidades de administración de memoria con respecto a los archivos NIB. Usted es responsable de liberar los objetos de nivel superior en cualquier archivo NIB que cargue. Lea la documentación de Apple sobre el tema.

mj1531
fuente
66
Esto no es verdad. Si usted es responsable o no de liberar objetos de nivel superior depende de la clase de la que herede y de la plataforma que esté utilizando. Ver developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/… entre otros.
mmalc
10

Encienda todas las advertencias de GCC, luego apague las que regularmente causan los encabezados de Apple para reducir el ruido.

También ejecute el análisis estático de Clang con frecuencia; puede habilitarlo para todas las compilaciones a través de la configuración de compilación "Ejecutar analizador estático".

Escriba pruebas unitarias y ejecútelas con cada compilación.

oefe
fuente
Y, si puede, active "Tratar las advertencias como errores". No permita que exista ninguna advertencia.
Peter Hosey
2
Un script útil para configurar su proyecto con advertencias recomendadas está disponible aquí: rentzsch.tumblr.com/post/237349423/hoseyifyxcodewarnings-scpt
Johan Kool el
10

Variables y propiedades

1 / Mantener sus encabezados limpios, ocultando la implementación
No incluya variables de instancia en su encabezado. Variables privadas puestas en continuación de clase como propiedades. Las variables públicas se declaran como propiedades públicas en su encabezado. Si solo debe leerse, declararlo como de solo lectura y sobrescribirlo como readwrite en la continuación de clase. Básicamente no estoy usando variables en absoluto, solo propiedades.

2 / Dé a sus propiedades un nombre de variable no predeterminado, por ejemplo:


@synthesize property = property_;

Motivo 1: detectará los errores causados ​​por olvidar "self". al asignar la propiedad. Razón 2: De mis experimentos, el Analizador de fugas en los instrumentos tiene problemas para detectar propiedades con fugas con nombre predeterminado.

3 / Nunca use retener o liberar directamente en propiedades (o solo en situaciones muy excepcionales). En su dealloc solo les asigna un cero. Las propiedades de retención están destinadas a manejar la retención / liberación por sí mismas. Nunca se sabe si un setter no está, por ejemplo, agregando o quitando observadores. Debe usar la variable directamente solo dentro de su setter y getter.

Puntos de vista

1 / Ponga cada definición de vista en un xib, si puede (la excepción suele ser el contenido dinámico y la configuración de la capa). Ahorra tiempo (es más fácil que escribir código), es fácil de cambiar y mantiene su código limpio.

2 / No intente optimizar las vistas disminuyendo el número de vistas. No cree UIImageView en su código en lugar de xib solo porque desea agregarle subvistas. Utilice UIImageView como fondo en su lugar. El marco de vista puede manejar cientos de vistas sin problemas.

3 / IBOutlets no tiene que ser siempre retenido (o fuerte). Tenga en cuenta que la mayoría de sus IBOutlets son parte de su jerarquía de vistas y, por lo tanto, se retienen implícitamente.

4 / Libere todos los IBOutlets en viewDidUnload

5 / Llame a viewDidUnload desde su método dealloc. No se llama implícitamente.

Memoria

1 / Objetos de liberación automática cuando los crea. Muchos errores son causados ​​al mover su llamada de liberación a una rama if-else o después de una declaración de devolución. La liberación en lugar de la liberación automática se debe usar solo en situaciones excepcionales, por ejemplo, cuando está esperando un runloop y no desea que su objeto se libere automáticamente demasiado pronto.

2 / Incluso si está utilizando el recuento de referencia automático, debe comprender perfectamente cómo funcionan los métodos de liberación retenida. El uso de retención de liberación manual no es más complicado que ARC, en ambos casos tiene que preocuparse por las fugas y los ciclos de retención. Considere la posibilidad de utilizar la liberación retenida manualmente en grandes proyectos o jerarquías de objetos complicadas.

Comentarios

1 / Haga su código autodocumentado. Cada nombre de variable y nombre de método debe indicar lo que está haciendo. Si el código está escrito correctamente (necesita mucha práctica en esto), no necesitará ningún comentario de código (no es lo mismo que los comentarios de documentación). Los algoritmos pueden ser complicados, pero el código siempre debe ser simple.

2 / A veces, necesitarás un comentario. Por lo general, para describir un comportamiento o pirateo de código no aparente. Si siente que tiene que escribir un comentario, primero intente reescribir el código para que sea más simple y sin la necesidad de comentarios.

Sangría

1 / No aumente demasiado la sangría. La mayor parte del código de su método debe tener sangría en el nivel del método. Los bloques anidados (si, para etc.) disminuyen la legibilidad. Si tiene tres bloques anidados, debe intentar colocar los bloques internos en un método separado. Nunca se deben usar cuatro o más bloques anidados. Si la mayor parte del código de su método está dentro de un if, niegue la condición if, por ejemplo:


if (self) {
   //... long initialization code ...
}

return self;

if (!self) {
   return nil;
}

//... long initialization code ...

return self;

Comprender el código C, principalmente estructuras C

Tenga en cuenta que Obj-C es solo una capa ligera de OOP sobre lenguaje C. Debe comprender cómo funcionan las estructuras de código básicas en C (enumeraciones, estructuras, matrices, punteros, etc.). Ejemplo:


view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height + 20);

es lo mismo que:


CGRect frame = view.frame;
frame.size.height += 20;
view.frame = frame;

Y muchos más

Mantenga su propio documento de estándares de codificación y actualícelo con frecuencia. Intenta aprender de tus errores. Comprenda por qué se creó un error e intente evitarlo utilizando estándares de codificación.

Nuestros estándares de codificación tienen actualmente unas 20 páginas, una combinación de estándares de codificación Java, estándares Google Obj-C / C ++ y nuestras propias adiciones. Documente su código, use sangría estándar estándar, espacios en blanco y líneas en blanco en los lugares correctos, etc.

Sulthan
fuente
9

Se más funcional .

Objective-C es un lenguaje orientado a objetos, pero Cocoa Framework es consciente del estilo funcional y, en muchos casos, está diseñado en un estilo funcional.

  1. Hay separación de mutabilidad. Use clases inmutables como primarias y objetos mutables como secundarios. Por ejemplo, use NSArray principalmente y use NSMutableArray solo cuando lo necesite.

  2. Hay funciones puras. No tantos, comprar muchas de las API de framework están diseñadas como una función pura. Mira las funciones como CGRectMake()o CGAffineTransformMake(). Obviamente, la forma del puntero parece más eficiente. Sin embargo, el argumento indirecto con punteros no puede ofrecer efectos secundarios libres. Diseñe estructuras lo más puramente posible. Separe incluso los objetos de estado. Use en -copylugar de -retaincuando pasa un valor a otro objeto. Porque el estado compartido puede influir en la mutación del valor en otro objeto en silencio. Por lo tanto, no puede estar libre de efectos secundarios. Si tiene un valor externo desde un objeto, cópielo. Por lo tanto, también es importante diseñar un estado compartido lo más mínimo posible.

Sin embargo, no tengas miedo de usar funciones impuras también.

  1. Hay una evaluación perezosa. Ver algo como -[UIViewController view]propiedad. La vista no se creará cuando se cree el objeto. Se creará cuando la persona que llama lea la viewpropiedad por primera vez. UIImageno se cargará hasta que se dibuje realmente. Hay muchas implementaciones como este diseño. Este tipo de diseños son muy útiles para la gestión de recursos, pero si no conoce el concepto de evaluación perezosa, no es fácil comprender su comportamiento.

  2. Hay cierre. Use bloques C tanto como sea posible. Esto simplificará mucho tu vida. Pero lea una vez más sobre la administración de memoria de bloque antes de usarlo.

  3. Hay GC semiautomático. NSAutoreleasePool. Uso -autoreleaseprimario. Use el manual -retain/-releasesecundario cuando realmente lo necesite. (ej .: optimización de memoria, eliminación explícita de recursos)

Eonil
fuente
2
En cuanto a 3) propondré el enfoque opuesto: ¡use retención / liberación manual siempre que sea posible! Quién sabe cómo se usará este código, y si se usará en un ciclo cerrado, puede explotar su uso de memoria innecesariamente.
Eiko
@Eiko Eso es solo una optimización prematura , no puede ser una guía general.
Eonil
1
Creo que es más una cuestión de diseño, especialmente cuando se trabaja en clases de modelos. Considero el crecimiento de la memoria como un efecto secundario, y eso no es lo que quiero que aparezca a menudo. Peor aún, otro desarrollador que usa mi código no tiene más remedio que envolver llamadas costosas en grupos de liberación automática (si es posible, mis objetos podrían enviarse a algún otro código de biblioteca). Y esos problemas son difíciles de diagnosticar más tarde, pero es barato evitarlos en primer lugar. Si copia / libera automáticamente objetos que pasaron, podría perderse si son mucho más grandes de lo que esperaba. Sin embargo, estoy más relajado con el código GUI.
Eiko
@Eiko Estoy de acuerdo autoreleaseque mantendrá la memoria por más tiempo en general, y el manual retain/releasepuede reducir el consumo de memoria en el caso. Sin embargo, debe ser una guía para la optimización de casos especiales (¡incluso si se siente siempre!), No puede ser la razón para generalizar la optimización prematura como práctica . Y de hecho, su sugerencia no es opuesta a la mía. Lo mencioné como caso de necesidad realmente :)
Eonil