Sohu Video iOS team Objective-C coding specification

Sohu Video iOS team Objective-C coding specification

introduce

There have been different coding methods and habits in the team for a long time, resulting in different coding styles of modules in the code, reducing readability and maintainability. It is decided by the team Zhang Ke,Li Hongli Write this specification.

Be sure to abide by relevant agreements after the pending draft, which can make the coding style of the team tend to be consistent and help to develop good coding habits.

catalogue

name

Basic principle: clarity
Follow Apple's naming convention as much as possible. The naming should be as clear and concise as possible, but in Objective-C, clarity is more important than simplicity.

  • The class name is uppercamel case
  • Class member, method, small hump (lowerCamelCase)
  • Small hump is preferred for the case of local variables, and the form of lowercase underline (snake_case) can also be used
  • The name of C function is big hump

recommend:

insertObject:atIndex:

removeObjectAtIndex:

opposition:

//The object type of insert and the location attribute of at are not specified
insert:at:

//The object type of remove is not specified, and the function of parameters is not specified
remove:

Don't use the abbreviation of the word, spell the whole word:

recommend:

ID, URL, JSON, WWW

destinationSelection

setBackgroundColor:

opposition:

id, Url, json, www

//Do not use abbreviations
destSel

setBkgdColor:

uniformity

The naming style of the whole project should be consistent, and it is best to be consistent with the code of Apple SDK. Methods that perform similar functions in different classes should have the same name. For example, we always use count to return the number of sets. We can't use count in class A and getNumber in class B.

use prefix

If the code needs to be packaged into a Framework for other projects, or the project is very large and needs to be divided into different modules, the use of naming prefix is very useful.

  • The prefix consists of capital letters and abbreviations. For example, the NS prefix in Cocoa represents the class in the foundation framework, and IB represents the Interface Builder framework.
  • You can use prefixes when naming classes, protocols, functions, constants, and typedef macros, but be careful not to use prefixes for member variables or methods because they are themselves contained in the namespace of the class.
  • Do not conflict with the apple SDK framework when naming prefixes.

Named classes and protocols

The class name starts with a capital letter and should contain a noun to represent the object type it represents. At the same time, it can be prefixed with necessary prefixes, such as NSString, NSDate, NSScanner, NSApplication, etc.

The protocol name should clearly indicate the behavior it performs, and should be distinguished from the class name. Therefore, the ending of ing is usually used to name a protocol, such as nscopying and nslocking.

Named header file

The header file name of the source code should clearly indicate its functions and contents:

  • If only a single class or protocol is defined in the header file, name the header file directly with the class name or protocol name, such as NSLocale H defines the NSLocale class.
  • If a series of classes, protocols and categories are defined in the header file, use the most important class name to name the header file, such as NSString H defines NSString and NSMutableString.
  • Each Framework should have a header file with the same name as the Framework, which contains references to all public class header files in the Framework, such as foundation h
  • Sometimes, the category extension of classes in other frameworks is implemented in the Framework. Such files are usually named by the extended Framework name + Additions, such as nsbundleadditions h.

Naming method

Objective-C method names are usually long, which is to make the program more readable. According to Apple * "a good method name should be read out in the form of a sentence" *.

Methods generally start with lowercase letters. The first letter of each subsequent word is capitalized. There should be no punctuation (including underline) in the method name, with two exceptions:

  • You can start with some common capital abbreviations, such as PDF,TIFF, etc.
  • Private methods or methods in a category can be named with an underlined prefix.

If the method means to let the object perform an action, use the verb to start the name. Be careful not to use redundant keywords such as do and does. The hint of the verb itself is enough:

//A method that begins with a verb means that the object performs an action
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;

If the method is to obtain an attribute value of an object, name the method directly with the attribute name. Be careful not to add get or other Verb Prefixes:

recommend:

//Use attribute names to name methods
- (NSSize)cellSize;

opposition:

//Added extra verb prefix
- (NSSize)calcCellSize;
- (NSSize)getCellSize;

For methods with multiple parameters, be sure to add keywords before each parameter. The keywords should clearly explain the function of the parameters:

recommend:

//Ensure that each parameter has keyword modification
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag;

opposition:

