Wikimedia Apps/Team/iOS/ObjectiveCStyleGuide

From mediawiki.org
< Wikimedia Apps‎ | Team‎ | iOS
Jump to navigation Jump to search

In addition to the guidelines below, you can find the current canonical examples of our coding style in the WMFCodingStyle.h and WMFCodingStyle.m files.

Influences[edit]

Here are some of the documents that informed the style guide. If something isn't mentioned here, it's probably covered in great detail in one of these:

Dot-Notation Syntax[edit]

Dot-notation should always be used for accessing and mutating properties. Bracket notation is to be used in all other instances.

For example:

view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

Not:

[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

Using dot-notation for instance methods is not encouraged, but is OK if they are cheap and pure, meaning there's little-to-no processing and absolutely zero side effects. Some examples of instance methods which are acceptable to call using dot notation are: -[NSArray count], -[NSString length], -[NSArray firstObject], etc...

Spacing[edit]

  • Indent using 4 spaces. Never indent with tabs. Be sure to set this preference in Xcode.
  • Method braces and other braces (if/else/switch/while etc.) always open on the same line as the statement but close on a new line.
  • Configure Xcode to automatically trim trailing whitespace in the Editing preferences.

For example:

if (user.isHappy) {
//Do something
} else {
//Do something else
}
  • There should be exactly one blank line between methods to aid in visual clarity and organization. Whitespace within methods should separate functionality, but often there should probably be new methods.
  • @synthesize and @dynamic should each be declared on new lines in the implementation.

Conditionals[edit]

Conditional bodies should always use braces even when a conditional body could be written without braces (e.g., it is one line only) to prevent errors. These errors include adding a second line and expecting it to be part of the if-statement. Another, even more dangerous defect may happen where the line "inside" the if-statement is commented out, and the next line unwittingly becomes part of the if-statement. In addition, this style is more consistent with all other conditionals, and therefore more easily scannable.

For example:

if (!error) {
    return success;
}

or

if (!error) { return success; }

Not:

if (!error)
    return success;

or

if (!error) return success;

Golden Path[edit]

When coding with conditionals, the left hand margin of the code should be the "golden" or "happy" path. That is, don't nest if statements. Multiple return statements are ok.

For example:

- (void)someMethod {
    if (![someOther boolValue]){
         return;
    }
    //Do something important
}

Not:

- (void)someMethod {
    if ([someOther boolValue]){
           //Do something important
    }
}

Ternary Operator[edit]

The Ternary operator, ? , should only be used when it increases clarity or code neatness. A single condition is usually all that should be evaluated. Evaluating multiple conditions is usually more understandable as an if statement, or refactored into instance variables.

For example:

result = a > b ? x : y;

or, you can use the ?: operator to simplify ternary statements into a style similar to Ruby and Javascript's || operator behavior:

result = a ?: b;

Not:

result = a > b ? x = c > d ? c : d : y;

Error handling[edit]

When methods return an error parameter by reference, switch on the returned value, not the error variable.

For example:

NSError *error;
if (![self trySomethingWithError:&error]) {
    // Handle Error
}

Not:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
    // Handle Error
}

Some of Apple’s APIs write garbage values to the error parameter (if non-NULL) in successful cases, so switching on the error can cause false negatives (and subsequently crash).

Methods[edit]

Declaration[edit]

In method signatures, there should be a space after the scope (-/+ symbol). There should be a space between the method segments, but not between method arguments and their preceding colon.

For Example:

// in the header:
- (void)setExampleText:(NSString*)text image:(UIImage*)image;
// in use:
[self setExampleText:text image:image];

Not:

// in the header:
-(void)setExampleText: (NSString*)text image: (UIImage*)image
// in use:
[self setExampleText: text image: image];

Definition[edit]

Method definitions should follow the same indentation rules as declaration, with the opening curly brace on the last line of the method signature:

- (void)someMethod {
    // impl...
}

Variables[edit]

Variables should be named as descriptively as possible. Single letter variable names should be avoided except in for() loops.

Asterisks indicating pointers belong with the variable, e.g., NSString *text not NSString* text or NSString * text. This is especially important when using the const qualifier, as NSString *const foo (constant pointer to NSString) is not the same as NSString const *foo (pointer to a constant NSString). Note how the variables and their types are read from right to left.

Property definitions should be used in place of naked instance variables unless there is an extremely good reason. If you need to make your variables private, use an anonymous category as outlined in the section on private properties.

