mirror of
https://github.com/wnagrodzki/CocoaProgrammingGuidelines.git
synced 2025-05-03 01:22:08 +02:00
1207 lines
35 KiB
TeX
1207 lines
35 KiB
TeX
% XeLaTeX can use any Mac OS X font. See the setromanfont command below.
|
||
% Input to XeLaTeX is full Unicode, so Unicode characters can be typed directly into the source.
|
||
|
||
% The next lines tell TeXShop to typeset with xelatex, and to open and save the source with Unicode encoding.
|
||
|
||
%!TEX TS-program = xelatex
|
||
%!TEX encoding = UTF-8 Unicode
|
||
|
||
|
||
% Apple Documentation Style - Latex Template
|
||
% https://github.com/izydor86/AppleDocumentationStyleLatexTemplate
|
||
% Wojciech Nagrodzki 2013
|
||
|
||
|
||
\documentclass[10pt]{extarticle}
|
||
\usepackage[margin=2cm]{geometry} % See geometry.pdf to learn the layout options. There are lots.
|
||
\geometry{a4paper} % ... or a4paper or a5paper or ...
|
||
\usepackage[parfill]{parskip} % Activate to begin paragraphs with an empty line rather than an indent
|
||
\usepackage{titlesec}
|
||
\usepackage{color}
|
||
\usepackage{hyperref}
|
||
\usepackage{verbatim}
|
||
\usepackage[framemethod=TikZ]{mdframed}
|
||
\usepackage{hyperref}
|
||
\usepackage{tabto}
|
||
\usepackage{minted}
|
||
|
||
|
||
% Define section, subsection and subsubsection font size and color
|
||
\definecolor{AllportsColor}{HTML}{0769AE}
|
||
|
||
\titleformat*{\section}
|
||
{\huge}
|
||
|
||
\titleformat*{\subsection}
|
||
{\color{AllportsColor}\LARGE}
|
||
|
||
\titleformat*{\subsubsection}
|
||
{\color{AllportsColor}\large}
|
||
|
||
|
||
% Define the colors of table of contents
|
||
% This is helpful to understand http://tex.stackexchange.com/questions/110253/what-the-first-argument-for-lsubsection-actually-is
|
||
\definecolor{LochmaraColor}{HTML}{0C88CC}
|
||
|
||
\makeatletter
|
||
|
||
\renewcommand{\l@section}[2]{\vspace{14pt}\@dottedtocline{2}{0pt}{0pt}{\bfseries\textcolor{LochmaraColor}{#1}}{#2}}
|
||
\renewcommand{\l@subsection}[2]{\@dottedtocline{2}{0pt}{0pt}{\textcolor{LochmaraColor}{#1}}{#2}}
|
||
\renewcommand{\l@subsubsection}[2]{\@dottedtocline{2}{20pt}{0pt}{\textcolor{LochmaraColor}{#1}}{#2}}
|
||
|
||
\renewcommand{\@dotsep}{10000}
|
||
|
||
\makeatother
|
||
|
||
|
||
% Insert 22pt white space before roc title. \titlespacing at line 65 changes it by -22 later on.
|
||
\renewcommand*\contentsname{\hspace{22pt}Contents}
|
||
|
||
|
||
% Remove numbers before sections, it does not remove a spacing from the left side of section
|
||
% Look below to see how I remove them
|
||
\renewcommand\thesection{}
|
||
\renewcommand\thesubsection{}
|
||
\renewcommand\thesubsubsection{}
|
||
|
||
|
||
% Define a spacing for section, subsection and subsubsection
|
||
% http://tex.stackexchange.com/questions/108684/spacing-before-and-after-section-titles
|
||
\titlespacing*{\section}
|
||
{-22pt}{0pt plus 0pt minus 0pt}{40pt plus 0pt minus 0pt}
|
||
\titlespacing*{\subsection}
|
||
{-18pt}{42pt plus 64pt minus 0pt}{0pt}
|
||
\titlespacing*{\subsubsection}
|
||
{-13pt}{12pt plus 0pt minus 0pt}{0pt}
|
||
|
||
|
||
% Section begins on new page
|
||
\newcommand{\sectionbreak}{\clearpage}
|
||
|
||
|
||
% Line spacing
|
||
\linespread{1.3}
|
||
|
||
|
||
% Lists a code inside of a blue border
|
||
\definecolor{BaliHaiColor}{HTML}{7CA1B3}
|
||
\newminted[codelisting]{objective-c}{tabsize=4, fontsize=\footnotesize}
|
||
\usemintedstyle{xcode}
|
||
\BeforeBeginEnvironment{codelisting}{\footnotesize\mdframed[middlelinewidth=0.5pt, middlelinecolor=BaliHaiColor, skipabove=15pt]}%
|
||
\AfterEndEnvironment{codelisting}{\endmdframed\vspace{12pt}\normalsize}
|
||
|
||
|
||
% Lists text inside yellow border
|
||
\definecolor{GoldenDreamColor}{HTML}{F2D138}
|
||
\newenvironment{tiplisting}
|
||
{\small\mdframed[middlelinewidth=0.5pt, middlelinecolor=GoldenDreamColor, skipabove=15pt]{\textbf{Tip:}}}
|
||
{\endmdframed\vspace{12pt}\normalsize}
|
||
|
||
|
||
% Lists text inside a blue border
|
||
\definecolor{MatisseColor}{HTML}{236AB2}
|
||
\newenvironment{importantlisting}
|
||
{\mdframed[middlelinewidth=0.5pt, middlelinecolor=MatisseColor, skipabove=15pt]{\textbf{Important:}}}
|
||
{\endmdframed\vspace{12pt}}
|
||
|
||
|
||
% Inline monospaced text
|
||
\definecolor{TundoraColor}{HTML}{444444}
|
||
\newcommand{\inlinecode}[1]{{\textcolor{TundoraColor}{\texttt{#1}}}}
|
||
|
||
|
||
% Hyperlinks
|
||
\hypersetup{
|
||
colorlinks = true,
|
||
allcolors = {LochmaraColor},
|
||
}
|
||
|
||
|
||
% Will Robertson's fontspec.sty can be used to simplify font choices.
|
||
% To experiment, open /Applications/Font Book to examine the fonts provided on Mac OS X,
|
||
% and change "Hoefler Text" to any of these choices.
|
||
|
||
\usepackage{fontspec,xltxtra,xunicode}
|
||
\defaultfontfeatures{Mapping=tex-text}
|
||
\setromanfont[Mapping=tex-text]{Lucida Grande}
|
||
\setsansfont[Scale=MatchLowercase,Mapping=tex-text]{Gill Sans}
|
||
\setmonofont[Scale=MatchLowercase]{Menlo Regular}
|
||
|
||
|
||
\begin{document}
|
||
|
||
\begin{titlepage}
|
||
\begin{center}
|
||
\Huge Cocoa Programming Guidelines for Objective-C
|
||
|
||
\vspace{10mm}
|
||
\large Wojciech Nagrodzki\\ \today
|
||
|
||
\vspace{70mm}
|
||
\includegraphics[width=70mm]{cocoa_cup.jpg}
|
||
|
||
\vspace{70mm}
|
||
Special thanks to: Krzysztof Profic, Bartłomiej Hyży, Grzegorz Wikiera,\\Piotr Węgrzynek and Daniel Garbień
|
||
|
||
|
||
\end{center}
|
||
\end{titlepage}
|
||
|
||
|
||
\tableofcontents
|
||
|
||
\section{Code Appearance}
|
||
|
||
|
||
Hardly any software is maintained by its original author for its whole life. Writing code in an unified way helps engineers to understand it more quickly.
|
||
|
||
|
||
\subsection{Class names are not prefixed usually}
|
||
|
||
Prefixes are used only for classes which are to be shared between applications.
|
||
|
||
|
||
\subsection{Protocol names indicates behaviour}
|
||
Most protocols group related methods that aren’t associated with any class in particular. This type of protocol should be named so that the protocol won’t be confused with a class. A common convention is to use a gerund (“...ing”) form:
|
||
|
||
\begin{itemize}
|
||
\item NSLocking \tabto{3cm} Good
|
||
\item NSLock \tabto{3cm} Poor (seems like a name for a class)
|
||
\end{itemize}
|
||
|
||
Some protocols group a number of unrelated methods (rather than create several separate small protocols). These protocols tend to be associated with a class that is the principal expression of the protocol. In these cases, the convention is to give the protocol the same name as the class. An example of this sort of protocol is the \inlinecode{NSObject} protocol.
|
||
|
||
|
||
\subsection{Header files follow a structure}
|
||
|
||
Each header file should be succinct, and comply with the following declaration's order:
|
||
|
||
\begin{enumerate}
|
||
\item Imports
|
||
\item Forward class declarations
|
||
\item Enumeration types
|
||
\item Constants
|
||
\item Notification names and its user info dictionary keys
|
||
\item Delegate protocol
|
||
\item Data source protocol
|
||
\item Class interface
|
||
\end{enumerate}
|
||
|
||
A class interface has a fixed structure as well:
|
||
|
||
\begin{enumerate}
|
||
\item Properties
|
||
\item Data source property
|
||
\item Delegate property
|
||
\item Class methods
|
||
\item Instance methods
|
||
\end{enumerate}
|
||
|
||
Please pay attention to the linespacing, it is also a rule.
|
||
|
||
\begin{codelisting}
|
||
#import <Foundation/Foundation.h>
|
||
|
||
@class ExampleClass;
|
||
|
||
|
||
typedef NS_ENUM(NSInteger, Enumeration) {
|
||
EnumerationA = 1,
|
||
EnumerationB
|
||
};
|
||
|
||
extern CGFloat const ExampleClassDefaultHeight;
|
||
|
||
extern NSString * const ExampleClassWillPerformActionNotification;
|
||
extern NSString * const ExampleClassDidPerformActionNotification;
|
||
extern NSString * const ExampleClassActionNameKey;
|
||
|
||
|
||
@protocol ExampleClassDelegate <NSObject>
|
||
|
||
- (void)exampleClass:(ExampleClass *)exampleClass didPerformAction:(Action *)action;
|
||
|
||
@optional
|
||
|
||
- (void)exampleClass:(ExampleClass *)exampleClass willPerformAction:(Action *)action;
|
||
|
||
@end
|
||
|
||
|
||
@protocol ExampleClassDataSource <NSObject>
|
||
|
||
- (NSInteger)exampleClassNumberOfActions:(ExampleClass *)exampleClass;
|
||
|
||
@end
|
||
|
||
|
||
@interface ExampleClass : NSObject
|
||
|
||
@property (strong, nonatomic) NSURL * initialProperty;
|
||
@property (weak, nonatomic) id<ExampleClassDataSource> dataSource;
|
||
@property (weak, nonatomic) id<ExampleClassDelegate> delegate;
|
||
|
||
+ (id)exampleClassWithInitialProperty:(NSURL *)initialProperty;
|
||
- (id)initWithInitialProperty:(NSURL *)initialProperty;
|
||
- (void)performAction;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Implementation files are divided by pragma marks}
|
||
|
||
Methods in an implementation file appear in the same order as they are declared in the header. They are also grouped by pragma marks similar to the following:
|
||
|
||
\begin{codelisting}
|
||
#pragma mark - Public Properties
|
||
#pragma mark - Public Class Methods
|
||
#pragma mark - Public Instance Methods
|
||
#pragma mark - IBActions
|
||
#pragma mark - Overridden
|
||
#pragma mark - Private Properties
|
||
#pragma mark - Private Class Methods
|
||
#pragma mark - Private Instance Methods
|
||
#pragma mark - Protocols
|
||
#pragma mark - Notifications
|
||
\end{codelisting}
|
||
|
||
In need of greater granularity:
|
||
|
||
\begin{codelisting}
|
||
#pragma mark - Overridden (UIView)
|
||
#pragma mark - Overridden (UIContainerViewControllerCallbacks)
|
||
#pragma mark - Overridden (UIViewControllerRotation)
|
||
\end{codelisting}
|
||
|
||
\subsection{Property attributes are kept in an order}
|
||
The attributes are kept in the same order through all property declarations.
|
||
|
||
\begin{codelisting}
|
||
[assign | weak | strong | copy] + [nonatomic | atomic] + [readonly | readwrite] + [getter = ]
|
||
\end{codelisting}
|
||
|
||
None of the attributes can be omitted with the exception of readwrite.
|
||
|
||
\begin{codelisting}
|
||
@property (assign, nonatomic) CGFloat height;
|
||
@property (strong, nonatomic) UIColor * color;
|
||
@property (copy, nonatomic) NSString * name;
|
||
@property (weak, nonatomic) id <UITableViewDelegate> delegate;
|
||
\end{codelisting}
|
||
|
||
Getters for boolean properties, if they are adjectives, are renamed in the following manner:
|
||
|
||
\begin{codelisting}
|
||
@property (assign, nonatomic, getter = isVisible) BOOL visible;
|
||
@property (assign, nonatomic, getter = isEnabled) BOOL enabled;
|
||
@property (assign, nonatomic, getter = isTracking) BOOL tracking
|
||
\end{codelisting}
|
||
|
||
\begin{importantlisting}
|
||
The copy attribute is often used with NSString, NSArray or NSDictionary to preserve encapsulation, since the value passed into the setter might be an instance of mutable subclass.
|
||
\end{importantlisting}
|
||
|
||
|
||
\subsection{Protocols and constants are prefixed with a class name}
|
||
|
||
Protocols, notification names, enumeration types and other constants that refer to a particular class are prefixed with the name of that class.
|
||
|
||
\begin{codelisting}
|
||
typedef NS_ENUM(NSInteger, UITableViewStyle) {
|
||
UITableViewStylePlain,
|
||
UITableViewStyleGrouped
|
||
};
|
||
|
||
UIKIT_EXTERN NSString *const UITableViewIndexSearch;
|
||
|
||
UIKIT_EXTERN const CGFloat UITableViewAutomaticDimension;
|
||
|
||
UIKIT_EXTERN NSString *const UITableViewSelectionDidChangeNotification;
|
||
|
||
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>
|
||
...
|
||
\end{codelisting}
|
||
|
||
\subsection{IBOutlets are declared privately}
|
||
|
||
Outlets are defined as weak properties at the top of a class extension, and divided by one line from the other properties.
|
||
|
||
\begin{codelisting}
|
||
@interface PanelViewController ()
|
||
|
||
@property (weak, nonatomic) IBOutlet UIButton * infoButton;
|
||
@property (weak, nonatomic) IBOutlet UIButton * closeButton;
|
||
@property (weak, nonatomic) IBOutlet UILabel * descriptionLabel;
|
||
|
||
@property (weak, nonatomic) UIView * overlayView;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Classes may have additional header file for subclasses}
|
||
|
||
Private methods and properties are never exposed. To provide a subclass access to them, they have to be declared in a category entitled ForSubclassEyesOnly. It should be placed in a separate header file, named in the following manner: [Class Name] + Subclass.h.
|
||
|
||
\begin{tiplisting}
|
||
UIGestureRecognizerSubclass.h is a good example of that approach.
|
||
\end{tiplisting}
|
||
|
||
|
||
\subsection{Methods return early and often}
|
||
|
||
A nesting usually makes code harder to read.
|
||
|
||
\begin{codelisting}
|
||
- (BOOL)loginUser:(NSString *)user withPassword:(NSString *)password
|
||
{
|
||
if (user.length >= 6) {
|
||
if ((password.length >= 8) {
|
||
// the actual logging code
|
||
}
|
||
}
|
||
return NO;
|
||
}
|
||
\end{codelisting}
|
||
|
||
Getting invalid cases out of the way first will keep the working code with one level of indentation, and ensure that all parameters are valid. This paradigm is called The Golden Path.
|
||
|
||
\begin{codelisting}
|
||
- (BOOL)loginUser:(NSString *)user withPassword:(NSString *)password
|
||
{
|
||
if (user.length < 6) return NO;
|
||
if (password.length < 8) return NO;
|
||
|
||
// actual logging code
|
||
}
|
||
\end{codelisting}
|
||
|
||
This approach has one more advantage, it is easier to add an error handling later on.
|
||
|
||
\begin{codelisting}
|
||
- (BOOL)loginUser:(NSString *)user withPassword:(NSString *)password error:(NSError **)error
|
||
{
|
||
if (user.length < 6) {
|
||
if (error != NULL)
|
||
*error = [NSError errorWithDomain:ExampleDomain
|
||
code:1001
|
||
userInfo:@{NSLocalizedDescriptionKey: @"User has to..."}];
|
||
return NO;
|
||
}
|
||
|
||
if (password.length < 8) {
|
||
if (error != NULL)
|
||
*error = [NSError errorWithDomain:ExampleDomain
|
||
code:1002
|
||
userInfo:@{NSLocalizedDescriptionKey: @"Password has to..."}];
|
||
return NO;
|
||
}
|
||
|
||
// actual logging code
|
||
}
|
||
\end{codelisting}
|
||
|
||
\subsection{Methods are not bisected with conditionals}
|
||
|
||
The following method structure is not acceptable.
|
||
|
||
\begin{codelisting}
|
||
- (void)method
|
||
{
|
||
if (self.valueX == 10) {
|
||
// perform some actions
|
||
}
|
||
else {
|
||
// perform some other actions
|
||
}
|
||
}
|
||
\end{codelisting}
|
||
|
||
Bisection can be removed by returning within an if statement.
|
||
|
||
\begin{codelisting}
|
||
- (void)method
|
||
{
|
||
if (self.valueX == 10) {
|
||
// perform some actions
|
||
return;
|
||
}
|
||
|
||
// perform some other actions
|
||
}
|
||
\end{codelisting}
|
||
|
||
In one case bisection is allowed.
|
||
|
||
\begin{codelisting}
|
||
- (void)method
|
||
{
|
||
if (self.valueX == 10) {
|
||
// perform some actions
|
||
}
|
||
else {
|
||
// perform some other actions
|
||
}
|
||
|
||
// perform some actions no matter what
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Methods can be prefixed with get}
|
||
|
||
Get prefix is used only in situations when a value is returned indirectly via a memory address.
|
||
|
||
\begin{codelisting}
|
||
@interface NSValue : NSObject <NSCopying, NSSecureCoding>
|
||
|
||
- (void)getValue:(void *)value;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
|
||
\section{General rules}
|
||
|
||
\subsection{Header files are imported only if necessary}
|
||
|
||
Importing a header file is allowed:
|
||
|
||
\begin{itemize}
|
||
\item if class needs to conform to a protocol (header with protocol declaration)
|
||
\item if class is inheriting from another class (header with superclass declaration)
|
||
\item if class uses enums in its interface (header with enums declarations)
|
||
\end{itemize}
|
||
|
||
In any other case a forward declaration should be applied.
|
||
|
||
|
||
\subsection{Enums do not start with zero value}
|
||
|
||
The rule guards against retrieving a proper enum value from nil object.
|
||
|
||
\begin{codelisting}
|
||
typedef NS_ENUM(NSUInteger, PaymentMethod) {
|
||
PaymentMethodCash = 1,
|
||
PaymentMethodCreditCard,
|
||
PaymentMethodCheque,
|
||
};
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Switch statements are exhaustive}
|
||
|
||
Assertion is triggered if invalid enum value is encountered.
|
||
|
||
\begin{codelisting}
|
||
switch (order.paymentMethod)
|
||
{
|
||
case PaymentMethodCash:
|
||
break;
|
||
|
||
case PaymentMethodCreditCard:
|
||
break;
|
||
|
||
case PaymentMethodCheque:
|
||
break;
|
||
|
||
default:
|
||
NSAssert(NO, @"Invalid enum value");
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Delegate methods always pass the sender}
|
||
|
||
A delegation method passes the sender as the first parameter. It is a good practice to use will/did paradigm.
|
||
|
||
\begin{codelisting}
|
||
- (NSInteger)exampleClassNumberOfActions:(ExampleClass *)exampleClass
|
||
- (void)exampleClass:(ExampleClass *)exampleClass willPerformAction:(Action *)action;
|
||
- (void)exampleClass:(ExampleClass *)exampleClass didPerformAction:(Action *)action;
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Property's default values are documented conditionally}
|
||
|
||
The \inlinecode{alloc} method clears out the memory allocated for an object’s properties, by setting it to zero. If a property value is not altered during the initialisation, a note about its default value may be omitted.
|
||
|
||
\begin{codelisting}
|
||
/*
|
||
* A Boolean value that determines whether the view is hidden.
|
||
*/
|
||
@property(nonatomic, getter=isHidden) BOOL hidden // represented by zeroed memory
|
||
\end{codelisting}
|
||
|
||
On the contrary, if the default value is changed, a mention about it is included.
|
||
|
||
\begin{codelisting}
|
||
/*
|
||
* Defines the anchor point of the layer's bounds rectangle.
|
||
* The default value of this property is (0.5, 0.5).
|
||
*/
|
||
@property (nonatomic) CGPoint anchorPoint // not represented by zeroed memory
|
||
\end{codelisting}
|
||
|
||
\subsection{Init methods take only mandatory parameters}
|
||
|
||
All settings required to a proper initialisation are passed as initialiser's parameters. They are later accessible through readonly properties.
|
||
|
||
\begin{codelisting}
|
||
@property (strong, nonatomic, readonly) DownloaderMode downloaderMode;
|
||
|
||
- (id)initWithDownloaderMode:(DownloaderMode)downloaderMode;
|
||
\end{codelisting}
|
||
|
||
\begin{tiplisting}
|
||
If you need a convenience method to create instances, consider creating factory methods.
|
||
\end{tiplisting}
|
||
|
||
|
||
\subsection{Accessors are not used in init and dealloc}
|
||
|
||
Instance subclasses may be in an inconsistent state during \inlinecode{init} and \inlinecode{dealloc} method execution, hence code in those methods must avoid invoking accessors.
|
||
|
||
\begin{codelisting}
|
||
- (id)init
|
||
{
|
||
self = [super init];
|
||
if (self) {
|
||
_foo = [NSMutableSet set];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void)dealloc
|
||
{
|
||
[_titleLabel removeObserver:self forKeyPath:@"text"];
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Abstract classes can be faked with assertion}
|
||
|
||
Creating instances of an abstract class is foiled by the following assertion.
|
||
|
||
\begin{codelisting}
|
||
- (id)init
|
||
{
|
||
self = [super init];
|
||
if (self) {
|
||
NSAssert1([self isMemberOfClass:[MyAbstractClass class]] == NO,
|
||
@"%@ is an abstract class. Please do not create instances of it.", [self class]);
|
||
}
|
||
return self;
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Abstract methods raise exceptions}
|
||
|
||
Forcing a subclass to provide an implementation of a method is accomplished by raising an exception in superclass method.
|
||
|
||
\begin{codelisting}
|
||
- (void)abstractMethod
|
||
{
|
||
[NSException raise:NSInternalInconsistencyException
|
||
format:@"It's template method. Implementation must be provided in subclass."];
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{The highest level of abstraction is used by default}
|
||
|
||
Lower levels are used only when more control is required. For example, instead using GCD:
|
||
|
||
\begin{codelisting}
|
||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||
// code
|
||
}
|
||
\end{codelisting}
|
||
|
||
Use an operation queue.
|
||
|
||
\begin{codelisting}
|
||
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
||
// code
|
||
}];
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Exceptions are not used to control flow}
|
||
|
||
The general pattern is that exceptions are reserved for programming or unexpected runtime errors, such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, or losing the connection to the window server. A program catching such exception should quit soon afterwards. Exceptions must not be used to control flow in favor of \inlinecode{NSError} objects.
|
||
When developing a class or a framework exceptions are thrown to indicate that class or framework are being misused:
|
||
|
||
\begin{codelisting}
|
||
- (void)abstractMethod
|
||
{
|
||
[NSException raise:NSInternalInconsistencyException
|
||
format:@"It's template method, you need to implement it in your subclass"];
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Lazy loading reduces memory footprint}
|
||
|
||
Creating an object on demand reduces initialisation time of a containing class. The following getter implementation is not thread safe, as two threads might try to initialise \inlinecode{\_cacheDictionary} at the same time.
|
||
|
||
\begin{codelisting}
|
||
- (NSMutableDictionary *)cacheDictionary
|
||
{
|
||
if (_cacheDictionary == nil) {
|
||
_cacheDictionary = [NSMutableDictionary dictionary];
|
||
}
|
||
return _cacheDictionary;
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{No object register other objects as observers}
|
||
|
||
Object registers only itself as an observer.
|
||
|
||
\begin{codelisting}
|
||
[obj addObserver:self forKeyPath:@"isExecuting" options:NSKeyValueObservingOptionNew context:NULL];
|
||
\end{codelisting}
|
||
|
||
Unregistering follows the same rule.
|
||
|
||
\begin{codelisting}
|
||
[obj removeObserver:self forKeyPath:@"isExecuting" context:NULL];
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Methods do not return NSError object}
|
||
|
||
A failure is indicated by \inlinecode{nil} or \inlinecode{NO} returned by a method. Success similarly by \inlinecode{YES} or not-nil pointer.
|
||
|
||
\begin{codelisting}
|
||
- (id)initWithContentsOfURL:(NSURL *)aURL;
|
||
- (BOOL)writeToURL:(NSURL *)aURL atomically:(BOOL)atomically;
|
||
\end{codelisting}
|
||
|
||
\inlinecode{NSError} object is used only for providing additional information about a failure.
|
||
|
||
\begin{codelisting}
|
||
- (id)initWithContentsOfURL:(NSURL *)aURL options:(NSDataReadingOptions)mask error:(NSError **)errorPtr;
|
||
- (BOOL)writeToURL:(NSURL *)aURL options:(NSDataWritingOptions)mask error:(NSError **)errorPtr
|
||
\end{codelisting}
|
||
|
||
Therefore you should always check if the return value is \inlinecode{nil} or \inlinecode{NO} before attempting to do anything with an \inlinecode{NSError} object. Moreover, you ought to check the domain before examining the error code.
|
||
|
||
|
||
\subsection{Custom errors belong to error domains}
|
||
|
||
A custom error have both the error domain and error code defined.
|
||
|
||
\begin{codelisting}
|
||
extern NSString * const MyErrorDomain;
|
||
|
||
enum : NSUInteger
|
||
{
|
||
MyErrorCode1,
|
||
MyErrorCode2,
|
||
MyUnknownErrorCode,
|
||
};
|
||
\end{codelisting}
|
||
|
||
Both parameters including localised description are used during an error initialisation.
|
||
|
||
\begin{codelisting}
|
||
if (error != NULL) {
|
||
if(error_situation_1) {
|
||
*error = [NSError errorWithDomain:MyErrorDomain
|
||
code:MyErrorCode1
|
||
userInfo:@{NSLocalizedDescriptionKey: @"Description of error 1"}];
|
||
}
|
||
else if (error_situation_2) {
|
||
*error = [NSError errorWithDomain:MyErrorDomain
|
||
code:MyErrorCode2
|
||
userInfo:@{NSLocalizedDescriptionKey: @"Description of error 2"}];
|
||
}
|
||
else {
|
||
*error = [NSError errorWithDomain:MyErrorDomain
|
||
code:MyUnknownErrorCode
|
||
userInfo:@{NSLocalizedDescriptionKey: @"Unknown error"}];
|
||
}
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Properties can be added to existing classes}
|
||
|
||
A property is added to an existing class by using associated objects. Please pay attention to the way the key is defined.
|
||
|
||
\begin{codelisting}
|
||
static void * const navigationItemKey = (void *)&navigationItemKey;
|
||
|
||
- (void)setNavigationItem:(UINavigationItem *)navigationItem
|
||
{
|
||
objc_setAssociatedObject(self,
|
||
navigationItemKey,
|
||
navigationItem,
|
||
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||
}
|
||
\end{codelisting}
|
||
|
||
\begin{codelisting}
|
||
- (UINavigationItem *)navigationItem
|
||
{
|
||
return objc_getAssociatedObject(self, navigationItemKey);
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Object's equality requites three methods to be provided}
|
||
|
||
\inlinecode{isEqualToYourClassName:} provides the actual logic and works only with the instances of receiver's class. Passing an object of any other class triggers the assertion. \inlinecode{isEqual:} works with any object. \inlinecode{hash:} usually XORs the hash values of the properties.
|
||
|
||
\begin{codelisting}
|
||
- (BOOL)isEqualToPerson:(Person *)person
|
||
{
|
||
if (person == nil) return NO;
|
||
|
||
NSParameterAssert([object isMemberOfClass:[Person class]]);
|
||
|
||
return ((self.name == nil && person.name == nil) || [self.name isEqual:person.name]) &&
|
||
((self.weight == nil && person.weight == nil) || [self.weight isEqual:person.weight]) &&
|
||
((self.age == nil && person.age == nil) || [self.age isEqual:person.age]);
|
||
}
|
||
|
||
- (BOOL)isEqual:(id)object
|
||
{
|
||
if ([self isMemberOfClass:[object class]]) return [self isEqualToPerson:object];
|
||
return NO;
|
||
}
|
||
|
||
- (NSUInteger)hash
|
||
{
|
||
return self.name.hash ^ self.surname.hash ^ self.age.hash;
|
||
}
|
||
\end{codelisting}
|
||
|
||
If mutable subclass has to be provided, all the occurrences of \inlinecode{isMemberOfClass:} should be replaced with \inlinecode{isKindOfClass:}.
|
||
|
||
|
||
\section{Concurrency}
|
||
|
||
Recommended reading \href{http://www.objc.io/issue-2/}{Concurrent Programming - objc.io}
|
||
|
||
\begin{importantlisting}
|
||
To avoid priority inversion problems with Grand Central Dispatch use default queue priority DISPATCH\_QUEUE\_PRIORITY\_DEFAULT in almost all cases.
|
||
\end{importantlisting}
|
||
|
||
|
||
\subsection{Prefer dispatch queues to locks for mutual exclusion}
|
||
|
||
Critical sections of the code are isolated by a serial dispatch queue. It allows the calling thread to continue execution in contrast to \inlinecode{NSLock} and \inlinecode{@synchronized} directive.
|
||
|
||
\begin{codelisting}
|
||
- (id)init
|
||
{
|
||
self = [super init];
|
||
if (self) {
|
||
NSString * label = [NSString stringWithFormat:@"%@.isolationQueue.%p", [self class], self];
|
||
_isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_SERIAL);
|
||
}
|
||
return self;
|
||
}
|
||
\end{codelisting}
|
||
|
||
The above mentioned queue label helps finding the owning object of the queue while debugging.
|
||
|
||
\begin{codelisting}
|
||
- (void)startDownloading
|
||
{
|
||
dispatch_async(self.isolationQueue, ^{
|
||
// critical code section A
|
||
}
|
||
}
|
||
|
||
- (void)cancelDownloading
|
||
{
|
||
dispatch_async(self.isolationQueue, ^{
|
||
// critical code section B
|
||
}
|
||
}
|
||
\end{codelisting}
|
||
|
||
\begin{importantlisting}
|
||
The queue is not created lazily as it would require the getter to be thread safe.
|
||
\end{importantlisting}
|
||
|
||
|
||
\subsection{Multiple readers one writer}
|
||
|
||
A concurrent dispatch queue is used to synchronise access to a property in an efficient way.
|
||
|
||
\begin{codelisting}
|
||
- (id)init
|
||
{
|
||
self = [super init];
|
||
if (self) {
|
||
NSString * queueLabel = [NSString stringWithFormat:@"%@.syncQueue.%p", [self class], self];
|
||
_syncQueue = dispatch_queue_create([queueLabel UTF8String], DISPATCH_QUEUE_CONCURRENT);
|
||
}
|
||
return self;
|
||
}
|
||
\end{codelisting}
|
||
|
||
Dispatch barrier async runs the block after all previously scheduled blocks are completed and before any following blocks are run.
|
||
|
||
\begin{codelisting}
|
||
- (void)setObject:(id)anObject forKey:(id <NSCopying>)aKey
|
||
{
|
||
aKey = [aKey copyWithZone:NULL];
|
||
dispatch_barrier_async(self.syncQueue, ^{
|
||
[self.mutableDictionary setObject:anObject forKey:aKey];
|
||
});
|
||
}
|
||
|
||
- (id)objectForKey:(id)aKey
|
||
{
|
||
__block id object;
|
||
dispatch_sync(self.syncQueue, ^{
|
||
object = [self.mutableDictionary objectForKey:aKey];
|
||
});
|
||
return object;
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{NSOperation can be cancelled before it begins execution}
|
||
|
||
The main method checks if an operation is cancelled at the very beginning, and interrupts execution if condition is true.
|
||
|
||
\begin{codelisting}
|
||
- (void)main
|
||
{
|
||
if (self.isCancelled) return;
|
||
|
||
// code
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\section{UIView}
|
||
|
||
\subsection{View is usually initialised in two ways}
|
||
|
||
Either by calling \inlinecode{initWithFrame:}, or \inlinecode{initWithCoder:} method when it is unarchived form a nib file. Both situations are covered.
|
||
|
||
\begin{codelisting}
|
||
@interface ExampleView ()
|
||
|
||
@property (strong, nonatomic) UITextField * textField;
|
||
|
||
@end
|
||
|
||
@implementation ExampleView
|
||
|
||
- (id)initWithFrame:(CGRect)frame
|
||
{
|
||
self = [super initWithFrame:frame];
|
||
if (self) {
|
||
[self initializeExampleView];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||
{
|
||
self = [super initWithCoder:aDecoder];
|
||
if (self) {
|
||
[self initializeExampleView];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void)initializeExampleView
|
||
{
|
||
_textField = [[UITextField alloc] init];
|
||
_textField.translatesAutoresizingMaskIntoConstraints = NO;
|
||
[self addSubview:_textField];
|
||
|
||
NSDictionary * views = NSDictionaryOfVariableBindings(_textField);
|
||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-8-[_textField]-8-|"
|
||
options:0
|
||
metrics:nil
|
||
views:views]];
|
||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[_textField]-8-|"
|
||
options:0
|
||
metrics:nil
|
||
views:views]];
|
||
}
|
||
\end{codelisting}
|
||
|
||
Note that we do not implement \inlinecode{encodeWithCoder:} method. \inlinecode{UIView}s and \inlinecode{UIViewController}s does not follow normal serialisation process, their state is persisted in a model.
|
||
|
||
|
||
\subsection{The interface of generic view}
|
||
|
||
The view exposes a minimal set of properties and methods that are required for its configuration.
|
||
If possible, a property is not backed up by an instance variable, but rather by view's or subview's property.
|
||
|
||
\begin{codelisting}
|
||
- (NSString *)name
|
||
{
|
||
return self.nameLabel.text;
|
||
}
|
||
|
||
- (void)setName:(NSString *)name
|
||
{
|
||
self.nameLabel.text = name;
|
||
}
|
||
\end{codelisting}
|
||
|
||
The view is independent of a model. A mapping between view's and model's properties is kept in a category on that view. This way a view controller is kept cleaner.
|
||
|
||
\begin{codelisting}
|
||
@implementation UITableViewCell (Person)
|
||
|
||
- (void)configureWithPerson:(Person *)person
|
||
{
|
||
self.imageView.image = person.photo;
|
||
self.textLabel.text = person.name;
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{The interface of specific view}
|
||
|
||
The specific view is tightly coupled with model. It exposes one method that takes a model object and configures the view.
|
||
|
||
\begin{codelisting}
|
||
@interface PersonTableViewCell : UITableViewCell
|
||
|
||
- (void)configureWithPerson:(Person *)person
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
|
||
\section{UIViewController}
|
||
|
||
\subsection{Properties affecting user interface}
|
||
|
||
It is a common practice to have properties on view controller that influence its view. Putting view adjusting code in the setter is not enough, as view may be not loaded. After it is loaded, view controller should adjust it according to the property. In result it is convenient to create a separate method for adjusting the view as it will be called from the setter and \inlinecode{viewDidLoad} as well.
|
||
|
||
\begin{codelisting}
|
||
- (void)setClient:(Client *)client
|
||
{
|
||
_client = client;
|
||
[self adjustUserInterfaceForClient:client];
|
||
}
|
||
|
||
- (void)viewDidLoad
|
||
{
|
||
[super viewDidLoad];
|
||
[self adjustUserInterfaceForClient:self.client];
|
||
}
|
||
|
||
- (void)adjustUserInterfaceForClient:(Client *)client
|
||
{
|
||
if (self.isViewLoaded == NO) return;
|
||
// code adjusting interface
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{View controller containment}
|
||
|
||
|
||
\subsubsection{Container specific items are provided by a property}
|
||
|
||
|
||
If a container requires its child to have specific properties, it delivers a category on \inlinecode{UIViewController}. The category provides an item which encapsulates all of the needed properties.
|
||
|
||
\begin{codelisting}
|
||
- (UINavigationItem *)navigationItem
|
||
{
|
||
UINavigationItem * navigationItem = objc_getAssociatedObject(self, kNavigationItemKey);
|
||
if (navigationItem == nil) {
|
||
navigationItem = [[UINavigationItem alloc] init];
|
||
self.navigationItem = navigationItem;
|
||
}
|
||
return navigationItem;
|
||
}
|
||
\end{codelisting}
|
||
|
||
Whenever container needs to stay in sync with child's properties, it uses Key Value Observing.
|
||
|
||
|
||
\subsubsection{Container is accessible from contained view controllers}
|
||
|
||
Child view controllers can access the container through a property.
|
||
|
||
\begin{codelisting}
|
||
@interface UIViewController (UINavigationControllerItem)
|
||
|
||
@property(nonatomic,readonly,retain) UINavigationController *navigationController;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
The following getter implementation traverses view controller hierarchy, and returns the closest parent view controller of the container class.
|
||
|
||
\begin{codelisting}
|
||
- (UINavigationController *)navigationController
|
||
{
|
||
UIViewController * controller = self.parentViewController;
|
||
while (controller != nil && [controller class] != [UINavigationController class]) {
|
||
controller = controller.parentViewController;
|
||
}
|
||
return (UINavigationController *)controller;
|
||
}
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{The deallocation problem}
|
||
|
||
The \inlinecode{dealloc} method of \inlinecode{UIViewController} does things that are not safe to do on a secondary thread, thus it must be deallocated on the main thread.
|
||
|
||
\begin{codelisting}
|
||
[anObject asynchronousOperationWithCompletionHandler:^(NSData *result) {
|
||
[self processData:result];
|
||
}];
|
||
\end{codelisting}
|
||
|
||
The foregoing code would cause a problem if implemented in \inlinecode{UIViewController} subclass as the self is retained by the bock. To fix the problem it must be referenced by a weak pointer.
|
||
|
||
\begin{tiplisting}
|
||
More about the deallocation problem can be found in \href{https://developer.apple.com/library/ios/technotes/tn2109}{Technical Note TN2109}
|
||
\end{tiplisting}
|
||
|
||
|
||
\section{Core Data}
|
||
|
||
|
||
\begin{importantlisting}
|
||
NSManagedObject's properties must not be prefixed with "new". Due to @dynamic compiler directive, the following compiler error is suppressed: "property's synthesised getter follows Cocoa naming convention for returning 'owned' objects".
|
||
\end{importantlisting}
|
||
|
||
|
||
\subsection{Category provides helper methods to managed object}
|
||
|
||
\inlinecode{NSManagedObject} subclasses can be generated by Xcode schema editor automatically.
|
||
|
||
\begin{codelisting}
|
||
@interface Person : NSManagedObject
|
||
|
||
@property (nonatomic, retain) NSString * name;
|
||
@property (nonatomic, retain) NSString * surname;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
Therefore, all helper methods are kept in a category in a separate file, otherwise they would be overridden by the editor.
|
||
|
||
\begin{codelisting}
|
||
@interface Person (Additions)
|
||
|
||
- (NSString *)fullName;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
\subsection{Custom objects are stored as transformable attributes}
|
||
|
||
A custom object can be stored by Core Data as a transformable attribute, if it conforms to \inlinecode{NSCoding} protocol. Xcode schema editor uses id for this attributes, which deprives developer of good type checking.
|
||
|
||
\begin{codelisting}
|
||
@interface Person : NSManagedObject
|
||
|
||
@property (nonatomic, retain) id eyeColorTransformableType;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
To ameliorate the situation, a property with the proper class type is provided by a category.
|
||
|
||
\begin{codelisting}
|
||
@interface Person (Additions)
|
||
|
||
@property (strong, nonatomic) UIColor * eyeColor;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
Consequently the original property is never used except by the shadowing property's accessors.
|
||
|
||
\begin{codelisting}
|
||
@implementation Person (Additions)
|
||
|
||
- (void)setEyeColor:(UIColor *)eyeColor
|
||
{
|
||
self.eyeColorTransformableType = eyeColor;
|
||
}
|
||
|
||
- (UIColor *)eyeColor
|
||
{
|
||
return self.eyeColorTransformableType;
|
||
}
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
|
||
\subsection{Objective-C types are stored through NSValue}
|
||
|
||
\inlinecode{NSValue} is a simple container for Objective-C data types. It conforms to \inlinecode{NSCoding} protocol, thus can be stored by Core Data as transformable attribute.
|
||
|
||
\begin{codelisting}
|
||
@interface Figure : NSManagedObject
|
||
|
||
@property (nonatomic, retain) id imaginaryPositionTransformableType; // stores NSValue object
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
A category provides a shadowing property for the convenience.
|
||
|
||
\begin{codelisting}
|
||
struct ComplexNumber {
|
||
float real;
|
||
float imaginary;
|
||
};
|
||
typedef struct ComplexNumber ComplexNumber;
|
||
|
||
extern ComplexNumber const ComplexNumberZero;
|
||
|
||
|
||
@interface Figure (Additions)
|
||
|
||
@property (assign, nonatomic) ComplexNumber imaginaryPosition;
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
The getter initialises the local variable so it does not return uninitialised value, if the shadowed property is \inlinecode{nil}.
|
||
|
||
\begin{codelisting}
|
||
@implementation Figure (Additions)
|
||
|
||
- (ComplexNumber)imaginaryPosition
|
||
{
|
||
ComplexNumber imaginaryPosition = ComplexNumberZero;
|
||
[self.imaginaryPositionTransformableType getValue:&imaginaryPosition];
|
||
return imaginaryPosition;
|
||
}
|
||
\end{codelisting}
|
||
|
||
The setter creates \inlinecode{NSValue} object with the passed value, and stores it.
|
||
|
||
\begin{codelisting}
|
||
- (void)setImaginaryPosition:(ComplexNumber)imaginaryPosition
|
||
{
|
||
NSValue * value = [NSValue valueWithBytes:&imaginaryPosition objCType:@encode(ComplexNumber)];
|
||
self.imaginaryPositionTransformableType = value;
|
||
}
|
||
|
||
@end
|
||
\end{codelisting}
|
||
|
||
|
||
\end{document}
|