//Error, missing keyword
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;

recommend:

- (id)viewWithTag:(NSInteger)aTag;

opposition:

//The role of keywords is not clear
- (id)taggedView:(int)aTag;

Do not use and to connect two parameters. And is usually used to indicate that the method performs two relatively independent operations (in terms of design, it should be divided into two independent methods at this time):

recommend:

//Use "and" to represent two relatively independent operations
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;

opposition:

//Do not use "and" to connect parameters
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;

There are also some points to pay attention to in the parameter naming of methods:

  • Similar to the method name, the first letter of the parameter is lowercase, and the first letter of each subsequent word is uppercase
  • Don't use words like pointer and PTR in the method name to represent the pointer. The type of the parameter itself is sufficient
  • Do not use parameter names with only one or two letters
  • Don't use abbreviations. Spell the whole word

Some common parameter names are listed below:

...action:(SEL)aSelector
...alignment:(int)mode
...atIndex:(int)index
...content:(NSRect)aRect
...doubleValue:(double)aDouble
...floatValue:(float)aFloat
...font:(NSFont *)fontObj
...frame:(NSRect)frameRect
...intValue:(int)anInt
...keyEquivalent:(NSString *)charCode
...length:(int)numBytes
...point:(NSPoint)aPoint
...stringValue:(NSString *)aString
...tag:(int)anInt
...target:(id)anObject
...title:(NSString *)aString

Named access method

Access method refers to the method used to obtain and set class attribute values. Different types of attributes correspond to different access method specifications:

//Access method paradigm when attribute is a noun
- (type)noun;
- (void)setNoun:(type)aNoun;
//example
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;

//Attribute is a paradigm of adjective time access method
- (BOOL)isAdjective;
- (void)setAdjective:(BOOL)flag;
//example
- (BOOL)isEditable;
- (void)setEditable:(BOOL)flag;

//Property is a verb time access method paradigm
- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;
//example
- (BOOL)showsAlpha;
- (void)setShowsAlpha:(BOOL)flag;

When naming access methods, do not convert verbs into passive forms:

recommend:

- (void)setAcceptsGlyphInfo:(BOOL)flag;
- (BOOL)acceptsGlyphInfo;

opposition:

//Don't use the passive form of verbs
- (void)setGlyphInfoAccepted:(BOOL)flag;
- (BOOL)glyphInfoAccepted;

can,should,will and other words can be used to help express the meaning of access method, but don't use do and does:

recommend:

- (void)setCanHide:(BOOL)flag;
- (BOOL)canHide;
- (void)setShouldCloseDocument:(BOOL)flag;
- (BOOL)shouldCloseDocument;

opposition:

//Do not use "do" or "does"
- (void)setDoesAcceptGlyphInfo:(BOOL)flag;
- (BOOL)doesAcceptGlyphInfo;

Why is the get prefix not applicable in Objective-C to represent the attribute acquisition method? Because in Objective-C, get is usually only used to represent functions that return values from function pointers:

//All three parameters are used as the return value of the function, and the function name can be prefixed with "get"
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;

Named delegate

When a specific event occurs, the object triggers its registered delegate method. Delegation is a common way to deliver messages in Objective-C. Delegate has its fixed naming paradigm.

The first parameter of a delegate method is the object that triggers it, and the first keyword is the class name of the trigger object, unless the delegate method has only one parameter named sender:

//The first keyword is the class name that triggers the delegate
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;

//When there is only one "sender" parameter, the class name can be omitted
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;

According to the timing and purpose of the triggering of the entrustment method, use the keywords such as should, will and did

- (void)browserDidScroll:(NSBrowser *)sender;

- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;,

- (BOOL)windowShouldClose:(id)sender;

Collection operation class method

Some objects manage a series of collections of other objects or elements, and need to use methods similar to "add, delete, check and modify" to operate the collection. The naming paradigm of these methods is generally:

//Set operation paradigm
- (void)addElement:(elementType)anObj;
- (void)removeElement:(elementType)anObj;
- (NSArray *)elements;

//example
- (void)addLayoutManager:(NSLayoutManager *)obj;
- (void)removeLayoutManager:(NSLayoutManager *)obj;
- (NSArray *)layoutManagers;