Direct instance variable access should be avoided except in initializer methods (init, initWithCoder:, etc…), dealloc methods and within custom setters and getters. For more information on using Accessor Methods in Initializer Methods and dealloc, see here.

@synthesize should not be written out explicitly unless you need to override the default instance variable name.

For example:

@interface WMFSection : NSObject

@property (nonatomic) NSString *headline;

@end

Not:

@interface WMFSection : NSObject {
    NSString *headline;
}

Blocks[edit]

Block types[edit]

Types of blocks should be defined using a typedef, so that the type can be easily referenced in other places. Like constants, you should define your blocks as close to the code where they are used, like in the header of the class that uses them. Block definitions should also omit their return type when possible as well as their arguments when they are void. Block arguments should always be declared in the typedef.

For example:

typedef void (^WMFCompletion) (id object, NSError *error);
WMFCompletionBlock block = ^ (id object, NSError *error) { /* ... */ };

Not:

void(^block)(id, NSError*) = ^void(id obj, NSError *error) { /* ... */ };

Block parameters[edit]

Block parameters should always be named so that they are filled in on autocomplete (Developers can rename the variables when using the block if they choose).

For example:

typedef void (^WMFCompletion) (id object, NSError *error);

Not:

typedef void (^WMFCompletion) (id, NSError*);

Block as arguments[edit]

Block arguments should always be the last arguments for a method.

For example:

// in the header:
- (void)doSomething:(id)foo success:(SuccessCallback)success failure:(FailureCallback))failure;
// in use:
[self doSomething:foo success:^{ /* ... */ } failure:^{ /* ... */ }];

Block properties[edit]

NOTE: This needs to be investigated, as Objective-C block implementation might already handle copy-on-retain.
Blocks should always be copied when used outside the lexical scope in which they are declared, e.g. asynchronous completion blocks. This goes for storage in a property or in a collection (i.e. NSArray, NSDictionary, etc.).

@property (nonatomic, copy) WMFCompletion completion;

Protocols[edit]

Unless a good reason is given, all protocol methods should be @required (the default). Single method protocols are always @required. Use @optional sparingly to prevent from having to sprinkle respondsToSelector throughout your code.

Protocols can be defined in their own header, or in the header of the class that requires that protocol. Typically delegates should follow the later pattern.

An example of a Delegate protocol:

@protocol WMFUserViewControllerDelegate <NSObject>;

Protocols should always inherent from the NSObject protocol. The first parameter in a protocol method should be the object calling the protocol method:

@protocol WMFUserViewControllerDelegate <NSObject>

- (void)viewController:(WMFUserViewController*)controller didUpdateUser:(WMFUser*)user;

@end

Naming Delegate Protocols[edit]

Delegate protocols should always be named in "notification" style. "…didUpdate…", "…willChange…", "…shouldChange…"

Categories[edit]

Categories should be named for the sort of functionality they provide. Try not to create umbrella categories. They should also be prefixed:

@interface UIImage (WMFImageResizing)

Categories are also useful for removing methods that subclasses require out of the public interface:

@interface WMFExampleViewController (WMFSubclasses)

Always name your category files as the "Class+CategoryName":

WMFExampleViewController+WMFSubclasses.h

Category instance methods should always have a prefix (similar to classes) to prevent name collisions. For example, we'll be using wmf_ to prefix our categories on platform API classes.

ARC[edit]

ARC should be enabled for all new projects. All existing projects should be converted to ARC as soon as possible.

Naming[edit]

Apple naming conventions should be adhered to wherever possible, especially those related to memory management rules (NARC).

Long, descriptive method and variable names are good.

For example:

UIButton *settingsButton;

Not

UIButton *setBut;

A 2-4 letter prefix (e.g. WMF) should always be used for class names and constants. Constants should be camel-case with all words capitalized and prefixed by the related class name for clarity.

For example:

static const NSTimeInterval WMFArticleViewControllerNavigationFadeAnimationDuration = 0.3;

Not:

static const NSTimeInterval fadetime = 1.7;

Properties and local variables should be camel-case with the leading word being lowercase.

Instance variables should be camel-case with the leading word being lowercase, and should be prefixed with an underscore. This is consistent with instance variables synthesized automatically by LLVM. If LLVM can synthesize the variable automatically, then let it.

For example:

@synthesize descriptiveVariableName = _descriptiveYetDifferentVariableName;

Public Interfaces[edit]

Your class interfaces should ONLY contain methods and properties that need to be exposed publicly. All other code should be in the implementation file.

All methods and properties should be clearly named (you were doing that already, no?), and commented so that documentation can be generated automatically.

Comments[edit]

In general, code should be self-documenting. Comments should be used when necessary for clarity. Here are a few specific situations:

Class Interfaces[edit]

Always include a high level description in a block comment at the top of the header file. What the class is used for, Why you may use it, etc…

All methods and properties should be clearly named, as mentioned above. However, you should also add Java style comments to be used to generate documentation. The easiest way to do that is to use the VVDocumenter Xcode plugin.

In addition to the parameters and return values, each method and property should be documented with any default values or behavior that the user must understand to use the API.

Method Implementations[edit]

In general, implementations should not include comments. Block comments should generally be avoided, as code should be as self-documenting as possible, with only the need for intermittent, few-line explanations.

When they are needed, comments should be used to explain why a particular piece of code does something. Any comments that are used must be kept up-to-date or deleted.

An example may be a complex algorithm that cannot be broken up easily (something like JSON parsing, or other complex math or logic).

init and dealloc[edit]

init methods should be structured like this:

- (instancetype)init {
    self = [super init]; // or call the designated initializer
    if (self) {
        // Custom initialization
    }

    return self;
}

Only implement dealloc if required. For instance, when unsubscribing from notifications.

Literals[edit]

NSString, NSDictionary, NSArray, and NSNumber literals should be used whenever creating immutable instances of those objects. Pay special care that nil values not be passed into NSArray and NSDictionary literals, as this will cause a crash.

For example:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

Not:

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[edit]

When accessing the x, y, width, or height of a CGRect, always use the CGGeometry functions instead of direct struct member access. From Apple's CGGeometry reference:

All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.

For example:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

Not:

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;

Constants[edit]

Constants should be kept in the classes/files in which they are used for easy reference. You should not create a "Constants" file and use it as a junk drawer for all constants in the app. For instance, if you define a notification WMFUserDidLogOutNotification, that should be defined in the header of the class posting the notification.

Constants are preferred over in-line string literals or numbers, as they allow for easy reproduction of commonly used variables and can be quickly changed without the need for find and replace. Constants should not be declared using #define. Instead, use the static qualifier within your implementation file, or extern for public constants in the header.

For example:

// Foo.h
extern NSString *const FooPublicConstant;
// Foo.m
NSString *const FooPublicConstant = @"Foo";
static NSString *const FooPrivateConstant = @"PrivateFoo";

Not:

#define CompanyName @"Wikimedia Foundation"

Enumerated Types[edit]

When using enums, it is recommended to use the new fixed underlying type specification because it has stronger type checking and code completion. The SDK now includes a macro to facilitate and encourage use of fixed underlying types — NS_ENUM()

Example:

typedef NS_ENUM(NSInteger, WMFAdRequestState) {
    WMFAdRequestStateInactive,
    WMFAdRequestStateLoading
};

Bitmasks[edit]

When working with bitmasks, use the NS_OPTIONS macro.

Example:

typedef NS_OPTIONS(NSUInteger, WMFAdCategory) {
  WMFAdCategoryAutos      = 1 << 0,
  WMFAdCategoryJobs       = 1 << 1,
  WMFAdCategoryRealState  = 1 << 2,
  WMFAdCategoryTechnology = 1 << 3
};

Private Properties[edit]

Private properties should be declared in class extensions (anonymous categories) in the implementation file of a class. Named categories (such as WMFPrivate or private) should never be used unless extending another class.

For example:

@interface WMFAdvertisement ()

@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;

@end

Booleans[edit]

Since nil resolves to NO it is unnecessary to compare it in conditions. Never compare something directly to YES, because YES is defined to 1 and a BOOL can be up to 8 bits.

This allows for more consistency across files and greater visual clarity.

For example:

if (!someObject) {
}

Not:

if (someObject == nil) {
}

For a BOOL, here are two examples:

if (isAwesome)
if (![someObject boolValue])

Not:

if ([someObject boolValue] == NO)
if (isAwesome == YES) // Never do this.

If the name of a BOOL property is expressed as an adjective, the property can omit the “is” prefix but specifies the conventional name for the get accessor, for example:

@property (assign, getter=isEditable) BOOL editable;

Text and example taken from the [Naming Guidelines].

Singletons[edit]

