最近刚进入公司,技术主管要求整理一下iOS端的代码规范给他,所以参考了两篇编码规范的文章,简单整理了一下,详细的内容文末有参考文章的链接。
条件语句
1.条件语句
if (!error) {
return success;
}
2.不使用尤达表达式 if(5 == count)
if ([myValue isEqual:@42]) { ...
3.nil和BOOL的检查
推荐使用
if (someObject) { ...
if (![someObject boolValue]) { ...
if (!someObject) { ...
4.条件语句编程
使用多个return 比免增加循环的复杂度(不嵌套if语句):
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
单个判断
(void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
5.复杂的表达式
复杂的if子句,把它们提取出来赋给BOO变量,逻辑清晰。
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
6.三元运算符
result = a > b ? x : y;
7.错误处理
NSError *error = nil;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
8.case语句
当一个 case 包含了多行语句的时候,需要加上括号。
switch (condition) {
case 1:
// ...
break;
case 2: {
// ...
// Multi-line example using braces
break;
}
case 3:
// ...
break;
default:
// ...
break;
}
9.枚举类型
typedef NS_ENUM(NSUInteger, ZOCMachineState) {
ZOCMachineStateNone,
ZOCMachineStateIdle,
ZOCMachineStateRunning,
ZOCMachineStatePaused
};
命名
1.命名规则
尽可能遵守 Apple 的命名约定,推荐使用长的、描述性的方法和变量名。
UIButton *settingsButton;
//清晰
insertObject:atIndex:
//不清晰,insert的对象类型和at的位置属性没有说明
insert:at:
//清晰
removeObjectAtIndex:
//不清晰,remove的对象类型没有说明,参数的作用没有说明
remove:
2.常量
推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用 static声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用。
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
常量应该在头文件中以这样的形式暴露给外部,并在实现文件中为它赋值。
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
3.方法
方法名与方法类型 (-/+ 符号)之间应该以空格间隔。方法段之间也应该以空格间隔(以符合 Apple 风格)。以“驼峰”表示法显示。
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用 do , does 这种多余的关键字,动词本身的暗示就足够了:
// 动词打头的方法表示让对象执行一个动作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
如果方法是为了获取对象的一个属性值,直接用属性名称来命名这个方法,注意不要添加 get 或者其他的动词前缀:
// 正确,使用属性名来命名方法
- (NSSize)cellSize;
如果一个函数有特别多的参数或者名称很长,应该将其按照 : 来对齐分行显示:
- (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)delegate;
函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多。
存取方法是指用来获取和设置类属性值的方法,属性的不同类型,对应着不同的存取方法规范:
//属性是一个名词时的存取方法范式
- (type)noun;
- (void)setNoun:(type)aNoun;
//栗子
- (NSString *)title;
- (void)setTitle:(NSString *)aTitle;
//属性是一个形容词时存取方法的范式
- (BOOL)isAdjective;
- (void)setAdjective:(BOOL)flag;
//栗子
- (BOOL)isEditable;
- (void)setEditable:(BOOL)flag;
//属性是一个动词时存取方法的范式
- (BOOL)verbObject;
- (void)setVerbObject:(BOOL)flag;
//栗子
- (BOOL)showsAlpha;
- (void)setShowsAlpha:(BOOL)flag;
命名存取方法时不要将动词转化为被动形式来使用:
//正确
- (void)setAcceptsGlyphInfo:(BOOL)flag;
- (BOOL)acceptsGlyphInfo;
//错误,不要使用动词的被动形式
- (void)setGlyphInfoAccepted:(BOOL)flag;
- (BOOL)glyphInfoAccepted;
可以使用can,should,will等词来协助表达存取方法的意思,但不要使用do,和does:
//正确
- (void)setCanHide:(BOOL)flag;
- (BOOL)canHide;
- (void)setShouldCloseDocument:(BOOL)flag;
- (BOOL)shouldCloseDocument;
//错误,不要使用"do"或者"does"
- (void)setDoesAcceptGlyphInfo:(BOOL)flag;
- (BOOL)doesAcceptGlyphInfo;
4.字面值
使用字面值来创建不可变的 NSString, NSDictionary, NSArray, 和 NSNumber 对象。
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
5.数据结构
如果构造代码写在一行,需要在括号两端留有一个空格,使得被构造的元素于与构造语法区分开来:
NSArray *array = @[ [foo description], @"Another String", [bar description] ];
NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] };
如果构造代码不写在一行内,构造元素需要使用两个空格来进行缩进,右括号 ] 或者 } 写在新的一行,并且与调用语法糖那行代码的第一个非空字符对齐:
NSArray *array = @[
@"This",
@"is",
@"an",
@"array"
];
NSDictionary *dictionary = @{
NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};
类
1.类名
好的类的命名规范:当你创建一个子类的时候,你应该把说明性的部分放在前缀和父类名的在中间。
有一个 ZOCNetworkClient 类,子类的名字会是ZOCTwitterNetworkClient (注意 "Twitter" 在 "ZOC" 和 "NetworkClient" 之间); 按照这个约定, 一个UIViewController 的子类会是 ZOCTimelineViewController.
2.Initializer
- (instancetype)init
{
self = [super init]; // call the designated initializer
if (self) {
// Custom initialization
}
return self;
}
3.Designated 和 Secondary 初始化方法
- (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];
}
4.建议对所有返回类的实例的类方法和实例方法使用 instancetype 类型
@interface ZOCPerson
+ (instancetype)personWithName:(NSString *)name;
@end
5.单例
对于 GCD,用 dispatch_once() 函数就可以
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
6.属性
属性应该尽可能描述性地命名,避免缩写,并且是小写字母开头的驼峰命名
NSString *text;
UILabel *titleLabel;
UIImageView *avtarImageView;
UITextField *usernameTextField;
UIButton *signinButton;
当使用 setter getter 方法的时候尽量使用点符号。应该总是用点符号来访问以及设置属性。
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
推荐按照下面的格式来定义属性:
@property (nonatomic, readwrite, copy) NSString *name;
私有属性应该定义在类的实现文件的类的扩展 (匿名的 category) 中。不允许在有名字的 category(如 ZOCPrivate)中定义私有属性,除非你扩展其他类。
@interface ZOCViewController ()
@property (nonatomic, strong) UIView *bannerView;
@end
7.懒加载
- (NSDateFormatter *)dateFormatter {
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[_dateFormatter setLocale:enUSPOSIXLocale];
[_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];//毫秒是SSS,而非SSSSS
}
return _dateFormatter;
}
Categories
一个好的实践是在 category 名中使用前缀。
@interface NSDate (ZOCTimeExtensions)
- (NSString *)zoc_timeAgoShort;
@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
Protocols
一个类的Delegate对象通常还引用着类本身,这样很容易造成引用循环的问题,所以类的Delegate属性要设置为弱引用。
@protocol ZOCFeedParserProtocol
@property (nonatomic, weak) id delegate;
@property (nonatomic, strong) NSURL *url;
- (BOOL)start;
- (void)stop;
@end
@protocol ZOCFeedParserDelegate
@optional
- (void)feedParserDidStart:(id)parser;
- (void)feedParser:(id)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info;
- (void)feedParser:(id)parser didParseFeedItem:(ZOCFeedItemDTO *)item;
- (void)feedParserDidFinish:(id)parser;
- (void)feedParser:(id)parser didFailWithError:(NSError *)error;
@end
NSNotification
当你定义你自己的 NSNotification 的时候你应该把你的通知的名字定义为一个字符串常量,就像你暴露给其他类的其他字符串常量一样。你应该在公开的接口文件中将其声明为 extern 的, 并且在对应的实现文件里面定义。
// Foo.h
extern NSString * const ZOCFooDidBecomeBarNotification
// Foo.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
美化代码
缩进使用 4 个空格 ,在 Xcode > Preferences > Text Editing 将 Tab 和自动缩进都设置为 4 个空格。在 Xcode > Preferences > Text Editing > Page guide at column: 中将最大行长设置为 80 ,过长的一行代码将会导致可读性问题。
方法的大括号和其他的大括号(if/else/switch/while 等) 总是在同一行开始,在新起一行结束。
if (user.isHappy) {
//Do something
} else {
//Do something else
}
应该总是让冒号对齐。有一些方法签名可能超过三个冒号,用冒号对齐可以让代码更具有可读性。即使有代码块存在,也应该用冒号对齐方法。
[UIView animateWithDuration:1.0
animations:^{
// something
}
completion:^(BOOL finished) {
// something
}];
代码块如果在闭合的圆括号内的话,会返回最后语句的值
NSURL *url = ({
NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, endpoint];
[NSURL URLWithString:urlString];
});
#pragma mark - 是一个在类内部组织代码并且帮助你分组方法实现的好办法。
建议使用 #pragma mark - 来分离:
-
不同功能组的方法
-
protocols 的实现
-
对父类方法的重写
#pragma mark - life cycle
#pragma mark - system delegate
#pragma mark - user delegate
#pragma mark - private method
#pragma mark - public method
#pragma mark - getter & setter
注释(单行和多行)
// Return a user-readable form of a Frobnozz, html-escaped.
/**
This comment serves to demonstrate the format of a docstring.
Note that the summary line is always at most one line long, and
after the opening block comment, and each line of text is preceded
by a single space.
*/
头文档
/**
* Designated initializer.
*
* @param store The store for CRUD operations.
* @param searchService The search service used to query the store.
*
* @return A ZOCCRUDOperationsStore object.
*/
- (instancetype)initWithOperationsStore:(id)store
searchService:(id)searchService;
有很多可以自动生成注释格式的插件,推荐使用插件VVDocumenter。
共有接口要设计的简洁,满足核心的功能需求就可以了。
不要设计很少会被用到,但是参数极其复杂的API。
如果要定义复杂的方法,使用类别或者类扩展。
参考文章
禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship)中文翻译