Diese Anleitung fasst die Coding Standards des iOS Teams der New York Times zusammen. Wir freuen uns über Feedback unter [issues] (https://github.com/NYTimes/objetive-c-style-guide/issues), pull requests und tweets. Wir suchen aktuell noch Verstärkung.
Danke an alle Beitragenden.
Hier sind einige Dokumente von Apple, durch die die Anleitung geprägt wurde. Wenn hier etwas fehlen sollte, wird es wahrscheinlich in einer der folgenden Quellen detailliert betrachtet:
- The Objective-C Programming Language
- Cocoa Fundamentals Guide
- Coding Guidelines for Cocoa
- iOS App Programming Guide
- Punktnotation
- Abstände
- Bedingungen
- Fehlerbehandlung
- Methoden
- Variablen
- Bezeichnung
- Kommentare
- Init & Dealloc
- Literale
- CGRect Funktionen
- Konstanten
- Enumarationen
- Bitfelder
- Private Properties
- Bezeichnung von Bildern
- Wahrheitswerte
- Singletons (Entwurfsmuster)
- Importierung
- Xcode Projekt
Die Punktnotation sollte immer verwendet werden, um auf Properties zuzugreifen oder sie zu verändern. Die Notation mit eckigen Klammern wird in allen anderen Fällen bevorzugt.
Beispiel:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
Nicht:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
- Einrückungen benutzen 4 Leerzeichen. Rücke niemals mit Tabs ein. Versichere dich, die entsprechende Einstellung in Xcode vorzunehmen.
- Bei Methoden und in anderen Fällen (
if
/else
/switch
/while
etc.), wird die öffnende Klammer immer in die gleiche Zeile entsprechend der tatsächlichen Anweisung - die schließende Klammer jedoch in eine neue Zeile, gesetzt.
Beispiel:
if (user.isHappy) {
//Do something
}
else {
//Do something else
}
- Es soll genau eine leere Zeile zwischen den Methoden eingefügt werden, um Übersicht und Organisation zu fördern. Leerzeichen innerhalb von Methoden, sollte funktional unterteilt sein, wobei in vielen Fällen neue Methoden verwendet werden sollten.
@synthesize
und@dynamic
sollten in der Implementation jeweils in neuen Zeilen deklariert werden.
Der Rumpf einer Bedingung sollte immer Klammern verwenden, auch wenn es ohne Klammern stehen könnte (es ist nur eine Zeile), damit Fehler vermieden werden.
Diese Fehler entstehen beim Einfügen einer zweiten Zeile, von der man erwartet, dass sie Teil der if-Anweisung wird. Ein weiterer, sogar viel gefährlicherer Fehler könnte passieren, wenn die Zeile "innerhalb" der if-Anweisung auskommentiert wird und die nächste Zeile unwillentlich Teil dieser if-Anweisung wird. Davon abgesehen, ist diese Form viel konsistenter, verglichen mit den anderen Bedingungsanweisungen und deshalb auch einfacher zu lesen.
Beispiel:
if (!error) {
return success;
}
Nicht:
if (!error)
return success;
oder
if (!error) return success;
Der ternäre Operator, ? , sollte nur verwendet werden, wenn es der Klarheit dient und den Code sauber aussehen lässt. Eine einzelne Bedingung ist normalerweise alles, was ausgewertet werden sollte. Mehrere Bedingungen sollten besser in einer if-Anweisung ausgewertet werden oder in Instanzvariablen übertragen werden.
For example:
result = a > b ? x : y;
Not:
result = a > b ? x = c > d ? c : d : y;
Wenn Methoden einen Fehler als Parameter in Form einer Referenz zurückgeben, sollte der Rückgabewert der Methode überprüft werden, nicht die Fehler-Variable.
Beispiel:
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
Nicht:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// Handle Error
}
Einige der Apple API's schreiben im erfolgreichen Fall Müll in die Parameter (wenn nicht NULL), so dass das Auswerten eines Fehlers falsche Ergebnisse bringen kann (und folglich das Programm abstürzen könnte).
In Methoden Signaturen sollte Abstand hinter dem +/- Zeichen sein. Es sollte ein Leerzeichen zwischen den einzelnen Teilen der Methodensegmente eingefügt werden.
Beispiel:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
Variablen sollten so deskriptiv wie möglich sein. Variablen mit einzelnen Buchstaben sollten vermieden werden, wenn sie nicht in for
Schleifen verwendet werden.
Asterisks, die auf Zeiger hinweisen, gehören zur Variable - z.B. NSString *text
nicht NSString* text
oder NSString * text
. Ausnahmen bilden die Konstanten (NSString * const NYTConstantString
).
Property Eigenschaften sollten, wenn immer es möglich ist, an Stelle von einfachen Instanzvariablen verwendet werden. Direkter Zugriff auf Instanzvariablen sollte vermieden werden, Ausnahmen bilden Methoden zur Initialisierung (init
, initWithCoder:
, etc...), dealloc
Methoden und innerhalb von eigenen Zugriffsmethoden (Setter/Getter). Weitere Informationen zur Verwendung von Zugriffsmethoden und Methoden zur Initialisierung und dealloc, finden sich hier
Beispiel:
@interface NYTSection: NSObject
@property (nonatomic) NSString *headline;
@end
Nicht:
@interface NYTSection : NSObject {
NSString *headline;
}
Wenn es um die Qualifikationen von Variablen, [die mit ARC eingeführt wurden, geht]((https://developer.apple.com/library/ios/releasenotes/objectivec/rn-transitioningtoarc/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226-CH1-SW4), sollte die entsprechende Qualifikation zwischen dem Asterisk und dem Variablennamen plaziert werden. Z.B.: NSString *__weak text
.
Die von Apple vorgegebenen Konventionen zur Benennung sollten wenn immer möglich eingehalten werden. Besonders jene zur Speicherverwaltung(NARC).
Lange, ausführliche Methoden- und Variablennamen sind gut.
Beispiel:
UIButton *settingsButton;
Nicht
UIButton *setBut;
Ein Prefix aus drei Buchstaben (z.B. NYT
) sollte immer vor Klassennamen und Konstanten stehen, kann bei Core Data Entitäten aber weggelassen werden. Konstanten sollten "Camel-Case" sein, alle Wörter beginnen mit Großbuchstaben und der entsprechende Klassenname wird vorangestellt, um für Klarheit zu sorgen.
Beispiel:
static const NSTimeInterval NYTArticleViewControllerNavigationFadeAnimationDuration = 0.3;
Nicht:
static const NSTimeInterval fadetime = 1.7;
Properties und lokale Variablen sollten ebenfalls "Camel-Case" sein, wobei der erste Buchstabe dann klein geschrieben wird.
Instanzvariablen sollten "Camel-Case" sein, wobei das erste Wort klein geschrieben wird und ein Unterstrich vorangestellt wird. Das ist einheitlich mit der Art und Weise, wie LLVM Instanzvariablen automatisch synthetisiert Wenn LLVM die Variablen automatisch synthetisiert, sollte man das auch zulassen.
Beispiel:
@synthesize descriptiveVariableName = _descriptiveVariableName;
Not:
id varnm;
Wenn sie gebraucht werden, sollten Kommentare verwendet werden um zu erklären, warum ein bestimmtes Codefragment etwas tut. Alle Kommentare die verwendet werden, müssen ständig aktuell sein oder andernfalls gelöscht werden.
Blockkommentare sollten generell vermieden werden, da Code (abgesehen von ein paar erklärenden Zeilen zwischendurch) so selbsterklärend wie möglich sein sollte. Das bezieht sich nicht auf Kommentare, die angewendet werden, um eine Dokumentation zu erstellen.
dealloc
Methoden sollten direkt an den Anfang der Implementation, direkt hinter de @synthesize
und @dynamic
Aufruf platziert werden.
init
Methoden sollten folgendermaßen strukturiert werden:
- (instancetype)init {
self = [super init]; // oder Aufruf des designated initializer
if (self) {
// Custom initialization
}
return self;
}
NSString
, NSDictionary
, NSArray
und NSNumber
Literale sollten immer verwendet werden, wenn nicht veränderbare (immutable) Instanzen dieser Klassen erstellt werden. Besonders sollte darauf geachtet werden keine nil
Werte in die NSArray
und NSDictionary
Literale geschrieben werden, da diese einen Absturz verursachen werden.
Beispiel:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
Nicht:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
Beim Zugriff auf x
, y
, width
oder height
eines CGRect sollten statt des direkt Zugriffs auf die struct
Werte, immer die CGGeometry
Funktionen verwendet werden.
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
Beispiel:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
Nicht:
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
Konstanten werden gegenüber in-line Zeichenketten oder Zahlen bevorzugt, da sie eine einfache Wiederverwendung von gemeinsam verwendeten Variablen erlauben. Zudem können sie einfach ersetzt werden, ohne sie zuvor mit "Finde und Ersetze" ausfindig gemacht zu haben. Konstanten sollten als static
Konstanten und nicht mit #define deklariert werden, so fern sie nicht explizit in Makros verwendet werden.
Beispiel:
static NSString * const NYTAboutViewControllerCompanyName = @"The New York Times Company";
static const CGFloat NYTImageThumbnailHeight = 50.0;
Nicht:
#define CompanyName @"The New York Times Company"
#define thumbnailHeight 2
Bei der Verwendung von Enumerierungen wird empfohlen, von der neueren Typ-Spezifizierung Gebrauch zu machen, da eine stärkere Typ-Prüfung und Code-Vervollständigung mit inbegriffen ist. Das SDK stellt nun ein Makro mit dem festgelegten Typen NS_ENUM
zur Verfügung.
Beispiel:
typedef NS_ENUM(NSInteger, NYTAdRequestState) {
NYTAdRequestStateInactive,
NYTAdRequestStateLoading
};
Wenn mit Bitfeldern gearbeitet wird, sollte das NS_OPTION
Makro verwendet werden.
Beispiel:
typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
NYTAdCategoryAutos = 1 << 0,
NYTAdCategoryJobs = 1 << 1,
NYTAdCategoryRealState = 1 << 2,
NYTAdCategoryTechnology = 1 << 3
};
Private Eigenschaften sollten in den Klassenerweiterungen der Implementierung einer Klasse deklariert werden (Anonyme Kategorien).
Beispiel:
@interface NYTAdvertisement ()
@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;
@end
Bilder sollten einheitlich benannt werden um der Verwirrung bei den Entwicklern und im Unternehmen vorzubeugen. Sie sollten als eine Camel-Case Zeichenkette benannt werden und ihr Name sollte auf ihre Verwendung aufmerksam machen. Darauf folgt der Name (ohne Präfix) der Klasse oder der Eigenschaft, die sie anpassen (wenn sie das überhaupt tun). Darauf folgt eine weitere Beschreibung ihrer Farbe, Position und schliesslich der Zustand.
Beispiel:
RefreshBarButtonItem
/RefreshBarButtonItem@2x
andRefreshBarButtonItemSelected
/RefreshBarButtonItemSelected@2x
ArticleNavigationBarWhite
/ArticleNavigationBarWhite@2x
andArticleNavigationBarBlackSelected
/ArticleNavigationBarBlackSelected@2x
.
Bilder die in einem ähnlichen Bereich eingesetzt werden, sollten innerhalb des Bilder-Ordners in entsprechende Unterordner eingeteilt werden.
Da nil
zu NO
aufgelöst wird, ist es nicht notwendig, es in Bedingungen zu vergleichen. Vergleiche nie etwas direkt mit YES
, da YES
als 1 definiert ist und ein BOOL
-Typ kann 8 Bits lang sein.
Dadurch ist eine Gleichmäßigkeit über mehrere Files hinweg und bessere visuelle Klarheit gewährleistet.
Beispiel:
if (!someObject) {
}
Not:
if (someObject == nil) {
}
Für ein BOOL
, hier ein weiteres Beispiel:
if (isAwesome)
if (![someObject boolValue])
Not:
if (isAwesome == YES) // Niemals so.
if ([someObject boolValue] == NO)
Wenn der Name eines BOOL
Property als Adjektiv formuliert wurde, kann die Vorsilbbe "is" weggelassen werden. Allerdings sollte der konventionelle Name für den Getter Zugriff spezifiert werden, z.B.:
@property (assign, getter=isEditable) BOOL editable;
Text und Beispiel stammen von Cocoa Naming Guidelines.
Singletons sollten ein thread-sicheres Muster bei der Erstellung ihrer Instanzen einhalten.
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Dies wird [einigen möglichen Abstürzen] vorbeugen (http://cocoasamurai.blogspot.com/2011/04/singletons-your-doing-them-wrong.html).
Wenn es mehr als eine Importierungsanweisung gibt, sollten diese zusammen gruppiert werden. Das Kommentieren der Gruppen ist optional.
Notiz: Für Module soll die @import syntax verwendet werden.
// Frameworks
@import QuartzCore;
// Models
#import "NYTUser.h"
// Views
#import "NYTButton.h"
#import "NYTUserView.h"
Die real vorhanden Dateien sollten mit den Dateien in Xcode synchronisiert werden, um ein Durcheinander zu vermeiden. Alle Gruppen, die in Xcode erstellt wurden, sollten sich im Dateisystem widerspiegeln. Code sollte nicht nur nach seinem Typ, sondern auch nach den entsprechenden Features gruppiert werden, um eine bessere Übersicht zu gewährleisten.
Wenn möglich, sollte die Option "Treat Warnings as Errors" in den Target Build Einstellungen angepasst werden und soviel [zusätzliche Warnungen] wie möglich ausgeben (http://boredzo.org/blog/archives/2009-11-07/warnings). Verwende [Clangs pragma feature] (http://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-via-pragmas), wenn eine bestimmte Warnung ignoriert werden soll.
Wenn unsere Version nicht Euren Geschmack trifft, schaut euch ein paar andere Styleguides an: