Objective-C开发编码规范

命名

UIColor *myColor = [UIColor whiteColor];
//UIColor *myColour = [UIColor whiteColor];

常量是容易重复被使用和无需通过查找和代替就能快速修改值.

常量应该使用static来声明而不是使用#define,除非显式地使用宏.

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;
static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";
static CGFloat const RWTImageThumbnailHeight = 50.0;

还可以使用extern变量来声明常量:

//.h文件
extern NSString *const kMarketIdAppleAppStore;
extern CGFloat const RWTImageThumbnailHeight;
//.m文件
NSString *const kMarketIdAppleAppStore = @"AppleAppStore";
CGFloat const RWTImageThumbnailHeight = 44.0f;
@interface NSString (NSStringEncodingDetection)
@interface NSArray (NYTAccessors)
- (id)nyt_objectOrNilAtIndex:(NSUInteger)index;
@end

类似的,推荐使用枚举类型(NS_ENUM()),固定基本类型来进行类型检查和代码补全.

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
  RWTLeftMenuTopItemMain,
  RWTLeftMenuTopItemShows,
  RWTLeftMenuTopItemSchedule
};
typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
    NYTAdCategoryAutos      = 1 << 0,
    NYTAdCategoryJobs       = 1 << 1,
    NYTAdCategoryRealState  = 1 << 2,
    NYTAdCategoryTechnology = 1 << 3
};
@interface MyClass: NSObject
//默认为strong
@property (nonatomic) NSString *headline;
//定义一个public的getter方法
@property (nonatomic, readonly) Type propertyName;
//使用BOOL属性时,自定义getter方法
@property (nonatomic, assign, getter = isSomething) BOOL something;
@end
// @interface MyClass : NSObject {
//     NSString *headline;
// }

@interface MyClass ()
// Private Access
//定义一个private的setter方法
@property (nonatomic, strong, readwrite) Type propertyName;
@end
@implementation MyClass
@end

除了在初始化方法(init,initWithCoder:等),dealloc方法和自定义的setter,getter方法.(使用实例变量来避免setter,getter方法的潜在副作用.参考:Don’t Use Accessor Methods in Initializer Methods and dealloc)

//init方法返回值为:instancetype,而不是id
- (instancetype)init {
    self = [super init];
    if (self) {
        _count = [[NSNumber alloc] initWithInteger:0];
        // 不能使用`setter`,`getter`方法
        // self.count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}
- (void)dealloc {
  // 释放资源,停止线程等操作.
  // 资源的释放顺序应该按照它们的初始化顺序或者定义的顺序.
  [[NSNotificationCenter defaultCenter] removeObserver:self];
}

使用__strong,__weak,__unsafe_unretained, __autoreleasing等限定符时,要保证限定符在星号和变量之间.(eg:NSString * __weak text)

(相关资料:ARC Introduces New Lifetime Qualifiers)

@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (copy, nonatomic) NSString *tutorialName;

参考资料:Cocoa Naming Guidelines

代码结构

在类似的的功能函数分组和protocol/delegate实现中使用#pragma mark -来分类方法.

参考:

#pragma mark - Lifecycle

//init方法返回值为:instancetype,而不是id
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Custom Accessors

- (void)setCustomProperty:(id)value {}
- (id)customProperty {}

#pragma mark - IBActions

- (IBAction)submitData:(id)sender {}

#pragma mark - Public

- (void)publicMethod {}

#pragma mark - Private

- (void)privateMethod {}

#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - NSCopying

- (id)copyWithZone:(NSZone *)zone {}

#pragma mark - NSObject

- (NSString *)description {}

代码格式

if (user.isHappy) {
    //Do something
} else {
    //Do something else
}
- (void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {
...
}

and的作为连接,只有方法参数为多个时才使用. 函数名很长时,可以使用换行,但是参数一定要对齐.函数调用同理.

//init方法返回值为:instancetype,而不是id
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
//- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;

使用布尔值时,需要特别注意:

objectivec使用YESNO表示布尔值.正确的使用方法:

// nil检查
if (someObject) {}
if (![anotherObject boolValue]) {}
#pragma mark 错误的使用方法
// if (someObject == nil) {}
// if ([anotherObject boolValue] == NO) {}
// if (isAwesome == YES) {} // Never do this.
// if (isAwesome == true) {} // Never do this.

为了使得代码便于阅读,避免书写错误,尽量把判断条件表达式中判断的值倒写.

if (2 == indexPath.section) {
}

此处需要引入guard/黄金路径的概念:

当使用条件语句编码时,左手边的代码应该是”golden”或”happy”路径.也就是要尽量避免if语句的嵌套.

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

不应该是:

- (void)someMethod {
  if ([someOther boolValue]) {
    //Do something important
  }
}
// Frameworks
@import QuartzCore;

// Models
#import "NYTUser.h"

// Views
#import "NYTButton.h"
#import "NYTUserView.h"

避免过多的使用#import,建议使用@class代替依赖导入.(参考资料:Don’t #import in Header Files Unnecessarily)

@class MyOtherClass;
@interface MyClass : NSObject
@property (nonatomic, strong) MyOtherClass property;
@end

// #import "MyOtherClass.h"
// @interface MyClass : NSObject
// @property (nonatomic, strong) MyOtherClass property;
// @end

#import "MyOtherClass.h"
@implementation MyClass
@end

方法

头文件中应该只保存public接口方法和属性,private/protected的方法应该在实现文件中定义.

方法命名时尽量不要以set,get开头,重写setter,getter方法除外.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)setFoo:(NSString *)aFoo {
  [_foo autorelease];
  //字符串的赋值或者传递时保证其值以copy的形式存在,避免在不知情的情况下修改string值
  _foo = [aFoo copy];
}

避免使用new方法创建对象或者在子类中重写此方法,应该使用allocinit方法实例化对象.

init方法:返回类型应该使用instancetype而不是id.

- (instancetype)init {
  self = [super init];
  if (self) {
    // ...
  }
  return self;
}

当类构造方法被使用时,它应该返回类型是instancetype而不是id.

@interface Airplane
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end

单例对象应该使用线程安全模式来创建共享实例.

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

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

  return sharedInstance;
}

相关方法/函数的使用

应用程序应该避免直接访问和修改保存在CGRect数据结构中的数据.

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);
// CGFloat x = frame.origin.x;
// CGFloat y = frame.origin.y;
// CGFloat width = frame.size.width;
// CGFloat height = frame.size.height;
// CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

尽量使用点语法调用setter,getter方法.

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

NSStringFromClass(),NSClassFromString()等的使用.

CGFLOAT_MIN宏定义的使用.

注释

避免过多的注释使用,建议使用代码的自解释实现. 以下情况需要格外注意并作出相应注释:

  1. 产品需求临时变更:需要额外标注时间,任务,目的,其他tips等

    2017年06月18日21:07:13 @XX 需求由A变更为B

    另:需求变更需要邮件/任务等备份存档.

  2. 需求/bug临时解决方案/TODO

官方编码规范:

Apple Coding Guidelines for Cocoa

Google objectivec Style Guide

GitHub objectivec Style Guide

Wikimedia objectivec Style Guide

Introduction to Coding Guidelines for Cocoa

objectivec编码规范[译]

objectivec开发编码规范

objectivec编码规范:26个方面解决iOS开发问题