Это руководство по стилю кода, основанное на руководстве для разработчиков в The New York Times(https://github.com/NYTimes/objective-c-style-guide/contributors)
Согласованность с этим руководством очень важна. Согласованность внутри одного проекта еще важнее. А согласованность внутри модуля или функции — самое важное. Но важно помнить, что иногда это руководство неприменимо, и понимать, когда можно отойти от рекоммендаций.
Две причины, чтобы нарушить правила:
- Когда применение правила сделает код менее читабельным даже для того, кто привык читать код, который следует правилам.
- Чтобы писать в едином стиле с кодом, который уже есть в проекте и который нарушает правила (может быть, в силу исторических причин) — впрочем, это возможность подчистить чужой код.
Ниже представлены документы Apple по этой же теме:
- The Objective-C Programming Language
- Cocoa Fundamentals Guide
- Coding Guidelines for Cocoa
- iOS App Programming Guide
- Использование точек
- Отсутупы
- Условия
- Обработка ошибок
- Методы
- Переменные
- IBOutlets
- Правила именования
- Комментарии
- Инициализация и уничтожение объектов
- Литералы
- CGRect функции
- Константы
- Тип перечисление
- Private свойства
- Именование графических ресурсов
- Логический тип BOOL
- Синглтоны
- Предпочтительная структура .h / .m файлов
- Xcode проект
Используйте точки только для доступа к свойствам. Во всех остальных случаях используйте квадратные скобки.
Хорошо:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;Плохо:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;- Для отсупов используйте табы длиной в 4 пробела (проверьте, что ваш проект настроен на использование табов).
- Фигурные скобки в методах и других конструкциях (например
if/else/switch/while) всегда всегда должны открываться в той же строке, что и условие. Но закрываться должны на другой строке. - Не оставляйте пустых блоков с фигурными скобками. Исключение составляют методы, которые обязательны для имплементации, но их нужно оставить пустыми в конкретном случае.
Хорошо:
if (user.isHappy) {
//Do something
}
else {
//Do something else
}Плохо:
@interface Blob : DataMartsBaseItem
{
}- Всегда стоит добавлять пустую строку между методами - это придаст больше четкости коду. Отступы внутри методов всегда должны отделять функциональность, которую можно было бы вынести в отдельные методы.
@synthesizeи@dynamicдолжны начинаться на новой строке.- Модификаторы области видимости типа
@public,@protectedи подобные (типа@optionalв протоколах) нужно сдвигать на один пробел вправо.
Например:
@interface AFKSection : NSObject {
@private
NSString *_headline;
@protected
NSInteger _count;
}Тело условия необходимо писать в фигурных скобках даже в том случае, если это можно и не делать (когда оно может быть написано в одну строку) errors. Это исключит ошибки, возникающие когда следующая строка не попадает под условие, хотя должна (так думает разработчик). В другом случае, ошибка может возникнуть когда строка внутри условного оператора закоментирована и следующая строка становится телом условного оператора . К тому же, данный стиль лучше согласуется с оформлением других блоков и становится лучше читаемым.
Хорошо:
if (!error) {
return success;
}Плохо:
if (!error)
return success;или
if (!error) return success;Тернарный оператор, ? , используйте только когда это улучшает читаемость и понятность кода. В том случае, когда используется одно условие. Для нескольких условий более предпочтительно использовать оператор "if" или переменные.
Хорошо:
result = a > b ? x : y;Плохо:
result = a > b ? x = c > d ? c : d : y;Когда метод возвращает ошибку по ссылке, проверяйте возвращаемое значение, а не ошибку.
Хорошо:
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}Плохо:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// Handle Error
}Некоторые методы из API Apple возвращают ошибку(не NULL) даже в случае успешного выполнения, по этой причине не анализируйте значение ошибки.
В определениях методов пробелы должны стоять после типа методов (символы - и +). Так же пробелами нужно разделять параметры методов.
Например:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;Переменные нужно называть максимально информативно. Имена переменных, состоящие из одной буквы, допускаются только для счётчика в цикле for().
Звёздочку указателя нужно ставить впритык к имени переменной, например NSString *text а не NSString* text или NSString * text, исключение составляют константы.
Для private и protected переменных (которые используются исключительно внутри класса, и/или внутри подклассов) необходимо использовать instance-переменные, а не свойства. Не забывайте указывать область видимости переменной, так как по-умолчанию область видимости instance-переменных - protected.
Свойства можно использовать только для public-переменных, доступ к которым нужно получать вне класса. Исключения составляют свойства, для которых необходимо переопределить сеттер и/или геттер. Их нужно хранить в .m файле, в безымянной private-категории.
Для простых типов по максимуму используйте специальные типы такие как NSInteger, NSUInteger, CGFloat вместо int, float и т.д.. Это в дальнейшем облегчит переход на 64-битную платформу.
[Apple 64-Bit Transition Guide for Cocoa Touch] (https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/Major64-BitChanges/Major64-BitChanges.html#//apple_ref/doc/uid/TP40013501-CH2-SW8)
Хорошо:
@interface AFKSection : NSObject {
@private
NSString *_headline;
NSUInteger _count;
}
@property (strong, nonatomic) NSString *publicHeadline;Плохо:
@interface AFKSection: NSObject
@property (strong, nonatomic) NSString *publicHeadline;
@property (strong, nonatomic) NSString *headline;
@property (nonatomic) int count;
@end##IBOutlets
Для задания IBOutlet'ов используйте instance-переменные. По возможности избегайте использования их вне класса.
Все IBOutlet'ы нужно делать "слабыми" (weak), так как они находятся внутри иерархии, и на них уже ссылается их родитель.
strong допускается только для свойств объектов, на которые никто не ссылается (например, ViewController'ы, или другие объекты в xib'ах, находящиеся вверху их иерархии).
Хорошо:
@interface DMWidgetViewController : DMViewController {
@private
IBOutlet DMObjectPassportViewController *_objectPassportViewController;
IBOutlet DMNavigationMenuWidgetSelectViewController *_topMenuViewController;
IBOutlet DMSideMenuViewController *_sideMenuViewController;
__weak IBOutlet UIView *_containerView;
__weak IBOutlet UIView *_topBarContainerView;
__weak IBOutlet UIImageView *_contentBackgroundImageView;
}Плохо:
@interface DMSideMenuCell : UITableViewCell
@property (nonatomic, strong) IBOutlet UIImageView* cellBg;
@property (nonatomic, strong) IBOutlet UILabel* titleLbl;
@endили
@interface DMCarouselViewController : DMViewController {
@private
IBOutlet UIButton *changeTypeButton;
IBOutlet UILabel *dateLabel;
IBOutlet UIButton *refreshButton;
}По возможности старайтесь соблюдать соглашение Apple о именовании memory management rules (NARC).
Используйте подробные имена переменных.
Хорошо:
UIButton *settingsButton;Плохо:
UIButton *setBut;Трехбуквенные префиксы (например 'AFK') всегда используйте для имен классов и констант, однако ими можно пренебречь для сущностей Core Data
Константы должны именоваться ВерблюжьемРегистре, где все слова начинаются с большой буквы. Локальные константы должны начинаться с буквы k. Глобальные-же константы должны начинаться с имени класса, в котором описаны.
Хорошо:
static const NSTimeInterval kNavigationFadeAnimationDuration = 0.3;Плохо:
static const NSTimeInterval fadetime = 1.7;Свойства должны именоваться тоже в ВерблюжьемРегистре, только первое слово в нижнем регистре. Если Xcode автоматически синтезировал перменные, оставьте их. В противном случае, согласно выше сказанному, имена переменных для этих свойств должны начинаться с подчеркивания и первое слово должно быть в нижнем регистре. В таком формате Xcode обеспечивает связь между свойствами и переменными.
Хорошо:
@synthesize descriptiveVariableName = _descriptiveVariableName;Плохо:
id varnm;Внутри класса доступ (чтение / запись) к instance-переменным, у которых есть свойства (properties), должен осуществляться через self.. В этом случае сразу будет видно, что это именно свойство. Локальные переменные не должны содержать символов подчеркивания (_).
Комментарии необходимо писать там, где нужно объяснить зачем в конкретной части кода делается что-то. Любые используемые комментарии должны поддерживаться в актуальном состоянии, либо их следует удалить.
Если комментарий — фраза или предложение, первое слово должно быть написано с большой буквы, точку в конце предложения можно опустить.
Блоковых комментариев следует избегать, код должен быть самодокументирован и содержать только несколько строчек комментариев для нестабильных или временных участков.
Не оставляйте закомментированные строчки кода, их следует удалить перед отправкой кода в репозиторий.
Удаление: метод dealloc, если он необходим, должен помещаться вверху реализации (@implementation), сразу после @synthesize и @dynamic .
Инициализация: метод init должен быть расположен сразу после dealloc метода во всех классах.
Структура методов init должны быть такой:
- (instancetype)init {
self = [super init]; // or call the designated initalizer
if (self) {
// Custom initialization
}
return self;
}Для создания неизменяемых объектов (immutable objects) используйте литералы классов NSString, NSDictionary, NSArray, и NSNumber. Обратите внимание, что nil значения не могут быть переданны в объекты-литералы классов NSArray и NSDictionary, так как это приведет к ошибке.
Хорошо:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;Плохо:
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];Вместо прямого доступа к полям x, y, width, или height структуры CGRect, всегда используйте функции CGGeometry.
Из документации Apple CGGeometry:
Описанные в данном документе функции, принимающие структуру CGRect в качестве аргумента, неявно приводят эти структуры к стандартному виду. Поэтому, избегайте прямого доступа к полям структуры CGRect при написании программы. Вместо этого для работы со структурами и для получения значений отдельных полей используйте функции, описанные в данном руководстве.
Хорошо:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);Плохо:
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;Избегайте использования "магических чисел"(magic numbers) и строковых литералов. Вместо этого определяйте их как переменные-константы. Это позволяет сделать код намного понятней и облегчает их изменение.
Объявляйте константы с модификатором static и не объявляйте при помощи #define. Используйте #define только для макросов.
Названия локальных констант (enum-ов, локальных переменных-констант, и т.д.) должны начинаться со строчной буквы k.
Для разделения слов используйте ВерблюжийРегистр.
Для целочисленных (NSInteger) констант используйте NS_ENUM.
Хорошо:
static NSString * const kCompanyName = @"The New York Times Company";
static const CGFloat kImageThumbnailHeight = 50.0;
NS_ENUM(NSInteger, TableSize) {
kTableSizeRowsCount = 10
};Плохо:
#define CompanyName @"The New York Times Company"
#define thumbnailHeight 2Хорошая статья об альтернативах командам препроцессора
Глобальные константы должны задаваться в файле реализации, в заголовочном файле должно быть только описание константы с модификатором extern. Названия глобальных констант должны начинаться с имени класса, в котором определена константа.
Хорошо:
.h
extern NSString *const AFKSearchEngineSearchResultsScopeKey;.m
NSString *const AFKSearchEngineSearchResultsScopeKey = @"scopeKey";При использовании enumов рекомендуется использовать новый спецификатор типа так как он имеет более строгую проверку типов и улучшенное автозаполнение кода. Теперь SDK включает в себя макрос, облегчающий и улучшающий использование фиксированного идентификатора типов - NS_ENUM()
Хорошо:
typedef NS_ENUM(NSInteger, NYTAdRequestState) {
kNYTAdRequestStateInactive,
kNYTAdRequestStateLoading
};Плохо:
typedef enum
{
en_BlobUnknown,
en_BlobImageRetina,
en_BlobImageBgRetina,
en_BlobPdf
}
enumBlobType;Используйте private-свойства только когда нужно переопределить сеттер или геттер для intance-перменных (во всех остальных случаях используйте intance-переменные).
Объявляйте private-свойства в class extension (анонимных категориях) в файле с реализацией класса (.m). Используйте именованные категории (такие как NYTPrivate или private) только для расширения других классов.
Хорошо:
@interface NYTAdvertisement ()
@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;
@endИзображения должны быть названы в ВерблюжьемРегистре с описанием их назначения, содержать имя класса или свойства, для которого они предназначены (если такое имеется), далее идет цвет или расположение, и в конце состояние элемента, для которого предназначено изображение.
Хорошо
RefreshBarButtonItem/RefreshBarButtonItem@2xandRefreshBarButtonItemSelected/RefreshBarButtonItemSelected@2xArticleNavigationBarWhite/ArticleNavigationBarWhite@2xandArticleNavigationBarBlackSelected/ArticleNavigationBarBlackSelected@2x.
Изображения, используемые для оинаковых целей должны быть сгруппированы в папки.
Не сравнвайте напрямую с nil в условных операторах. Никогда не делайте сравнения с YES, потому что YES определен как (BOOL)1, и если вы будете сравнивать YES со значением длиной более чем 1 байт (например short или int), то при сравнении будет использован только младший байт этого значения.
Следование этип правилам позволяет добиться единобразия в коде и улучшить его читаемость.
Хорошо:
if (!someObject) {
}Плохо:
if (someObject == nil) {
}Хорошо(для типа BOOL):
if (isAwesome)
if (![someObject boolValue])Плохо(для типа BOOL):
if ([someObject boolValue] == NO)
if (isAwesome == YES) // Never do this.Если имя BOOL-свойства интерпретируется как прилагательное, то префикс "is" можно опустить, но обязательно укажите его в названии геттера.
@property (assign, getter=isEditable) BOOL editable;Текст и примеры взяты из Cocoa Naming Guidelines.
Для реализации синглтонов используйте потокобезопасный шаблон
#import <Foundation/Foundation.h>
@interface MySingleton : NSObject
+(instancetype) sharedInstance;
+(instancetype) alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype) init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype) new __attribute__((unavailable("new not available, call sharedInstance instead")));
@end
#import "MySingleton.h"
@implementation MySingleton
+(instancetype) sharedInstance {
static id shared = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [(MySingleton *)[super alloc] initUniqueInstance];
});
return shared;
}
-(instancetype) initUniqueInstance {
return (MySingleton *)[super init];
}
@endИспользование шаблона предотвратит возможные многочисленные ошибки.
Порядок объявления переменных:
- IBOutlets (strong, потом weak)
- Объекты внутренних классов
- Объекты UIKit (UIImage, UINib))
- Объекты Foundation (NSString, NSArray)
- Простые типы данных (NSInteger, BOOL)
- Переменные
- Свойства
- IBActions
- Публичные методы
- dealloc
- init-методы
- Методы жизненного цикла UIViewController'a в порядке их вызова (viewDidLoad, viewWillAppear)
- Сеттеры / геттеры
- IBActions
- Методы делегатов и другие переопределенные методы
- Остальные методы
Логические блоки кода разделяйте #pragma mark - <Название блока>
Хорошо:
- (void)dealloc {}
- (instancetype)init {}
#pragma mark - Lifecycle
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}Физическая структура файлов должна соответствовать логической структуре файлов в Xcode. Любые группы (groups) в Xcode-проекте должны соответствовать папкам в файловой системе. Для большей ясности группируйте код не только по типу, но и по функциональности.
Всегда, при возможности, включайте "Treat Warnings as Errors" в настройках (Build Settings) таргета и включайте как можно больше дополнительных warning-ов. Если вам необходимо проигнорировать какой то конкретный warning, используйте Clang's pragma feature.