Note that if the returned set is unordered, use NSSet instead of NSArray. If you need to insert a named element into a specific location, you can use the following:

- (void)insertLayoutManager:(NSLayoutManager *)obj atIndex:(int)index;
- (void)removeLayoutManagerAtIndex:(int)index;

If there is a pointer to the managed object in the managed collection element, set it to the weak type to prevent reference cycling.

The following is the collection operation method of NSWindow class in SDK:

- (void)addChildWindow:(NSWindow *)childWin ordered:(NSWindowOrderingMode)place;
- (void)removeChildWindow:(NSWindow *)childWin;
- (NSArray *)childWindows;
- (NSWindow *)parentWindow;
- (void)setParentWindow:(NSWindow *)window;

Named attributes and instance variables

The attribute is associated with the access method of the object. The first letter of the attribute is lowercase and the first letter of subsequent words is uppercase. There is no need to add a prefix. Attributes are named by function as nouns or verbs:

//Noun attribute
@property (strong) NSString *title;

//Verb attribute
@property (assign) BOOL showsAlpha;

The attribute can also be named as an adjective. At this time, a get method with is prefix is usually specified to improve readability:

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

Name the instance variable and add_ Prefix (some historical codes will put after it), and other attributes are the same as named attributes:

@implementation MyClass {
    BOOL _showsTitle;
}

Generally speaking, classes need to hide the details of data storage from users, so do not define instance methods as public accessible interfaces, but use @ private, @ protected prefixes.

named constant

If you want to define a set of related constants, try to use enumeration types. The naming rules of enumeration types are the same as those of functions.
NS is recommended_ Enum and NS_OPTIONS macro to define enumeration types. See the official Adopting Modern Objective-C Article 1:

//Define an enumeration
typedef NS_ENUM(NSInteger, NSMatrixMode) {
    NSRadioModeMatrix,
    NSHighlightModeMatrix,
    NSListModeMatrix,
    NSTrackModeMatrix
};

Define bit map:

typedef NS_OPTIONS(NSUInteger, NSWindowMask) {
    NSBorderlessWindowMask      = 0,
    NSTitledWindowMask          = 1 << 0,
    NSClosableWindowMask        = 1 << 1,
    NSMiniaturizableWindowMask  = 1 << 2,
    NSResizableWindowMask       = 1 << 3
};

Use const to define floating-point or single integer constants. If you want to define a group of related integer constants, enumeration should be used first. The naming convention of constants is the same as that of functions:

const float NSLightGray;

Do not use #define macro to define constants. If it is an integer constant, try to use enumeration, floating-point constant and const definition# Define is usually used to decide whether to compile a piece of code for the compiler, such as:

#ifdef DEBUG

Note that generally, macros defined by the compiler will have a _ before and after, For example*__ MACH__*.

Naming notice

Notifications are often used to deliver messages between modules, so notifications should indicate the events as much as possible. The naming paradigm of notifications is:

[The name of the class that triggered the notification] + [Did | Will] + [action] + Notification

example:

NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

Picture naming

1. Name it in English without Pinyin

2. Each part is separated by '-'. Capitalize the first letter of the split.

3. Try to show content + use type

4. Try to put the same page under the same folder

recommend:

Download-Progressbar-Highlighted@2x.png
Download-Progressbar-Normal@2x.png

notes

You and I have experienced the pain of reading code without comments. Good comments can not only make people easily understand your program, but also improve the force of the code. Note that notes are for others to understand, not just yourself.

File Comments

Each file must write a file comment, which usually contains

  • Module where the file is located
  • Author information
  • Historical version information
  • Copyright information
  • Contents and functions of the document

Chestnuts with a good document note:

