目录
前言
命名规范
注释规范
#define规范
全局常量宏(不推荐使用)
私有常量宏(不推荐使用)
类函数宏
#pragma mark规范
#import规范
类规范
@Interface规范
常量规范
协议规范
变量规范
@property规范
@implementation规范
方法规范
Initializer和dealloc规范
Designated 和 Secondary 初始化方法
类簇规范
懒加载(Lazy Loading)规范
条件规范
if-else规范
黄金路径法则
复杂的表达式
三元运算符
枚举规范
Block规范
NSNotification规范
Categories规范
Protocols和delegate规范
Protocols
delegate
单例模式规范
资源包规范
Bundle Identifier规范
参考文献
好的代码有一些特性:简明,自我解释,优秀的组织,良好的文档,良好的命名,优秀的设计以及可以被久经考验。
原则一:代码应该简洁易懂,逻辑清晰
原则二:面向变化编程,而不是面向需求编程。
原则三:先保证程序的正确性,防止过度工程
在正确可用的代码写出之前就过度地考虑扩展,重用的问题,使得工程过度复杂。
命名严禁使用拼音、数字与英文混合的方式,更不允许直接使用中文的方式。
命名使用正确的英文拼写和语法可以让阅读者易于理解,避免歧义。
命名含义清楚,尽量使用全称不使用缩写(除了公认的缩写)。
命名尽量做到自注释(通过名字不需要注释就能了解其功能和作用),若做不到就加相应注释。
命名主要方法:
注:命名方法的具体使用会在每个规范里指出。
注释要么一直维护,要么干脆删掉。
块注释应该被避免,代码本身应该尽可能就像文档一样表示意图,只需要很少的打断注释。
优秀的代码大部分是可以自注释的,完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助。
但并不是说一定不能写注释,有以下四种情况比较适合写注释:
公共接口(注释要告诉阅读代码的人,当前类的功能描述、传入的参数、返回的值、如何使用此类等)。
涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
记录一个复杂的类的完整的实现思想和步骤,写在类的创建注释里面(可以要让阅读代码的人更方便地理解和使用这个类)。
除了上述这四种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。
全局常量宏和类函数宏需要定义在一个特定的头文件里,供整个项目使用。
私有常量宏只能定义在特定的.m文件中。
本文仅是为了完善#define规范,所以还是介绍了常量宏的规范,常量宏的规范这部分可以跳过。
千万不要用常量宏,不管是全局还是私有。而是用extern和const来声明全局常量,用static和const来声明私有常量。
规范要求:
举个栗子:
#define IST_NAVIGATIONBAR_HEIGHT 64
#define IST_NAVIGATIONBAR_WIDTH 64
规范要求:
举个栗子:
#define kNavigationBarHeight 64
#define kNavigationBarWidht 64
规范要求:
举个栗子:
#define ISTUserDefaults [NSUserDefaults standardUserDefaults]
#define ISTNotificationCenter [NSNotificationCenter defaultCenter]
#define IST_SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define IST_SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
pragma mark -
是在类内部模块组织函数方法的好办法。一定要使用
#pragma mark -
来分离:不同功能组的方法、protocols 的实现、对父类方法的重写等等。
规范要求:
举个栗子:
#pragma mark - life cycle
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(color:) name:@"Notification" object:nil];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - 通知响应事件
- (void)colorChange:(UIColor *)color {
self.view.backgroundColor = color;
}
如果需要#import(或@class)两个文件(或类)以上,就要根据功能进行分组,并且对分组进行注释。
规范要求:
举个栗子:
//系统框架
#import
//第三方库
#import
//view
#import "IOSBookView.h"
//model
#import "IOSBookModel.h"
//viewModel
#import "IOSBookviewModel.h"
//viewController
#import "IOSBookViewController.h"
//其他(包括与当前.m对应.h和工具类、常量类、配置类等)
#import "IOSTool.h"
#import "IOSConstant.h"
在实际开发中,一般都会给项目中所有的类加上属于本项目的前缀。
使用大驼峰命名,一般格式:前缀 + 功能 + 类型后缀。
举个栗子:
ISTHomePageViewController
IST(项目简写)+ HomePage(功能) +ViewController(类型后缀)
其他例子:
ISTHomePageView
ISTHomePageModel
1)全局常量
全局常量应全部声明在同一个.h文件中,供整个项目使用,并在.m文件中实现全局常量,不暴露出来。
规范要求:
举个栗子:
//.h
extern NSString * const ISTNetworkStatusChangeNotification;
extern NSString * const ISTStatusChangeNotification;
//.m
NSString *const ISTNetworkStatusChangeNotification = @"ISTNetworkStatusChangeNotification";
NSString *const ISTNetworkChangeNotification = @"ISTNetworkChangeNotification";
2)私有常量
规范要求:
举个栗子:
static const NSInteger kPasswordCount = 16;
static const NSInteger kNavigationBarHeight = 64;
static NSString * const kUserName = @"SunSatan";
static NSString * const kUserMessage = @"Message";
规范要求:
举个栗子:
@interface ClassStudentProfileViewController ()
<
UITableViewDataSource
,UITableViewDelegate
,UIImagePickerControllerDelegate
,UINavigationControllerDelegate
>
@end
这样不管是删除还是添加新协议,体验都很好。
1)实例变量
规范要求:
举个栗子:
UIButton *settingsButton;
NSArray *dateArray;
2)私有成员变量
规范要求:
“*”
分类对齐(基础数据类型另外分类对齐)。举个栗子:
@interface ViewController () {
NSString *_yearDataString;
NSString *_dayDataString;
BOOL _isAdsOpen;
BOOL _isHomeMenuOpen;
}
永远不要在 init 方法和dealloc方法里面用 getter 和 setter 方法(点语法访问属性),应当直接访问@property的实例变量。
在上一条的描述情况以外,其他任何地方都应当使用 getter 和 setter 方法(点语法访问属性)。
在实现文件中应避免使用@synthesize,
因为Xcode已经自动添加了。
公共属性应该定义在.h文件中,私有属性应该定义在.m文件的类扩展中。
规范要求:
“*”
分类对齐(基础数据类型另外分类对齐)。举个栗子:
@property (nonatomic, readwrite, strong) UILabel *tipsLabel; //这里写注释
@property (nonatomic, readwrite, strong) UIImageView *backgroundImageView; //这里写注释
@property (nonatomic, readwrite, copy) NSString *dayDataString; //这里写注释
@property (nonatomic, readwrite, copy) NSString *userNameString; //这里写注释
@property (nonatomic, readwrite, assign) int messageQuantityInt; //这里写注释
@property (nonatomic, readwrite, assign) float orderPaymentAmountFloat; //这里写注释
这个地方会有争议,因为有些建议是应该与xib中拉出来的@property的关键字写法一致,但是我认为,xib中拉出的应该归属为另一类(不是自己写的),自己手写的@property还是与官方一致好一些。
@property (weak, nonatomic) IBOutlet UILabel *titleLab;
@property (weak, nonatomic) IBOutlet UILabel *infoLab;
举一个官方(UITableViewCell)个栗子:
@property (nonatomic, readonly, strong, nullable) UIImageView *imageView NS_AVAILABLE_IOS(3_0);
@property (nonatomic, readonly, strong, nullable) UILabel *textLabel NS_AVAILABLE_IOS(3_0);
@property (nonatomic, readonly, strong, nullable) UILabel *detailTextLabel NS_AVAILABLE_IOS(3_0);
若要使@property有一个公共的 getter 和一个私有的 setter,应该在.h声明外部可访问的属性的关键字为 readonly
,并且在类扩展中重新定义该属性的关键字为 readwrite
。 这样外部无法修改该属性,而内部可以,从而确保了@property访问安全性。
举个栗子:
// .h文件中
@interface MyClass : NSObject
@property (nonatomic, readonly, strong) NSObject *object;
@end
// .m文件中
@interface MyClass ()
@property (nonatomic, readwrite, strong) NSObject *object;
@end
@implementation MyClass
// Do Something cool
@end
描述BOOL
属性的词如果是形容词,那么setter不应该带is
前缀,但它对应的 getter 访问器应该带上这个前缀。
举个栗子:
@property (assign, getter=isEditable) BOOL editable;
不能使用 "and" 这个词来阐明方法有多个参数。
一个方法的长度必须限制在50行以内(空行不算)。
一个方法只做一件事,只有一个具体的功能(单一原则)。
对于有返回值的方法,每一个分支都必须有返回值。
方法一开始就要对参数的正确性和有效性进行检查,回参数错误立即返回,参数完全无误后才能开始do some important。
不管是声明方法还是调用方法都不能使用冒汗对齐(方法参数超过四个可以酌情使用冒号对齐,增加可读性,但有block时坚决不能使用冒号对齐)。
规范要求:
举个栗子:
//声明
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
//实现
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height {
//这里空一行
//code
}
//或者
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height
{
//code
}
//注释
/**
<#Description#>
@param store <#store description#>
@param searchService <#searchService description#>
@return <#return value description#>
*/
- (instancetype)initWithOperationsStore:(id)store searchService:(id)searchService;
规范要求:
dealloc
方法放在实现文件的最前面(即使dealloc
方法什么也不做),init
应该跟在dealloc
方法后面。init
方法, 指定init
方法应该放在最前面,间接init
方法跟在后面,这样更有逻辑性。init
方法返回类型应该永远使用instancetype
而不是id
。init
方法中做的事情需要在dealloc
方法中撤销。和dealloc
方法里面用 getter 和 setter 方法(点语法访问属性),应当直接访问实例变量。举个栗子:
- (instancetype)init {
self = [super init]; // call the designated initializer
if (self) {
// Custom initialization
}
return self;
}
Objective-C 有指定初始化方法(designated initializer)和间接初始化方法(secondary initializer)的观念。
designated 初始化方法是提供所有的参数的初始化,而secondary 初始化方法是一个或多个参数的选择性初始化,并且提供一个或者更多的默认参数来调用 designated 初始化的初始化方法。
这个栗子很好地展示了Designated 和 Secondary 初始化方法的使用和它们之间的区别:
@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:
就是 designated 初始化方法,另外的两个是 secondary 初始化方法。因为它们仅仅是 designated 初始化方法
使用信息进行(类的)初始化处理期间,会使用一个抽象类(通常作为初始化方法的参数或者判定环境的可用性参数)来完成特定的逻辑或者实例化一个该抽象类的具体的子类。
使用类簇可以帮助移除很多条件语句。
这个栗子很好地展示了如何创建一个类簇:
@implementation ZOCKintsugiPhotoViewController
- (id)initWithPhotos:(NSArray *)photos {
if ([self isMemberOfClass:ZOCKintsugiPhotoViewController.class]) {
self = nil;
if ([UIDevice isPad]) {
self = [[ZOCKintsugiPhotoViewController_iPad alloc] initWithPhotos:photos];
}
else {
self = [[ZOCKintsugiPhotoViewController_iPhone alloc] initWithPhotos:photos];
}
return self;
}
return [super initWithNibName:nil bundle:nil];
}
@end
注:
[self isMemberOfClass:ZOCKintsugiPhotoViewController.class]
防止子类中重写初始化方法,避免无限递归。self = nil
的目的是移除ZOCKintsugiPhotoViewController
实例上的所有引用。 当实例化一个对象需要耗费很多资源,或者配置一次就要调用很多配置相关的方法而你又不想弄乱这些方法时,就需要重写 getter 方法以延迟实例化(在调用getter 方法时才实例化),而不是在 init 方法里给所有对象都分配内存。
但是不要滥用懒加载,需要慎重考虑是否懒加载,因为懒加载同样存在一些问题。
- (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;
}
if-else
规范条件语句体应该总是被大括号包围。
尽管有时候可以不使用大括号(比如条件语句体只有一行内容),但是这样做会带来很多问题隐患。
规范要求:
if-else
这一行,} 则另起一行。else需要要另起一行
。if-else
须穷举所有的情况,而且每个分支都须给出明确的结果。if-else
超过五层的时候, 就要考虑重构, 多层的if-else
结构很难维护。if中的判断
条件过多过长的时候,应该一一换行将所有条件对齐。举个栗子:
if (age < 0) {
return error;
}
else if (age > 200) {
return error;
}
return success;
在使用条件语句编程时,代码的左边距应该是一条“黄金”或者“快乐”的大道。
也就是说,不要嵌套 if
语句。使用多个 return 可以避免增加循环的复杂度,并提高代码的可读性。因为方法的重要部分没有嵌套在分支里面,并且可以很清楚地找到相关的代码。
举个栗子:
- (void)someMethod {
if (![someThing boolValue]) {
return;
}
if (![otherThing boolValue]) {
return;
}
// Do something important
}
//错误做法
- (void)someMethod {
if ([someOther boolValue]) {
// Do something important
}
}
当你的 if 子句有很多复杂的判断条件的时候,就应该把它们这些复杂的条件提取出来赋给一个 BOOL 变量,这样可以让逻辑更清楚,而且让每个子句的意义体现出来。
举个栗子:
BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession = nameContainsSwift && isCurrentYear;
if (isSwiftSession) {
// Do something very cool
}
三元运算符应该只用在它能让代码更加简洁和清楚的地方,而不是使用之后反而会使代码更难以理解。
举个栗子:
result = a > b ? x : y;
result = object ? : [self createObject];
//错误使用
result = a > b ? x = c > d ? c : d : y;
result = object ? object : [self createObject];
当使用枚举的时候,要使用Objective-C的基础类型定义,因为它有更强大的类型检查和代码补全。
并且是使用NS_ENUM和NS_OPTIONS定义枚举。
typedef NS_ENUM(NSUInteger, ZOCMachineState) {
ZOCMachineStateNone,
ZOCMachineStateIdle,
ZOCMachineStateRunning,
ZOCMachineStatePaused
};
typedef NS_OPTIONS(NSUInteger,NYTAdCategory){
NYTAdCategoryAutos = 1 << 0,
NYTAdCategoryJobs = 1 << 1,
NYTAdCategoryRealState = 1 << 2,
NYTAdCategoryTechnology = 1 << 3
};
1)作为参数
当Block作为参数时,尽量只使用一个单独的 block 作为接口的最后一个参数。
把需要提供的数据和错误信息整合到一个单独 block 中,比分别提供成功和失败的 block 要好。
完成处理的 block 的参数很常见:第一个参数是调用者希望获取的数据,第二个是错误相关的信息。但需要遵循以下两点:
objects
不为 nil,则 error
必须为 nilobjects
为 nil,则 error
必须不为 nil- (void)downloadObjectsAtPath:(NSString *)path
completion:(void(^)(NSArray *objects, NSError *error))completion;
2)循环引用
总是如下避免循环引用:
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomethingWithData:data];
[strongSelf doSomethingWithData:data];
}
}];
把通知的名字定义为一个字符串常量,在公开的接口文件中将其声明为 extern
的, 并且在对应的实现文件里面定义。
通知的名字是在全局常量的命名法的基础上添加 Did/Will 动词和"Notifications" 后缀。
多个字符串常量的对齐方法与之前的全局常量一致。
举个栗子:
// .h
extern NSString * const ZOCFooDidBecomeBarNotification
// .m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";
规范要求:
举个栗子:
@interface UIView (ZOCOneLog)
+(void) zoc_viewLog;
@end
Protocols名使用类名+ 协议后缀(Delegate/DataSources/Protocols等)。
@required和@optional不可省略(哪怕其中没有声明方法),且@required在@optional之前。
Protocols方法必须以该类作为第一个参数,用于区分调用Protocols方法的多个该类实例对象。
@class delegateView;
@protocol delegateViewDelegate
@required
- (BOOL)start:(delegateView *)view;
@optional
- (void)colorChange:(delegateView *)view color:(UIColor *)color;
@end
//下面这种写法人神共怒
- (void)changeColorWithColor:(UIColor *)color;
delegate应与Protocols名中协议后缀一致,并且为全小写。
delegate属性应使用weak修饰和id数据类型。
@property(nonatomic, readwrite, weak) id delegate;
对于@optional中的方法,委托者必须在发送消息前检查代理是否确实实现了特定的方法(否则会 crash),确保安全。
if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) {
[self.delegate signUpViewControllerDidPressSignUpButton:self];
}
如果可能,请尽量避免使用单例模式而是使用依赖注入。 然而,如果一定要用单例模,请使用线程安全的模式来创建共享的单例。
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Bundle Identifier使用反域名命名法,全部采用小写字母,一般格式:域名后缀 + 公司顶级域名 + 应用名。
举个栗子:
com.companyname.applicationname
注:为了写出这一篇属于自己的iOS-Objective-C编程规范,我参考了以下文章、文献:
《禅与 Objective-C 编程艺术(Zen and the Art of the Objective-C Craftsmanship 中文翻译)》
https://github.com/NYTimes/objective-c-style-guide
https://www.jianshu.com/p/21f059f04181
https://www.jianshu.com/p/9dd18e69a954
https://www.jianshu.com/p/1784cd67e8de
https://blog.csdn.net/qq350116542/article/details/51195386#commentBox
https://blog.csdn.net/touch929/article/details/72896223
https://juejin.im/post/5940c8befe88c2006a468ea6
Objective-C编码规范:26个方面解决iOS开发问题