项目开发,Objective-C 编码规范

1,312 阅读8分钟
原文链接: www.jianshu.com

最近刚进入公司,技术主管要求整理一下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编码规范

禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship)中文翻译