Singleton objects should use a thread-safe pattern for creating their shared instance. Unless there is a reason, singleton methods should always be named sharedInstance so that classes with singletons are easy to identify.

+ (instancetype)sharedInstance {
   static id sharedInstance = nil;

   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
      sharedInstance = [[self alloc] init];
   });

   return sharedInstance;
}

This will prevent possible and sometimes prolific crashes.

Image Naming and Asset Catalogs[edit]

Images should be placed in the asset catalog in all new projects.

Image names should be named consistently to preserve organization and developer sanity. They should be named as one camel case string with a description of their purpose, followed by the un-prefixed name of the class or property they are customizing (if there is one), followed by a further description of color and/or placement, and finally their state.

For example:

  • RefreshBarButtonItem / RefreshBarButtonItem@2x and RefreshBarButtonItemSelected / RefreshBarButtonItemSelected@2x
  • ArticleNavigationBarWhite / ArticleNavigationBarWhite@2x and ArticleNavigationBarBlackSelected / ArticleNavigationBarBlackSelected@2x.

Do your best to communicate these standards to the designer slicing the artwork. Even still, images sliced by the designer might not conform to our standards. If this is the case, you can just change the name in the asset catalog **WITHOUT* changing the file name. That way when your slices are updated, you don't have to rename them again.

File Organization[edit]

Header Files[edit]

Header files should only ever declare the public interface. Private/protected methods should be contained in class continuations inside the implementation file.

Use the @class directive to forward declare classes referenced in the header instead of importing header files. This prevents compile time issues.

Order of Content[edit]

  • Each header file should contain the following in this order:
  • Class/Header imports
  • @class declarations
  • External constants (e.g. NSNotification constants)
  • External Function Declarations
  • Structs / Enums
  • Class Delegate Protocols Declarations
  • Class Interface
  • Class Categories
  • Class Delegate Protocols

Grouping of Methods and Properties[edit]

Methods and properties should be grouped in a sensical way according to function. Not required, but using #pragma marks to group sections for large header files increases readability. Common groupings are Initialization, configuration, etc…

Implementation Files[edit]

Within an implementation, all methods should be logically grouped using #pragma marks, including protocol implementations. This allows faster code navigation in Xcode.

Do not forward declare methods at the top of the file, this no longer necessary with the latest versions of Xcode.

Do use the anonymous category for any "private" properties.

Order of Content[edit]

  • Class/Header Imports
  • Constants (e.g. NSNotification constants)
  • Function Implementations
  • Anonymous Class Category (for private properties)
  • Class Implementation
  • Class Categories

Grouping and order of class Implementation[edit]

  • @synthesize / @dynamic (only if required)
  • dealloc
  • init methods
  • class methods
  • accessors
  • subclass overrides
  • other groups (including logical functional areas and protocol implementations)

Project Organization[edit]

Directory Structure[edit]

All code files (including header, implementation, nibs, and storyboards) should be kept in a directory called "Source". You should not divide these files into subdirectories. This does make the project "less browsable" in the finder and command line, but increases flexibility when moving files between groups in Xcode - which is where most developers interact with the source files. Trying to keep Xcode groups in sync with actual file system directories is not worth the hassle.

Xcode Project[edit]

The organization of files should happen in the Xcode Project file. Your groups should make it easy to find your code and the names should be descriptive to the reader. Consider your project organization to be part of your documentation.

Groups[edit]

Structure the Project with the following top level groups:

  • Source (or "App Name")
    • Application
    • UI
  • Tests
  • Dependencies
  • Frameworks

Within each folder, code should be grouped not only by type, but also by feature for greater clarity.

Group Specific Organization[edit]

Application[edit]

Place the app delegate, info plist, global.h/.m, prefix-header and any other application level files within the Application Folder (functions, macros).

UI[edit]

Place all view controllers, story boards, nibs, asset catalogs and view classes within the UI group. This also includes any UI specific categories or protocols. Group UI sources according "Scenes", and "Flows" - this will map closely to view controllers, but this is a higher level abstraction and groups may include multiple view controllers. Group common code (used across several view controllers / flows) in a "Common" of "Shared" group and further subdivide that into specific areas (cells, controls, collection view layouts, transitions, etc…). If a view is only used by one view controller, it should NOT be placed in the "Common" group.

Other Top Level Groups[edit]

Group all non-UI classes functionally. This includes all models, model controllers, network controllers, categories and protocols. Group according to function. Place any non-application specific external classes within this group.