/*******************************************************************************
	Copyright (C), 2011-2013, Andrew Min Chang

	File name: 	AMCCommonLib.h
	Author:		Andrew Chang (Zhang Min) 
	E-mail:		LaplaceZhang@126.com
	
	Description: 	
			This file provide some covenient tool in calling library tools. One can easily include 
		library headers he wants by declaring the corresponding macros. 
			I hope this file is not only a header, but also a useful Linux library note.
			
	History:
		2012-??-??: On about come date around middle of Year 2012, file created as "commonLib.h"
		2012-08-20: Add shared memory library; add message queue.
		2012-08-21: Add socket library (local)
		2012-08-22: Add math library
		2012-08-23: Add socket library (internet)
		2012-08-24: Add daemon function
		2012-10-10: Change file name as "AMCCommonLib.h"
		2012-12-04: Add UDP support in AMC socket library
		2013-01-07: Add basic data type such as "sint8_t"
		2013-01-18: Add CFG_LIB_STR_NUM.
		2013-01-22: Add CFG_LIB_TIMER.
		2013-01-22: Remove CFG_LIB_DATA_TYPE because there is already AMCDataTypes.h

	Copyright information: 
			This file was intended to be under GPL protocol. However, I may use this library
		in my work as I am an employee. And my company may require me to keep it secret. 
		Therefore, this file is neither open source nor under GPL control. 
		
********************************************************************************/

The format of document notes is usually not required, which can be clear and easy to read, but the style should be unified in the whole project.

code annotation

Good code should be "self documenting", but detailed comments are still needed to explain the meaning, return value, function and possible side effects of parameters.

The definitions of methods, functions, classes, protocols and categories need comments. It is recommended to adopt Apple's standard comment style. The advantage is that you can click alt + to automatically pop up comments in the quoted place, which is very convenient.

There are many plug-ins that can automatically generate annotation format, which are recommended VVDocumenter:

[external chain picture transferring... (img-h4Wun5Lr-1618969857502)]

Xcode8 annotation shortcut

[external chain picture transferring... (img-3yQMmhVn-1618969857503)]

Some good notes:

/**
 *  Create a new preconnector to replace the old one with given mac address.
 *  NOTICE: We DO NOT stop the old preconnector, so handle it by yourself.
 *
 *  @param type       Connect type the preconnector use.
 *  @param macAddress Preconnector's mac address.
 */
- (void)refreshConnectorWithConnectType:(IPCConnectType)type  Mac:(NSString *)macAddress;

/**
 *  Stop current preconnecting when application is going to background.
 */
-(void)stopRunning;

/**
 *  Get the COPY of cloud device with a given mac address.
 *
 *  @param macAddress Mac address of the device.
 *
 *  @return Instance of IPCCloudDevice.
 */
-(IPCCloudDevice *)getCloudDeviceWithMac:(NSString *)macAddress;

// A delegate for NSApplication to handle notifications about app
// launch and shutdown. Owned by the main app controller.
@interface MyAppDelegate : NSObject {
  ...
}
@end

The notes of agreement and entrustment shall clearly state the conditions under which they are triggered:

/** Delegate - Sent when failed to init connection, like p2p failed. */
-(void)initConnectionDidFailed:(IPCConnectHandler *)handler;

If you want to refer to the parameter name or method function name in the comment, use | to enclose the parameter or method to avoid ambiguity:

// Sometimes we need |count| to be less than zero.

// Remember to call |StringWithoutSpaces("foo bar baz")|

The interface methods and attributes defined in the header file must be annotated!

Code format

Maximum length of each line

Similarly, in Xcode > Preferences > text editing > page guide at column: set the maximum length to 80. A long line of code will cause readability problems.

Space

Class method declaration should have a space between the method type and the return type.

recommend:

- (void)methodName:(NSString *)string;

opposition:

-(void)methodName:(NSString *)string;

There should be no space inside the bracket of conditional judgment.

recommend:

if (a < b) {
    // something
}

opposition:

if ( a < b ) {
    // something
}

There should be spaces on both sides of relational operators (such as > =,! =) and logical operators (such as & &, |).

recommend:

(someValue > 100)? YES : NO

(items)?: @[]

Whether to add spaces on both sides of the binary arithmetic operator is uncertain, which is determined according to the situation. There is no space before the unary operator and operand.

Function writing

A typical Objective-C function should be as follows:

- (void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {
    ...
}

There should be a space between - and (void). The position of the first brace {is at the end of the line where the function is located, and there should also be a space. (our C language specification requires that the first brace occupy a separate line, but considering the long function name of OC and the style of Apple SDK code, we still put the brace at the end of the line.)

If a function has too many parameters or a long name, it should be displayed in line by:

-(id)initWithModel:(IPCModle)model
       ConnectType:(IPCConnectType)connectType
        Resolution:(IPCResolution)resolution
          AuthName:(NSString *)authName
          Password:(NSString *)password
               MAC:(NSString *)mac
              AzIp:(NSString *)az_ip
             AzDns:(NSString *)az_dns
             Token:(NSString *)token
             Email:(NSString *)email
          Delegate:(id<IPCConnectHandlerDelegate>)delegate;

If the name of the first paragraph is too short during the branch, the subsequent names can be indented in units of the length of the Tab (4 spaces):

- (void)short:(GTMFoo *)theFoo
        longKeyword:(NSRect)theRect
  evenLongerKeyword:(float)theInterval
              error:(NSError **)theError {
    ...
}

function call

The format of function call is similar to that of writing. You can choose to write it on one line or divide it into multiple lines according to the length of the function:

recommend:

//Write on one line
[myObject doFooWith:arg1 name:arg2 error:arg3];

//Write in line and align according to ':'
[myObject doFooWith:arg1
               name:arg2
              error:arg3];

//If the name of the first paragraph is too short, it can be indented later
[myObj short:arg1
          longKeyword:arg2
    evenLongerKeyword:arg3
                error:arg4];

opposition:

//Errors, either in one line or all branches
[myObject doFooWith:arg1 name:arg2
              error:arg3];
[myObject doFooWith:arg1
               name:arg2 error:arg3];

//Error, align according to ':' instead of keyword
[myObject doFooWith:arg1
          name:arg2
          error:arg3];

@public and @ private Tags

@public and @ private tags should be indented with a space:

@interface MyClass : NSObject {
 @public
  ...
 @private
  ...
}
@end

Protocols

When writing the protocol, note that there is no space between the protocol and the type name enclosed by < >. For example, ipcconnecthandler() < ipcreconnectordelegate >, this rule applies to all places where the protocol is written, including function declaration, class declaration, instance variable, etc

@interface MyProtocoledClass : NSObject<NSWindowDelegate> {
 @private
    id<MyFancyDelegate> _delegate;
}

- (void)setDelegate:(id<MyFancyDelegate>)aDelegate;
@end

Closures

There are different writing rules according to the length of the block:

  • Shorter block s can be written on one line.
  • If the line is displayed, the closing bracket} of the block should be aligned with the first non empty character of the line calling the block.
  • The code in block is indented with 4 spaces.
  • If the block is too large, it should be declared as a variable separately.
  • ^And (there is no space between ^ and {, and there is a space between the right parenthesis of the parameter list) and {.
//The shorter block is written on one line
[operation setCompletionBlock:^{ [self onOperationDone]; }];

//block written in branches, with 4 spaces indented inside
[operation setCompletionBlock:^{
    [self.delegate newDataAvailable];
}];

//Blocks called using the C language API follow the same writing rules
dispatch_async(_fileIOQueue, ^{
    NSString* path = [self sessionFilePath];
    if (path) {
      // ...
    }
});

//The longer block keyword can be indented and written on a new line. Note that the closing bracket '}' of the block is aligned with the first non empty character of the line calling the block
[[SessionService sharedService]
    loadWindowWithCompletionBlock:^(SessionWindow *window) {
        if (window) {
          [self windowDidLoad:window];
        } else {
          [self errorLoadingWindow];
        }
    }];

//A long list of block parameters can also be indented and written on a new line
[[SessionService sharedService]
    loadWindowWithCompletionBlock:
        ^(SessionWindow *window) {
            if (window) {
              [self windowDidLoad:window];
            } else {
              [self errorLoadingWindow];
            }
        }];

//Huge block s should be defined as variables separately
void (^largeBlock)(void) = ^{
    // ...
};
[_operationQueue addOperationWithBlock:largeBlock];

//When using multiple block s in a call, notice that they are not aligned by ':' like a function, but indented by four spaces at the same time
[myObject doSomethingWith:arg1
    firstBlock:^(Foo *a) {
        // ...
    }
    secondBlock:^(Bar *b) {
        // ...
    }];

Literal grammar sugar

Better readable syntax sugar should be used to construct data structures such as NSArray and NSDictionary, and lengthy alloc and init methods should be avoided.

If the construction code is written on one line, a space should be left at both ends of the bracket to distinguish the constructed element from the construction syntax:

recommend:

//Leave spaces at both ends of "[]" or "{}" in the syntax
NSArray *array = @[ [foo description], @"Another String", [bar description] ];
NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] };

opposition:

//Leaving no spaces reduces readability
NSArray* array = @[[foo description], [bar description]];
NSDictionary* dict = @{NSForegroundColorAttributeName: [NSColor redColor]};

If the construction code is not written in one line, the construction element needs to be indented with two spaces, and the closing parenthesis] or} is written in a new line, and aligned with the first non empty character of the line calling the syntax sugar:

NSArray *array = @[
  @"This",
  @"is",
  @"an",
  @"array"
];

NSDictionary *dictionary = @{
  NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
  NSForegroundColorAttributeName : fontColor
};

When there are multiple lines of Key and Value dictionary, you can also leave one space in the middle:

recommend:

//There is a space before and after the colon ':'
NSDictionary *option1 = @{
  NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
  NSForegroundColorAttributeName : fontColor
};

//Align by Value
NSDictionary *option2 = @{
  NSFontAttributeName :            [NSFont fontWithName:@"Arial" size:12],
  NSForegroundColorAttributeName : fontColor
};

opposition:

//There should be a space before the colon
NSDictionary *wrong = @{
  AKey:       @"b",
  BLongerKey: @"c",
};

//Each element is either a separate line or written in one line
NSDictionary *alsoWrong= @{ AKey : @"a",
                            BLongerKey : @"b" };

//There can only be one space before the colon. After the colon, you can consider aligning according to Value
NSDictionary *stillWrong = @{
  AKey       : @"b",
  BLongerKey : @"c",
};

Code organization

  • The function length (number of lines) shall not exceed 2 / 3 of the screen and shall not exceed 70 lines.
  • The number of methods in a single file should not exceed 30
  • Do not sort by category (e.g. put ibactions together), but combine relevant by task
  • Code with more than two layers of loops is prohibited. Replace it with function or block.

recommend:

- (Task *)creatTaskWithPath:(NSString *)path {
    if (![path isURL]) {
        return nil;
    }

    if (![fileManager isWritableFileAtPath:path]) {
        return nil;
    }

    if ([taskManager hasTaskWithPath:path]) {
        return nil;
    }

    Task *aTask = [[Task alloc] initWithPath:path];
    return aTask;
}

opposition:

// To simplify the example, there is no error handling and pseudo code is used
- (Task *)creatTaskWithPath:(NSString *)path {
    Task *aTask;
    if ([path isURL]) {
        if ([fileManager isWritableFileAtPath:path]) {
            if (![taskManager hasTaskWithPath:path]) {
                aTask = [[Task alloc] initWithPath:path];
            }
            else {
                return nil;
            }
        }
        else {
            return nil;
        }
    }
    else {
        return nil;
    }
    return aTask;
}

Coding style

Everyone has their own coding style. Here are some good Cocoa programming styles and points for attention.

Do not use the new method

Although new can often be used instead of alloc init method, this may lead to unexpected problems when debugging memory. Cocoa's specification is to use the alloc init method. Using new will confuse some readers.

Public_ APIs should be as concise as possible

The common interface should be designed concisely to meet the core functional requirements. Don't design API s that are rarely used but have extremely complex parameters. If you want to define complex methods, use categories or class extensions.

#import and #include

#Import is a common way to reference header files in Cocoa. It can automatically prevent repeated references to files. When to use #import and when to use #include?

  • When referring to an Objective-C or Objective-C + + header file, use #import
  • When referring to a C or C + + header file, use #include. At this time, you must ensure that the referenced file provides a protection domain (#define guard).

Why not use #import all? It is mainly to ensure that there is no problem when the code is shared between different platforms.

Reference the root header file of the framework

As mentioned above, each framework will have a header file with the same name as the framework, which contains all references to the interfaces within the framework. When using the framework, you should directly reference the root header file instead of the header files of other sub modules. Even if you only use a small part of it, the compiler will automatically complete the optimization.

recommend:

//Reference root header file
#import <Foundation/Foundation.h>

opposition:

//Do not reference other header files in the framework alone
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>

Use of BOOL

BOOL is defined as signed char in Objective-C, which means that a variable of BOOL type can not only represent the values of YES(1) and NO(0), so never compare BOOL type variables with YES directly:

recommend:

BOOL great = [foo isGreat];
if (great)
  // ...be great!

opposition:

//It is impossible to determine whether the value of | great | is YES(1). Do not directly compare the BOOL value with YES
BOOL great = [foo isGreat];
if (great == YES)
  // ...be great!

Similarly, do not return other types of values as BOOL. In this case, the BOOL variable will only be assigned to the last byte of the value, which is likely to get 0 (NO). However, some logical operators, such as & &, ||,! The return of can be directly assigned to BOOL:

recommend:

- (BOOL)isBold {
  return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}

//Logical operators can be directly converted to BOOL
- (BOOL)isValid {
  return [self stringValue] != nil;
}

- (BOOL)isEnabled {
  return [self isValid] && [self isBold];
}

opposition:

//Do not convert Bo to other types
- (BOOL)isBold {
  return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
  return [self stringValue];
}

In addition, BOOL types can be combined with_ BOOL and BOOL are mutually transformed, but they cannot be transformed with Boolean.

init and dealloc

The recommended code organization is to put the dealloc method at the front of the implementation file (directly after @ synthesize and @ dynamic), and init should follow the dealloc method.

When the init``dealloc method is executed, the runtime environment of the class is not in normal state. Accessing variables using access methods may lead to unpredictable results. Therefore, instance variables should be accessed directly in these two methods.

recommend:

//Direct access to instance variables
- (instancetype)init {
  self = [super init];
  if (self) {
    _bar = [[NSMutableString alloc] init];
  }
  return self;
}
- (void)dealloc {
  [_bar release];
  [super dealloc];
}

opposition:

//Do not access through access methods
- (instancetype)init {
  self = [super init];
  if (self) {
    self.bar = [NSMutableString string];
  }
  return self;
}
- (void)dealloc {
  self.bar = nil;
  [super dealloc];
}

Designed and Secondary initialization methods

Objective-C has the concept of designated initializer and secondary initializer. The designed initialization method provides all parameters. The secondary initialization method is one or more, and provides one or more default parameters to call the designed initialization method.

@implementation ZOCEvent

- (instancetype)initWithTitle:(NSString *)title
                         date:(NSDate *)date
                     location:(CLLocation *)location
{
    self = [super init];
    if (self) {
        _title    = title;
        _date     = date;
        _location = location;
    }
    return self;
}

- (instancetype)initWithTitle:(NSString *)title
                         date:(NSDate *)date
{
    return [self initWithTitle:title date:date location:nil];
}

- (instancetype)initWithTitle:(NSString *)title
{
    return [self initWithTitle:title date:[NSDate date] location:nil];
}

@end

initWithTitle: date: location: is the designed initialization method, and the other two are secondary initialization methods. Because they just call the designed initialization method implemented by the class

Release resources in order

At the end of the life cycle of a class or Controller, it is often necessary to do some unfinished work, such as releasing resources and stopping threads. The release order of these unfinished work should be consistent with their initialization or definition order. This is to facilitate the search for errors during debugging and prevent omissions.

Ensure that NSObject is copied during assignment

When transferring or assigning values, it should be ensured that they are copied, which can prevent the value of String from being modified by other objects without knowledge.

- (void)setFoo:(NSString *)aFoo {
  _foo = [aFoo copy];
}

nil check

Because sending commands to nil objects in Objective-C will not throw exceptions or cause crashes, but completely "do nothing". Therefore, only nil is used in the program for logical inspection.

In addition, do not use forms such as nil == Object or Object == nil to judge.

recommend:

//Direct judgment
if (!objc) {
	...	
}

opposition:

//Do not use the form nil == Object
if (nil == objc) {
	...	
}

Use of dot syntax

Don't use pointwise syntax to call methods, just to access properties. This is to prevent code readability problems.

recommend:

//Accessing properties using pointwise syntax
NSString *oldName = myObject.name;
myObject.name = @"Alice";

opposition:

//Do not call methods with pointwise syntax
NSArray *array = [NSArray arrayWithObject:@"hello"];
NSUInteger numberOfItems = array.count;
array.release;

Delegate to use weak references

The Delegate object of a class usually refers to the class itself, which is easy to cause the problem of reference cycle, so the Delegate attribute of the class should be set to weak reference.

/** delegate */
@property (nonatomic, weak) id <IPCConnectHandlerDelegate> delegate;

Single case

If possible, try to avoid using singleton instead of dependency injection. However, if necessary, use a thread safe pattern to create shared instances. For GCD, use dispatch_ Just use the once() function.

+ (instancetype)sharedInstance
{
   static id sharedInstance = nil;
   static dispatch_once_t onceToken = 0;
   dispatch_once(&onceToken, ^{
      sharedInstance = [[self alloc] init];
   });
   return sharedInstance;
}

KVO

KVO trigger mechanism: one object (observer) detects whether an attribute of another object (observer) has changed. If the monitored attribute has changed, it will trigger a method of the observer (method name is fixed, similar to proxy method)

  • Register observer (specify observer and observer attributes for the observed)
  • Implement callback method
  • Trigger callback method
  • Remove observer

Reasons for general KVO collapse:

  • The observed object is destroyed (the observed object is a local variable)
  • The monitored mode is removed (e.g., pop, pop, etc.)
  • The registered listener was not removed, and the listener was registered again

Assert

Your method may require some parameters to meet specific conditions (for example, it cannot be nil). In this case, it is best to use NSParameterAssert() to assert whether the condition is true or throw an exception.

Categories

Although we know it's ugly, we should add our own lowercase prefix and underscore before our category method, such as - (id)zoc_myCategoryMethod. This practice is also recommended by apple.

This is very necessary. Because if the same method name has been used in the extended category or other categories, it will lead to unpredictable consequences. In fact, what is actually called is the implementation of the method in the last loaded category.

A good practice is to use prefixes in category names.

**Examples**

recommend:

@interface NSDate (ZOCTimeExtensions)
- (NSString *)zoc_timeAgoShort;
@end

opposition:

@interface NSDate (ZOCTimeExtensions)
- (NSString *)timeAgoShort;
@end

Classification can be used to define a set of methods with similar functions in the header file. This is also a common practice in Apple's Framework (the following example is taken from the NSDate header file). We also strongly recommend using this in our own code.

Our experience is that creating a set of classifications is very helpful for future refactoring. When the interface of a class is added, it may mean that your class has done too many things and violated the principle of single function of the class.

The previously created method grouping can be used to better represent different functions and break classes into more self-contained components.

@interface NSDate : NSObject <NSCopying, NSSecureCoding>

@property (readonly) NSTimeInterval timeIntervalSinceReferenceDate;

@end

@interface NSDate (NSDateCreation)

+ (instancetype)date;
+ (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
+ (instancetype)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti;
+ (instancetype)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
+ (instancetype)dateWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
// ...
@end

Pragma Mark

#Pragma mark is a good way to organize code within a class and help you implement grouping methods. We recommend using #pragma mark - to separate:

Methods of different functional groups
Implementation of protocols
Override of parent method

  • (void)dealloc { /* ... */ }
  • (instancetype)init { /* ... */ }

#pragma mark - View Lifecycle

  • (void)viewDidLoad { /* ... */ }
  • (void)viewWillAppear:(BOOL)animated { /* ... */ }
  • (void)didReceiveMemoryWarning { /* ... */ }

Design mode

ReactiveCocoa+MVVM

It is recommended to use ReactiveCocoa+MVVM to design the code architecture. Only when the code architecture is clear and reasonable can the above rules be icing on the cake.

Refer to the following articles for specific usage.

Getting started with ReactiveCocoa and MVVM

MVVM With ReactiveCocoa

All the specifications of this programming language, if not written here, are in Apple's documents:

Keywords: iOS Code Style

Added by Mindwreck on Thu, 03 Mar 2022 22:53:44 +0200