Quali sono le migliori pratiche che usi quando scrivi Objective-C e Cocoa? [chiuso]


346

Conosco l' HIG (che è abbastanza utile!), Ma quali pratiche di programmazione usi quando scrivi Objective-C, e più specificamente quando usi Cocoa (o CocoaTouch).


vedi questo post sul blog, molto bello. ironwolf.dangerousgames.com/blog/archives/913
user392412

Risposte:


398

Ho iniziato a fare alcune cose che non credo siano standard:

1) Con l'avvento delle proprietà, non uso più "_" per aggiungere il prefisso alle variabili di classe "private". Dopotutto, se una variabile è accessibile da altre classi non dovrebbe esserci una proprietà per essa? Non ho sempre apprezzato il prefisso "_" per rendere il codice più brutto, e ora posso lasciarlo fuori.

2) Parlando di cose private, preferisco inserire le definizioni dei metodi privati ​​all'interno del file .m in un'estensione di classe in questo modo:

#import "MyClass.h"

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

@implementation MyClass

Perché ingombrare il file .h con cose che non dovrebbero interessare agli estranei? Empty () funziona per le categorie private nel file .m e genera avvisi di compilazione se non si implementano i metodi dichiarati.

3) Ho preso a mettere dealloc nella parte superiore del file .m, proprio sotto le direttive @synthesize. Ciò che dovresti collocare non dovrebbe essere in cima all'elenco delle cose a cui vuoi pensare in una classe? Ciò è particolarmente vero in un ambiente come l'iPhone.

3.5) Nelle celle della tabella, rendere opaco ogni elemento (inclusa la cella stessa) per le prestazioni. Ciò significa impostare il colore di sfondo appropriato in tutto.

3.6) Quando si utilizza un NSURLConnection, di norma si consiglia di implementare il metodo delegato:

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

Trovo che la maggior parte delle chiamate Web siano molto singolari ed è più un'eccezione che la regola in cui vorrai che le risposte vengano memorizzate nella cache, soprattutto per le chiamate ai servizi web. L'implementazione del metodo come mostrato disabilita la memorizzazione nella cache delle risposte.

Interessanti anche alcuni buoni suggerimenti specifici per iPhone di Joseph Mattiello (ricevuti in una mailing list per iPhone). Ce ne sono altri, ma questi sono stati i più generalmente utili che ho pensato (nota che alcuni bit sono stati leggermente modificati dall'originale per includere i dettagli offerti nelle risposte):

4) Utilizzare la doppia precisione solo se necessario, ad esempio quando si lavora con CoreLocation. Assicurati di terminare le costanti in 'f' per fare in modo che gcc le memorizzi come float.

float val = someFloat * 2.2f;

Questo è per lo più importante quando someFloatpotrebbe effettivamente essere un doppio, non è necessaria la matematica in modalità mista, poiché stai perdendo precisione in 'val' sullo spazio di archiviazione. Mentre i numeri a virgola mobile sono supportati nell'hardware su iPhone, potrebbe essere necessario più tempo per eseguire l'aritmetica a doppia precisione rispetto alla precisione singola. Riferimenti:

Sui telefoni più vecchi si suppone che i calcoli funzionino alla stessa velocità ma nei registri si possono avere più componenti a precisione singola che doppi, quindi per molti calcoli la precisione singola finirà per essere più veloce.

5) Imposta le tue proprietà come nonatomic. Sono atomicdi default e al momento della sintesi, il codice semaforo verrà creato per prevenire problemi multi-threading. Il 99% di voi probabilmente non deve preoccuparsi di questo e il codice è molto meno gonfio e più efficiente in termini di memoria quando impostato su non anatomico.

6) SQLite può essere un modo molto, molto veloce per memorizzare nella cache set di dati di grandi dimensioni. Un'applicazione mappa, ad esempio, può memorizzare nella cache i suoi riquadri in file SQLite. La parte più costosa è l'I / O del disco. Evita molte piccole scritture inviando BEGIN;e COMMIT;tra blocchi di grandi dimensioni. Ad esempio, utilizziamo un timer di 2 secondi che si reimposta su ogni nuovo invio. Quando scade, inviamo COMMIT; , che fa sì che tutte le tue scritture vadano in un grosso blocco. SQLite archivia i dati delle transazioni su disco e, facendo questo, il wrapping Begin / End evita la creazione di molti file delle transazioni, raggruppando tutte le transazioni in un unico file.

Inoltre, SQL bloccherà la tua GUI se si trova sul tuo thread principale. Se hai una query molto lunga, è una buona idea archiviare le tue query come oggetti statici ed eseguire il tuo SQL su un thread separato. Assicurati di avvolgere tutto ciò che modifica il database per le stringhe di query in @synchronize() {}blocchi. Per domande brevi, lascia semplicemente le cose sul thread principale per una maggiore comodità.

Altri suggerimenti per l'ottimizzazione di SQLite sono qui, sebbene il documento appaia obsoleto molti dei punti sono probabilmente ancora validi;

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


3
Bel suggerimento sulla doppia aritmetica.
Adam Ernst,

8
Le estensioni di classe sono ora il metodo preferito per i metodi privati: developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/…
Casebash

9
I vostri consigli su raddoppia su iPhone non è aggiornato stackoverflow.com/questions/1622729/...
Casebash

3
Non obsoleto; completamente sbagliato: l'iPhone originale supportato galleggia e raddoppia nell'hardware approssimativamente alla stessa velocità. SQLite inoltre non mantiene le transazioni in memoria; sono registrati su disco. Solo le query lunghe bloccano l'interfaccia utente; è meno complicato eseguire tutto nel thread principale e utilizzare query più veloci.
tc.

1
@tc: ho corretto l'elemento SQL relativo alle transazioni, nota che io stesso non ho scritto quegli ultimi quattro o più elementi. Ho anche chiarito che la parte relativa allo spostamento delle query in background era solo per query molto lunghe (a volte non è possibile accorciarle). Ma chiamare l'intera cosa "sbagliata" a causa di alcuni punti è che mi sento piuttosto estremo. Inoltre, la risposta di cui sopra ha già dichiarato: "Sui telefoni più vecchi si suppone che i calcoli funzionino alla stessa velocità" ma si noti la parte relativa al maggior numero di registri di precisione singoli che li rende ancora preferibili.
Kendall Helmstetter Gelner,

109

Non utilizzare stringhe sconosciute come stringhe di formato

Quando metodi o funzioni accettano un argomento stringa di formato, è necessario assicurarsi di avere il controllo sul contenuto della stringa di formato.

Ad esempio, quando si registrano le stringhe, si è tentati di passare la variabile stringa come unico argomento a NSLog:

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

Il problema è che la stringa può contenere caratteri interpretati come stringhe di formato. Ciò può causare output errati, arresti anomali e problemi di sicurezza. Invece, è necessario sostituire la variabile stringa in una stringa di formato:

    NSLog(@"%@", aString);

4
Sono stato morso da questo prima.
Adam Ernst,

Questo è un buon consiglio per qualsiasi linguaggio di programmazione
Tom Fobear,

107

Usa convenzioni e terminologia standard di denominazione e formattazione del cacao piuttosto che qualsiasi cosa a cui sei abituato da un altro ambiente. Ci sono molti sviluppatori di Cocoa là fuori e quando un altro inizia a lavorare con il tuo codice, sarà molto più accessibile se sembra e si sente simile ad altri codici Cocoa.

Esempi di cosa fare e cosa non fare:

  • Non dichiarare id m_something;nell'interfaccia di un oggetto e chiamarlo una variabile o un campo membro ; usa somethingo _somethingper il suo nome e chiamalo una variabile di istanza .
  • Non nominare un getter -getSomething; il nome proprio di cacao è giusto -something.
  • Non nominare un setter -something:; dovrebbe essere-setSomething:
  • Il nome del metodo è intervallato dagli argomenti e include due punti; non lo -[NSObject performSelector:withObject:]è NSObject::performSelector.
  • Utilizzare gli intercap (CamelCase) nei nomi dei metodi, nei parametri, nelle variabili, nei nomi delle classi, ecc. Anziché nelle barre di sottolineatura (caratteri di sottolineatura).
  • I nomi delle classi iniziano con lettere maiuscole, variabili e metodi con lettere minuscole.

Qualunque cosa tu faccia, non usare la notazione ungherese in stile Win16 / Win32. Perfino Microsoft ha rinunciato a questo con il passaggio alla piattaforma .NET.


