Skip to content

DigDes/objective-c-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 

Repository files navigation

Objective-C Style Guide

Это руководство по стилю кода, основанное на руководстве для разработчиков в The New York Times(https://github.com/NYTimes/objective-c-style-guide/contributors)

Введение

Согласованность с этим руководством очень важна. Согласованность внутри одного проекта еще важнее. А согласованность внутри модуля или функции — самое важное. Но важно помнить, что иногда это руководство неприменимо, и понимать, когда можно отойти от рекоммендаций.

Две причины, чтобы нарушить правила:

  • Когда применение правила сделает код менее читабельным даже для того, кто привык читать код, который следует правилам.
  • Чтобы писать в едином стиле с кодом, который уже есть в проекте и который нарушает правила (может быть, в силу исторических причин) — впрочем, это возможность подчистить чужой код.

Ниже представлены документы Apple по этой же теме:

Содержание

Использование точек

Используйте точки для доступа к свойствам объектов и классов. Во всех остальных случаях используйте квадратные скобки.

Хорошо:

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];

CGRect Functions

Вместо прямого доступа к полям 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 свойства

Используйте 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 and RefreshBarButtonItemSelected / RefreshBarButtonItemSelected@2x
  • ArticleNavigationBarWhite / ArticleNavigationBarWhite@2x and ArticleNavigationBarBlackSelected / 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.h

@class A;
@class B;
@class C;

@protocol D;

class.m

#import "class.h"

#import <AAA/AAA.h>;

#import "A.h";
#import "B.h";
#import "C.h";
#import "D.h";
#import "E.h";

Плохо:

class.h

#import <AAA/AAA.h>;
#import "A.h";
#import "B.h";
#import "C.h";
#import "D.h";

class.m

#import "class.h";

Предпочтительная структура файлов

Порядок объявления переменных:

  • IBOutlets (strong, потом weak)
  • Объекты внутренних классов
  • Объекты UIKit (UIImage, UINib))
  • Объекты Foundation (NSString, NSArray)
  • Простые типы данных (NSInteger, BOOL)

.h

  • Переменные
  • Свойства
  • Публичные методы

.m

  • 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-проект

Физическая структура файлов должна соответствовать логической структуре файлов в Xcode. Любые группы (groups) в Xcode-проекте должны соответствовать папкам в файловой системе. Для большей ясности группируйте код не только по типу, но и по функциональности.

Всегда, при возможности, включайте "Treat Warnings as Errors" в настройках (Build Settings) таргета и включайте как можно больше дополнительных warning-ов. Если вам необходимо проигнорировать какой то конкретный warning, используйте Clang's pragma feature.

Другие руководства по стилю кода Objective-C