Это руководство по стилю кода, основанное на руководстве для разработчиков в 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]];
[view backgroundColor].copy;
- Для отсупов используйте 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;
Многострочные методы при вызове нужно выраввнивать по названиям параметров.
Например:
[actions addObject:[EWDocumentAction actionWithTitle:_document.assignmentButtonText
titleSelected:nil
image:_document.assignmentButtonImage
imageSelected:nil
backgroundColor:nil
actionType:EWDocumentActionTypeAssignment]];
Переменные нужно называть максимально информативно. Имена переменных, состоящие из одной буквы, допускаются только для счётчика в цикле 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 о именовании, особенно, если это касается правил управления памятью.
Используйте подробные имена переменных.
Хорошо:
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) {
NYTAdRequestStateInactive,
NYTAdRequestStateLoading
};
Плохо:
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@2x
andRefreshBarButtonItemSelected
/RefreshBarButtonItemSelected@2x
ArticleNavigationBarWhite
/ArticleNavigationBarWhite@2x
andArticleNavigationBarBlackSelected
/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
Использование шаблона предотвратит возможные многочисленные ошибки.
Блоки импортов и ссылок на классы/протоколы должны быть расположены в алфавитном порядке.
По максимуму избегайте добавление import'ов в заголовочных файлах. Использование испортов влечёт за собой неявные зависомости, когда класс используется в другом классе, но через импорт в заголовочного файла в третьем классе.
Порядок расположения импортов в .m файле:
- Заголовочный файл текущего класса
- Одна пустая строка
- Заголовочные файлы сторонних библиотек и фреймворков в алфавитном порядке
- Одна пустая строка
- Заголовочные файлы остальных классов в алфавитном порядке
- Две пустые строки
Хорошо:
@class A;
@class B;
@class C;
@protocol D;
#import "class.h"
#import <AAA/AAA.h>;
#import "A.h";
#import "B.h";
#import "C.h";
#import "D.h";
#import "E.h";
Плохо:
#import <AAA/AAA.h>;
#import "A.h";
#import "B.h";
#import "C.h";
#import "D.h";
#import "class.h";
Порядок объявления переменных:
- IBOutlets (strong, потом weak)
- Объекты внутренних классов
- Объекты UIKit (UIImage, UINib))
- Объекты Foundation (NSString, NSArray)
- Простые типы данных (NSInteger, BOOL)
- Переменные
- Свойства
- Публичные методы
- dealloc
- init-методы
- Методы жизненного цикла UIViewController'a в порядке их вызова (viewDidLoad, viewWillAppear)
- Сеттеры / геттеры
- Actions
- Методы делегатов и другие переопределенные методы
- Остальные методы
Логические блоки кода разделяйте // MARK: - <Название блока>
, до и после // MARK:
добавляйте две пустых строки.
Хорошо:
- (void)dealloc {}
- (instancetype)init {}
// MARK: - Lifecycle
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
// MARK: - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
// MARK: - Actions
- (IBAction)submitData:(id)sender {}
// MARK: - Protocol Conformance
// MARK: UITextFieldDelegate
// MARK: UITableViewDataSource
// MARK: UITableViewDelegate
// MARK: - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
// MARK: - NSObject
- (NSString *)description {}
// MARK: - Public
- (void)publicMethod {}
// MARK: - Private
- (void)privateMethod {}
Физическая структура файлов должна соответствовать логической структуре файлов в Xcode. Любые группы (groups) в Xcode-проекте должны соответствовать папкам в файловой системе. Для большей ясности группируйте код не только по типу, но и по функциональности.
Всегда, при возможности, включайте "Treat Warnings as Errors" в настройках (Build Settings) таргета и включайте как можно больше дополнительных warning-ов. Если вам необходимо проигнорировать какой то конкретный warning, используйте Clang's pragma feature.