5
Direi, non usare setSomething: / qualcosa - usa invece le proprietà. A questo punto ci sono poche persone che hanno davvero bisogno di prendere di mira Tiger (l'unica ragione per non usare le proprietà)
Kendall Helmstetter Gelner,

18
Le proprietà generano comunque metodi accessor per te e gli attributi getter = / setter = sulla proprietà ti consentono di specificare i nomi dei metodi. Inoltre, puoi usare la sintassi [pippo qualcosa] invece della sintassi pippo.something con proprietà. Quindi la denominazione degli accessori è ancora rilevante.
Chris Hanson,

3
Questo è un ottimo riferimento per qualcuno che viene dal C ++, dove ho fatto la maggior parte delle cose che sconsigli.
Clinton Blackmore,

4
Un setter non dovrebbe causare il salvataggio di qualcosa nel database. C'è un motivo per cui Core Data ha un metodo -save: su NSManagedObjectContext, piuttosto che i setter generano aggiornamenti immediati.
Chris Hanson,

2
Dubito che non fosse un'opzione, ma potrebbe aver richiesto di rivisitare l'architettura della tua app. (Per essere chiari: non sto dicendo "Avresti dovuto usare Core Data." Sto dicendo "I setter non dovrebbero salvare nel database.") Avere un contesto per gestire un grafico a oggetti, piuttosto che salvare singoli oggetti in esso , è praticamente sempre sia possibile sia una soluzione migliore.
Chris Hanson,

106

IBOutlets

Storicamente, la gestione della memoria delle prese è stata scarsa. La migliore pratica corrente è dichiarare gli sbocchi come proprietà:

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

L'uso delle proprietà rende chiara la semantica della gestione della memoria; fornisce anche un modello coerente se si utilizza la sintesi delle variabili di istanza.


1
il caricamento del pennino non lo trattenerebbe due volte? (una volta in pennino, secondo per incarico alla proprietà). Devo rilasciare quelli in dealloc?
Kornel,

6
È necessario azzerare le prese in viewDidUnload (iPhone OS 3.0+) o in un setView personalizzato: metodo per evitare perdite. Ovviamente dovresti rilasciare anche in dealloc.
Frank Szczerba,

2
Tieni presente che non tutti sono d'accordo con questo stile: weblog.bignerdranch.com/?p=95
Michael,

Questo è il modo in cui anche Apple fa le cose. "Inizio dello sviluppo di iPhone 3" menziona anche questo cambiamento rispetto alle versioni precedenti.
ustun

Ho menzionato questo in un altro commento, ma avrei dovuto inserirlo qui: una volta che la sintesi dinamica dell'ivar inizia a verificarsi per le app iOS (se / quando?), Sarai felice di aver messo IBOutlet sulla proprietà rispetto all'ivar!
Joe D'Andrea,

97

Utilizzare l'analizzatore statico LLVM / Clang

NOTA: Sotto Xcode 4 questo è ora incorporato nell'IDE.

Si utilizza il Clang Static Analyzer a - non sorprende - analizzare il C e il codice Objective-C (non C ++ ancora) su Mac OS X 10.5. È banale da installare e utilizzare:

  1. Scarica l'ultima versione da questa pagina .
  2. Dalla riga di comando, cdalla directory del progetto.
  3. Eseguire scan-build -k -V xcodebuild.

(Ci sono alcuni vincoli aggiuntivi ecc., In particolare è necessario analizzare un progetto nella sua configurazione "Debug" - vedere http://clang.llvm.org/StaticAnalysisUsage.html per i dettagli - ma è più o meno a cosa si riduce.)

L'analizzatore quindi produce una serie di pagine Web che mostrano la probabile gestione della memoria e altri problemi di base che il compilatore non è in grado di rilevare.


1
Ho avuto qualche problema a farlo funzionare finché non ho seguito queste istruzioni: oiledmachine.com/posts/2009/01/06/…
bbrown,

15
In XCode 3.2.1 su Snow Leopard, è già integrato. Puoi eseguirlo manualmente, usando Esegui -> Crea e analizza , oppure puoi abilitarlo per tutte le build tramite l'impostazione di costruzione "Esegui analizzatore statico". Si noti che questo strumento attualmente supporta solo C e Objective-C, ma non C ++ / Objective-C ++.
oefe,

94

Questo è sottile ma utile. Se ti stai passando come delegato a un altro oggetto, ripristina il delegato dell'oggetto prima di te dealloc.

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

In questo modo ti stai assicurando che non verranno inviati più metodi delegati. Mentre stai per deallocsparire nell'etere, vuoi assicurarti che nulla possa inviarti altri messaggi per caso. Ricorda self.someObject potrebbe essere trattenuto da un altro oggetto (potrebbe essere un singleton o nel pool di rilascio automatico o qualsiasi altra cosa) e fino a quando non gli dici "smetti di inviarmi messaggi!", Pensa che il tuo oggetto sia quasi in procinto di essere distribuito è un gioco equo.

Entrare in questa abitudine ti salverà da molti strani incidenti che sono un dolore per il debug.

Lo stesso principio si applica all'osservazione dei valori chiave e anche alle notifiche NS.

Modificare:

Ancora più difensivo, cambia:

self.someObject.delegate = NULL;

in:

if (self.someObject.delegate == self)
    self.someObject.delegate = NULL;

8
Non c'è nulla di sottile in questo, la documentazione dice chiaramente che è necessario farlo. Da 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,

È meglio usare zero invece di NULL, perché NULL non libererà memoria.
Naveen Shan,

@NaveenShan nil == NULL. Sono esattamente gli stessi tranne che nilè un ided NULLè un void *. La tua affermazione non è vera.

@WTP yep, nil == NULL, ma usare nil è chiaramente il modo preferito, se guardi i frammenti di codice di esempio apple, usano nil ovunque e, come hai detto, nil è un id, che lo rende preferibile al vuoto * , nei casi in cui invii ID, ovvero.
Ahti,

1
@Ahti esattamente, e Nil(maiuscolo) è di tipo Class*. Anche se sono tutti uguali, l'uso di quello sbagliato può introdurre piccoli bug cattivi, specialmente in Objective-C ++.

86

@kendell

Invece di:

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

Uso:

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

Novità in Objective-C 2.0.

Le estensioni di classe sono descritte nel riferimento Apple Objective-C 2.0.

"Le estensioni di classe ti consentono di dichiarare API richieste aggiuntive per una classe in posizioni diverse dal blocco @interface della classe primaria"

Quindi fanno parte della classe reale e NON una categoria (privata) in aggiunta alla classe. Differenza sottile ma importante.


Potresti farlo, ma mi piace etichettarlo esplicitamente come una sezione "privata" (più documentazione che funzionale) anche se ovviamente è già abbastanza ovvio dal fatto che si trova nel file .m ...
Kendall Helmstetter Gelner

2
Tranne che non v'è una differenza tra le categorie private e le estensioni di classe: "estensioni di classe permettono di dichiarare ulteriore API necessarie per una classe in posizioni diverse all'interno del blocco di classe @interface primario, come illustrato nel seguente esempio:" Vedi link a modifica.
schwa,

Sono d'accordo che c'è una differenza in cui il compilatore ti avvertirà quando non hai implementato i metodi CE - ma non trovo questo aspetto molto importante quando tutti i metodi sono nello stesso file e tutti privati. Preferisco ancora l'aspetto della manutenibilità della marcatura privata del blocco di riferimento in avanti
Kendall Helmstetter Gelner,

3
Davvero non vedo (Privato) come più gestibile di (). Se sei così preoccupato, una buona dose di commenti potrebbe aiutare. Ma ovviamente vivi e lascia vivere. YMMV ecc.
schwa,

17
C'è un vantaggio piuttosto importante da usare ()invece di (Private)(o qualche altro nome di categoria): puoi dichiarare le proprietà come readwrite mentre al pubblico sono solo di sola lettura. :)
Pascal,

75

Evitare il rilascio automatico

Poiché in genere (1) non si ha il controllo diretto sulla loro durata, gli oggetti rilasciati automaticamente possono persistere per un tempo relativamente lungo e aumentare inutilmente l'impronta di memoria dell'applicazione. Mentre sul desktop questo può avere poche conseguenze, su piattaforme più vincolate questo può essere un problema significativo. Su tutte le piattaforme, quindi, e specialmente su piattaforme più vincolate, si considera la migliore prassi per evitare l'uso di metodi che porterebbero a oggetti rilasciati automaticamente e si consiglia invece di utilizzare il modello alloc / init.

Pertanto, anziché:

aVariable = [AClass convenienceMethod];

dove possibile, dovresti invece usare:

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

Quando si scrivono i propri metodi che restituiscono un oggetto appena creato, è possibile sfruttare la convenzione di denominazione di Cocoa per segnalare al destinatario che deve essere rilasciato anteponendo il nome del metodo con "nuovo".

Pertanto, anziché:

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

potresti scrivere:

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

Poiché il nome del metodo inizia con "nuovo", i consumatori dell'API sanno di essere responsabili del rilascio dell'oggetto ricevuto (vedere, ad esempio, il newObjectmetodo NSObjectController ).

(1) Puoi prendere il controllo usando i tuoi pool di autorelease locali. Per ulteriori informazioni, consultare Pool di autorelease .


6
Trovo che i vantaggi di non utilizzare il rilascio automatico superi i suoi costi (vale a dire più bug di perdita di memoria). Il codice sul thread principale dovrebbe comunque essere piuttosto corto (o altrimenti si bloccherà l'interfaccia utente) e per un codice di background più lungo e ad alta intensità di memoria, è sempre possibile racchiudere le porzioni ad uso intensivo di memoria nei pool di rilascio automatico locali.
adib,

56
Non sono d'accordo. Dovresti usare oggetti rilasciati automaticamente quando possibile. Se aumentano troppo l'ingombro della memoria, dovresti usarne un altro NSAutoreleasePool. Ma solo dopo aver confermato che questo è davvero un problema. Ottimizzazione prematura e tutto il resto ...
Sven

3
Passo meno di 40 secondi. un giorno digitando [rilascio di someObject] e leggendo la "riga aggiuntiva" durante l'istanza di un nuovo oggetto, ma una volta ho bruciato 17 ore per trovare un bug di rilascio automatico che si sarebbe presentato solo in casi speciali e non avrebbe dato alcun errore coerente nella console. Quindi sono d'accordo con Adib quando lo definisce come "Trovo che i vantaggi di non utilizzare il rilascio automatico superi i suoi costi".
RickiG,

7
Sono d'accordo con Sven. L'obiettivo primario dovrebbe essere la chiarezza del codice e la riduzione degli errori di codifica, con l'ottimizzazione della memoria solo dove è necessaria. Digitare un [[[Foo alloc] init] autorelease] è veloce e si affronta immediatamente il problema del rilascio di questo nuovo oggetto. Durante la lettura del codice non è necessario cercare la versione corrispondente per assicurarsi che non vi siano perdite.
Mike Weller,

3
Il ciclo di vita degli oggetti rilasciati automaticamente è ben definito e determinabile a un livello sufficiente.
Eonil,

69

Alcuni di questi sono già stati menzionati, ma ecco cosa posso pensare dalla cima della mia testa:

  • Segui le regole di denominazione KVO. Anche se non usi KVO ora, nella mia esperienza spesso è ancora utile in futuro. E se stai usando KVO o attacchi, devi sapere che le cose funzioneranno come dovrebbero. Questo riguarda non solo i metodi di accesso e le variabili di istanza, ma anche troppe relazioni, convalida, chiavi dipendenti con notifica automatica e così via.
  • Inserisci i metodi privati ​​in una categoria. Non solo l'interfaccia, ma anche l'implementazione. È bene avere una certa distanza concettualmente tra metodi privati ​​e non privati. Includo tutto nel mio file .m.
  • Inserisci i metodi di thread in background in una categoria. Come sopra. Ho trovato bello mantenere una chiara barriera concettuale quando stai pensando a cosa c'è nel thread principale e cosa no.
  • Usa #pragma mark [section]. Di solito raggruppo con i miei metodi, le sostituzioni di ogni sottoclasse e qualsiasi informazione o protocollo formale. Questo rende molto più semplice passare esattamente a quello che sto cercando. Sullo stesso argomento, raggruppa metodi simili (come i metodi delegati di una vista tabella), non incollarli ovunque.
  • Prefisso metodi privati ​​e ivar con _. Mi piace l'aspetto e ho meno probabilità di usare un ivar quando intendo una proprietà per caso.
  • Non usare metodi / proprietà mutatori in init & dealloc. Non ho mai avuto nulla di brutto a causa di ciò, ma posso vedere la logica se cambi il metodo per fare qualcosa che dipende dallo stato del tuo oggetto.
  • Inserisci IBOutlets nelle proprietà. In realtà ho appena letto questo qui, ma inizierò a farlo. Indipendentemente da eventuali benefici della memoria, sembra stilisticamente migliore (almeno per me).
  • Evita di scrivere codice che non ti serve assolutamente. Questo in realtà copre molte cose, come creare ivar quando lo si desidera #defineo memorizzare nella cache un array invece di ordinarlo ogni volta che sono necessari i dati. C'è molto che potrei dire a riguardo, ma la linea di fondo è non scrivere codice fino a quando non ne hai bisogno, o il profiler te lo dice. Rende le cose molto più facili da mantenere a lungo termine.
  • Termina ciò che inizi. Avere un sacco di codice buggy a metà è il modo più veloce per uccidere un progetto morto. Se hai bisogno di un metodo stub che va bene, basta indicarlo inserendo NSLog( @"stub" )dentro, o comunque vuoi tenere traccia delle cose.

3
Suggerirei di inserire metodi privati ​​in una continuazione di classe. (ie @interface MyClass () ... @end in your .m)
Jason Medeiros

3
Invece di #PRAGMA puoi usare un commento // Mark: [Sezione] che è più portatile e funziona in modo identico.
aleemb,

A meno che non manchi una sintassi speciale, // Mark: non aggiunge un'etichetta nel menu a discesa delle funzioni di Xcode, che è in realtà la metà del motivo per usarlo.
Marc Charbonneau,

6
È necessario utilizzare lettere maiuscole, "// MARK: ...", per visualizzarlo nel menu a discesa.
Rhult,

3
Per quanto riguarda Finish what you startte puoi anche usare // TODO:per contrassegnare il codice per il completamento che apparirà nel menu a discesa.
ucciso il

56

Scrivi unit test. Puoi testare molte cose in Cocoa che potrebbero essere più difficili in altri framework. Ad esempio, con il codice UI, puoi generalmente verificare che le cose siano connesse come dovrebbero e confidare che funzioneranno una volta utilizzate. E puoi impostare facilmente e invocare metodi delegati per testarli.

Inoltre, non hai visibilità del metodo pubblico vs. protetto vs. privato che ti impedisce di scrivere test per i tuoi interni.


Quali framework di test mi consigliate?
melfar,

13
Xcode include OCUnit, un framework di unit test di Objective-C e supporto per l'esecuzione di bundle di unit test come parte del processo di compilazione.
Chris Hanson,

55

Regola d'oro: se tu allocallora release!

AGGIORNAMENTO: A meno che non si stia utilizzando ARC


26
Anche se si copy, mutableCopy, newo retain.
Sven,

54

Non scrivere Objective-C come se fosse Java / C # / C ++ / etc.

Una volta ho visto un team abituato a scrivere applicazioni web Java EE tentare di scrivere un'applicazione desktop Cocoa. Come se fosse un'applicazione web Java EE. C'erano un sacco di AbstractFooFactory e FooFactory e IFoo e Foo che volavano in giro quando tutto ciò di cui avevano veramente bisogno era una classe Foo e possibilmente un protocollo Fooable.

Parte della garanzia di non farlo è comprendere veramente le differenze nella lingua. Ad esempio, non sono necessarie le classi factory e factory astratte sopra perché i metodi della classe Objective-C vengono inviati in modo dinamico come i metodi di istanza e possono essere sovrascritti in sottoclassi.


10
Come sviluppatore Java che ha scritto una fabbrica astratta in Objective-C lo trovo intrigante. Ti dispiacerebbe spiegare un po 'di più come funziona, forse con un esempio?
teabot,

2
Credi ancora che non abbiamo bisogno di lezioni di fabbrica astratte dopo tutto il tempo trascorso da quando hai pubblicato questa risposta?
kirk.burleson,

50

Assicurati di aggiungere la pagina di Debug Magic ai segnalibri . Questa dovrebbe essere la tua prima fermata quando sbatti la testa contro un muro mentre cerchi di trovare la fonte di un bug di cacao.

Ad esempio, ti spiegherà come trovare il metodo in cui hai allocato per la prima volta la memoria che in seguito causa arresti anomali (come durante la chiusura dell'app).


1
Esiste ora una versione specifica per iOS della pagina Debugging Magic .
Jeethu,

38

Cerca di evitare ciò che ora ho deciso di chiamare Newbiecategoryaholism. Quando i nuovi arrivati ​​in Objective-C scoprono le categorie, spesso si scatenano, aggiungendo utili piccole categorie a ogni classe esistente ( "Cosa? Posso aggiungere un metodo per convertire un numero in numeri romani in NSNumber su!" ).

Non farlo

Il tuo codice sarà più portatile e più facile da capire senza decine di metodi di piccole categorie sparsi su due dozzine di classi di base.

Il più delle volte quando pensi davvero di aver bisogno di un metodo di categoria per semplificare il codice, scoprirai che non finirai mai per riutilizzare il metodo.

Ci sono anche altri pericoli, a meno che tu non stia indicando i metodi della tua categoria (e chi oltre al ddribin assolutamente folle?) C'è la possibilità che Apple, o un plug-in o qualcos'altro in esecuzione nel tuo spazio degli indirizzi definisca anche la stessa categoria metodo con lo stesso nome con un effetto collaterale leggermente diverso ....

OK. Ora che sei stato avvisato, ignora il "non fare questa parte". Ma esercitare estrema moderazione.


Mi piace la tua risposta, il mio consiglio non userebbe una categoria per memorizzare il codice di utilità a meno che non stai per replicare un codice in più di un posto e il codice appartiene chiaramente alla classe che stai per classificare ...
Kendall Helmstetter Gelner,

Vorrei solo inserire e esprimere il mio supporto per i metodi di categoria di namespace. Sembra proprio la cosa giusta da fare.
Michael Buckley,

+1 se solo per i numeri romani. Lo farei assolutamente!
Brian Postow,

14
Contrappunto: nell'ultimo anno e mezzo ho seguito la politica esattamente opposta: "Se può essere implementato in una categoria, fallo". Di conseguenza il mio codice è molto più conciso, più espressivo e più facile da leggere rispetto al dettagliato codice di esempio fornito da Apple. Ho perso un totale di circa 10 minuti a causa di un conflitto nello spazio dei nomi e probabilmente ho guadagnato mesi-uomo dalle efficienze che ho creato per me stesso. A ciascuno il suo, ma ho adottato questa politica conoscendo i rischi e sono estremamente felice di averlo fatto.
cduhn,

7
Non sono d'accordo Se sarà una funzione e si applica a un oggetto Foundation e puoi pensare a un buon nome, inseriscilo in una categoria. Il tuo codice sarà più leggibile. Penso che il punto saliente qui sia davvero: fai tutto con moderazione.
mxcl

37

Resisti alla sottoclasse del mondo. In Cocoa molto si fa attraverso la delega e l'uso del runtime sottostante che in altri framework avviene attraverso la sottoclasse.

Ad esempio, in Java usi molto le istanze di *Listenersottoclassi anonime e in .NET usi molto le tue EventArgssottoclassi. In Cocoa, non lo fai neanche tu: al suo posto viene utilizzata l'azione bersaglio.


6
Altrimenti noto come "Composizione sull'eredità".
Andrew Ebling

37

Ordina le stringhe come desidera l'utente

Quando si ordinano le stringhe da presentare all'utente, non è necessario utilizzare il compare:metodo semplice . Invece, dovresti sempre usare metodi di confronto localizzati comelocalizedCompare: o localizedCaseInsensitiveCompare:.

Per ulteriori dettagli, consultare Ricerca, confronto e ordinamento di stringhe .


31

Proprietà dichiarate

In genere è necessario utilizzare la funzione Proprietà dichiarate Objective-C 2.0 per tutte le proprietà. Se non sono pubblici, aggiungili in un'estensione di classe. L'uso delle proprietà dichiarate rende immediatamente chiara la semantica della gestione della memoria e semplifica la verifica del metodo dealloc: se raggruppate le dichiarazioni delle proprietà, potete scansionarle rapidamente e confrontarle con l'implementazione del metodo dealloc.

Dovresti pensare intensamente prima di non contrassegnare le proprietà come "non anatomiche". Come osserva la Guida al linguaggio di programmazione dell'obiettivo C , le proprietà sono atomiche per impostazione predefinita e comportano un notevole sovraccarico. Inoltre, semplicemente rendere atomiche tutte le proprietà non rende l'applicazione thread-safe. Si noti inoltre, ovviamente, che se non si specifica "nonatomico" e si implementano i propri metodi di accesso (piuttosto che sintetizzarli), è necessario implementarli in modo atomico.


26

Pensa ai valori zero

Come nota questa domanda , i messaggi nilsono validi in Objective-C. Sebbene questo sia spesso un vantaggio - portando a un codice più pulito e più naturale - la funzione può occasionalmente portare a bug particolari e difficili da rintracciare se ottieni un nilvalore quando non te lo aspettavi.


Ho questo: #define SXRelease(o); o = nile lo stesso per CFReleasee free. Questo semplifica tutto.

26

Usa NSAssert e i tuoi amici. Uso sempre zero come oggetto valido ... soprattutto l'invio di messaggi a zero è perfettamente valido in Obj-C. Tuttavia, se voglio davvero accertarmi dello stato di una variabile, utilizzo NSAssert e NSParameterAssert, che aiutano a rintracciare facilmente i problemi.



23

Semplice ma spesso dimenticato. Secondo le specifiche:

In generale, i metodi in diverse classi che hanno lo stesso selettore (lo stesso nome) devono anche condividere gli stessi tipi di ritorno e argomento. Questo vincolo è imposto dal compilatore per consentire l'associazione dinamica.

nel qual caso tutti gli stessi selettori nominati, anche se in classi diverse , saranno considerati come identici tipi di ritorno / argomento. Qui c'è un semplice esempio.

@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;
}   

è facile da dimenticare. Importante comunque
Brock Woolf,

3
Questo è solo un problema quando si astiene dalla digitazione statica. Se il compilatore conosce il tipo, l'argomento e i tipi restituiti possono differire senza problemi. Personnaly, trovo che questo non sia spesso un problema. Apple ha anche molti metodi che hanno lo stesso nome ma differiscono nei tipi di restituzione. Infine, c'è un flag di compilazione per avvisarti in casi ambigui.
Nikolai Ruhe,

Se seguiamo le linee guida della convenzione di denominazione di Apple, questa situazione non accadrà :)
Eonil

22

Se stai utilizzando Leopard (Mac OS X 10.5) o versioni successive, puoi utilizzare l'applicazione Instruments per trovare e tenere traccia delle perdite di memoria. Dopo aver creato il programma in Xcode, selezionare Esegui> Inizia con Performance Tool> Perdite.

Anche se l'app non mostra perdite, è possibile che gli oggetti rimangano troppo a lungo. In Instruments, è possibile utilizzare lo strumento ObjectAlloc per questo. Seleziona lo strumento ObjectAlloc nel tuo documento Instruments e visualizza i dettagli dello strumento (se non è già mostrato) scegliendo Visualizza> Dettaglio (dovrebbe avere un segno di spunta accanto ad esso). Sotto "Durata della allocazione" nel dettaglio ObjectAlloc, assicurati di scegliere il pulsante di opzione accanto a "Creato e ancora vivente".

Ora, ogni volta che interrompi la registrazione della tua applicazione, selezionando lo strumento ObjectAlloc ti mostrerà quanti riferimenti ci sono per ogni oggetto ancora vivo nella tua applicazione nella colonna "# Net". Assicurati di guardare non solo le tue classi, ma anche le classi degli oggetti di livello superiore dei tuoi file NIB. Ad esempio, se non hai finestre sullo schermo e vedi riferimenti a una NSWindow ancora in vita, potresti non averla rilasciata nel tuo codice.


21

Pulisci in dealloc.

Questa è una delle cose più facili da dimenticare - esp. quando si codifica a 150 miglia all'ora. Pulisci sempre, sempre, sempre i tuoi attributi / variabili membro in dealloc.

Mi piace usare gli attributi Objc 2 - con la nuova notazione punto - quindi questo rende la pulizia indolore. Spesso semplice come:

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

Questo si occuperà del rilascio per te e imposterà l'attributo su NULL (che considero la programmazione difensiva - nel caso in cui un altro metodo più in basso nel dealloc acceda nuovamente alla variabile membro - raro ma potrebbe accadere).

Con GC attivato in 10.5, questo non è più necessario, ma potresti comunque aver bisogno di ripulire le altre risorse che crei, invece puoi farlo nel metodo finalize.


12
In generale, si dovrebbe non utilizzare metodi di accesso a dealloc (o init).
mmalc,

1
A parte le ragioni delle prestazioni (gli accessi sono leggermente più lenti dell'accesso diretto) perché non dovrei usare gli accessori in dealloc o init?
schwa

1
(a) I motivi delle prestazioni sono di per sé un motivo perfettamente adeguato (specialmente se i tuoi accessori sono atomici). (b) È necessario evitare gli effetti collaterali che possono avere gli accessori. Quest'ultimo è particolarmente un problema se la tua classe può essere sottoclassata.
mmalc

3
Noterò che se stai eseguendo il runtime moderno con ivar sintetizzati devi usare gli accessor in dealloc. Un sacco di codice di runtime moderno è GC, ma non tutto.
Louis Gerbarg,

1
Una visione più estesa su come utilizzare o non utilizzare metodi / proprietà accessor -inite -deallocmetodi è disponibile qui: mikeash.com/?page=pyblog/…
Johan Kool,

17

Tutti questi commenti sono fantastici, ma sono davvero sorpreso che nessuno abbia menzionato la Guida allo stile Objective-C di Google che è stata pubblicata qualche tempo fa. Penso che abbiano svolto un lavoro molto approfondito.


7
Hmm, il primo esempio è già pieno di cazzate. Non documentare mai linguaggi linguistici. Se trovassi questo tipo di commenti in un file di intestazione, non mi preoccuperei di continuare a leggere.
Stephan Eggermont,

5
Oh miei occhi !!!!! Non posso credere a quello che ho visto.
Eonil


13

Non dimenticare che NSWindowController e NSViewController rilasceranno gli oggetti di livello superiore dei file NIB che governano.

Se carichi manualmente un file NIB, sei responsabile del rilascio degli oggetti di livello superiore di NIB quando hai finito con loro.


12

Uno piuttosto ovvio per un principiante: utilizzare la funzione di rientro automatico di Xcode per il proprio codice. Anche se stai copiando / incollando da un'altra fonte, una volta incollato il codice, puoi selezionare l'intero blocco di codice, fare clic con il tasto destro su di esso e quindi scegliere l'opzione per rientrare di nuovo tutto all'interno di quel blocco.

Xcode analizzerà effettivamente quella sezione e la indenterà in base a parentesi, loop, ecc. È molto più efficiente che premere la barra spaziatrice o il tasto tab per ogni riga.


Puoi anche impostare Tab su indent e quindi fare Cmd-A e Tab.
Plumenatore,

10

So di averlo ignorato quando ho iniziato a programmare Cocoa.

Assicurarsi di comprendere le responsabilità di gestione della memoria relative ai file NIB. Sei responsabile del rilascio degli oggetti di livello superiore in qualsiasi file NIB che carichi. Leggi la documentazione di Apple sull'argomento.


6
Questo non è vero. Il fatto che tu sia o meno responsabile del rilascio di oggetti di livello superiore dipende dalla classe da cui erediti e dalla piattaforma che stai utilizzando. Vedi developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/… tra gli altri.
mmalc,

10

Attiva tutti gli avvisi GCC, quindi disattiva quelli che sono regolarmente causati dalle intestazioni di Apple per ridurre il rumore.

Esegui spesso anche l'analisi statica di Clang; puoi abilitarlo per tutte le build tramite l'impostazione di build "Esegui analizzatore statico".

Scrivi unit test ed eseguili con ogni build.


E, se puoi, attiva "Tratta gli avvisi come errori". Non consentire l'esistenza di alcun avviso.
Peter Hosey,

2
Uno script utile per impostare il tuo progetto con gli avvisi consigliati è disponibile qui: rentzsch.tumblr.com/post/237349423/hoseyifyxcodewarnings-scpt
Johan Kool,

10

Variabili e proprietà

1 / Mantenere pulite le intestazioni, nascondendo l'implementazione
Non includere variabili di istanza nell'intestazione. Le variabili private inseriscono la continuazione della classe come proprietà. Le variabili pubbliche dichiarano come proprietà pubbliche nell'intestazione. Se deve essere solo letto, dichiaralo come di sola lettura e sovrascriverlo come readwrite nella classe di conversione. Fondamentalmente non sto usando affatto variabili, solo proprietà.

2 / Assegna alle tue proprietà un nome variabile non predefinito, ad esempio:


@synthesize property = property_;

Motivo 1: verranno rilevati errori causati dalla dimenticanza di "sé". quando si assegna la proprietà. Motivo 2: dai miei esperimenti, Leak Analyzer in Instruments ha problemi a rilevare la perdita di proprietà con il nome predefinito.

3 / Non utilizzare mai il mantenimento o il rilascio direttamente sulle proprietà (o solo in situazioni molto eccezionali). Nel tuo dealloc basta assegnare loro uno zero. Le proprietà di conservazione devono essere gestite da sole. Non si sa mai se un setter non sta, ad esempio, aggiungendo o rimuovendo osservatori. Dovresti usare la variabile direttamente solo all'interno del suo setter e getter.

Visualizzazioni

1 / Metti ogni definizione di vista in un xib, se puoi (l'eccezione è di solito il contenuto dinamico e le impostazioni del livello). Fa risparmiare tempo (è più facile che scrivere il codice), è facile da cambiare e mantiene pulito il codice.

2 / Non tentare di ottimizzare le visualizzazioni diminuendo il numero di visualizzazioni. Non creare UIImageView nel tuo codice invece di xib solo perché vuoi aggiungere delle subview in esso. Utilizzare invece UIImageView come sfondo. Il framework delle viste può gestire centinaia di visualizzazioni senza problemi.

3 / IBOutlet non devono essere sempre mantenuti (o forti). Nota che la maggior parte dei tuoi IBOutlet fanno parte della gerarchia di visualizzazione e quindi implicitamente mantenuti.

4 / Rilasciare tutti gli IBOutlet in viewDidUnload

5 / Call viewDidScarica dal tuo metodo dealloc. Non è implicitamente chiamato.

Memoria

1 / Rilascio automatico degli oggetti quando vengono creati. Molti bug sono causati dallo spostamento della chiamata di rilascio in un ramo if-else o dopo una dichiarazione di ritorno. Rilascio invece del rilascio automatico dovrebbe essere utilizzato solo in situazioni eccezionali, ad esempio quando si è in attesa di un runloop e non si desidera che il proprio oggetto venga rilasciato automaticamente in anticipo.

2 / Anche se si utilizza il conteggio dei riferimenti di Authomatic, è necessario comprendere perfettamente il funzionamento dei metodi di conservazione. L'uso manuale della funzione di ritenuta non è più complicato di ARC, in entrambi i casi devi preoccuparti di perdite e cicli di ritenzione. Prendi in considerazione l'utilizzo manuale della conservazione in progetti di grandi dimensioni o gerarchie di oggetti complicati.

Commenti

1 / Rendi il tuo codice auto documentato. Ogni nome di variabile e nome di metodo dovrebbe dire cosa sta facendo. Se il codice è scritto correttamente (è necessaria molta pratica in questo), non sarà necessario alcun commento sul codice (non uguale ai commenti sulla documentazione). Gli algoritmi possono essere complicati ma il codice dovrebbe essere sempre semplice.

2 / A volte, avrai bisogno di un commento. Di solito per descrivere un comportamento in codice non apparente o hack. Se ritieni di dover scrivere un commento, prova prima a riscrivere il codice per essere più semplice e senza la necessità di commenti.

dentellatura

1 / Non aumentare troppo il rientro. La maggior parte del codice del metodo deve essere rientrato a livello di metodo. I blocchi nidificati (if, for etc.) riducono la leggibilità. Se hai tre blocchi nidificati, dovresti provare a mettere i blocchi interni in un metodo separato. Quattro o più blocchi nidificati non devono mai essere utilizzati. Se la maggior parte del codice del metodo è all'interno di un if, annulla la condizione if, ad esempio:


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

return self;

if (!self) {
   return nil;
}

//... long initialization code ...

return self;

Comprendi il codice C, principalmente le strutture C.

Si noti che Obj-C è solo uno strato OOP leggero sul linguaggio C. Dovresti capire come funzionano le strutture di codice di base in C (enumerazioni, strutture, matrici, puntatori ecc.). Esempio:


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

equivale a:


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

E molti altri

Gestisci il tuo documento sugli standard di codifica e aggiornalo spesso. Prova a imparare dai tuoi bug. Scopri perché è stato creato un bug e cerca di evitarlo utilizzando gli standard di codifica.

I nostri standard di codifica hanno attualmente circa 20 pagine, un mix di Java Coding Standards, Google Obj-C / C ++ Standards e le nostre aggiunte. Documenta il tuo codice, usa rientri standard, spazi bianchi e righe vuote nei punti giusti, ecc.


9

Sii più funzionale .

Objective-C è un linguaggio orientato agli oggetti, ma è consapevole dello stile funzionale del framework Cocoa ed è progettato in molti casi uno stile funzionale.

  1. C'è separazione della mutabilità. Usa immutabile classi come primario e l'oggetto mutabile come secondario. Ad esempio, utilizzare NSArray principalmente e utilizzare NSMutableArray solo quando è necessario.

  2. Ci sono funzioni pure. Non così tanti, acquistare molte delle API del framework sono progettate come pura funzione. Guarda funzioni come CGRectMake()o CGAffineTransformMake(). Ovviamente la forma del puntatore sembra più efficiente. Tuttavia, l'argomento indiretto con i puntatori non può offrire effetti collaterali gratuiti. Progetta strutture il più puramente possibile. Separare gli oggetti stato pari. Utilizzare -copyinvece di -retainquando si passa un valore ad un altro oggetto. Perché lo stato condiviso può influenzare silenziosamente la mutazione al valore in un altro oggetto. Quindi non può essere privo di effetti collaterali. Se si dispone di un valore da esterno a oggetto, copiarlo. Quindi è anche importante progettare lo stato condiviso il più minimamente possibile.

Tuttavia, non aver paura di usare anche funzioni impure.

  1. C'è una valutazione pigra. Vedi qualcosa come la -[UIViewController view]proprietà. La vista non verrà creata quando viene creato l'oggetto. Verrà creato viewalla prima lettura della proprietà del chiamante . UIImagenon verrà caricato fino a quando non verrà effettivamente disegnato. Esistono molte implementazioni come questa progettazione. Questo tipo di progetti sono molto utili per la gestione delle risorse, ma se non si conosce il concetto di valutazione pigra, non è facile comprenderne il comportamento.

  2. C'è chiusura. Usa i blocchi C il più possibile. Questo semplificherà notevolmente la tua vita. Ma leggi ancora una volta sulla gestione della memoria a blocchi prima di usarlo.

  3. C'è un GC semi-automatico. NSAutoreleasePool. Usa -autoreleaseprimario. Usa il manuale -retain/-releasesecondario quando ne hai veramente bisogno. (es: ottimizzazione della memoria, eliminazione esplicita delle risorse)


2
Per quanto riguarda 3) proporrò l'approccio opposto: utilizzare la conservazione / rilascio manuale ove possibile! Chissà come verrà utilizzato questo codice - e se verrà utilizzato in un ciclo stretto può far esplodere inutilmente l'utilizzo della memoria.
Eiko,

@Eiko Questa è solo un'ottimizzazione precoce , non può essere una guida generale.
Eonil,

1
Penso che sia più una cosa di design, specialmente quando si lavora su classi di modelli. Considero la crescita della memoria come un effetto collaterale e non è quello che voglio apparire spesso. Peggio ancora, un altro sviluppatore che usa il mio codice non ha altra possibilità che avvolgere chiamate costose in pool di autorelease (se possibile, i miei oggetti potrebbero essere inviati ad un altro codice di libreria). E quei problemi sono difficili da diagnosticare in seguito, ma economici da evitare in primo luogo. Se si copiano / rilasciano automaticamente oggetti che sono stati superati, è possibile che si perdano se sono molto più grandi di quanto ci si aspettasse. Tuttavia, sono più rilassato con il codice GUI.
Eiko,

@Eiko Sono d'accordo autoreleaseche manterrà la memoria più a lungo in generale, e il manuale retain/releasepuò ridurre il consumo di memoria nel caso. Tuttavia, dovrebbe essere una guida per l'ottimizzazione di casi speciali (anche se ti senti sempre!), Non può essere la ragione per generalizzare l'ottimizzazione prematura come pratica . E in effetti, il tuo suggerimento non è contrario a me. L'ho menzionato come un caso di
reale
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.