没有规矩不成方圆。说说我比较推崇的代码规范。求同存异,别的规范也是可取的,具体问题具体分析。
工程结构
为整个工程创建 workspace
合理的工程目录结构。
Core:通用的机制实现类:统一的任务管理,模块管理,服务管理。
General:公用类和方法,包括工程内基类(Base),公用Category,公用UI组件(CustomUI),公用辅助方法(Helper)和宏定义(Macro)。
Vendors:第三方库。
先按业务划分,再按照MVC来划分。方便解耦,模块化。
将TableViewCell放在View目录,把具体的ViewController放在Controller目录,图片等资源放到Resource目录,工具类放到Utils目录。
头文件
#import的顺序按照:系统类、第三方类、自己类
#import <系统库>
#import <系统库>
#import <第三方库>
#import <第三方库>
#import “其他类”
常量
- 常量用static,不使用#define
static NSString * const AFURLSessionManagerLockName = @"com.alamofire.networking.session.manager.lock";
static NSUInteger const AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask = 3;
开头用k标识
字符k+模板名字首字母大写+作用名称,防止和其他的重复
NSString * const kTTTStrikeOutAttributeName = @"TTTStrikeOutAttribute";
声明Cell的重用
字符k+cell的名称+identifier
static NSString *const kQuestionCellIdentifier = @"kQuestionCellIdentifier";
const声明字符串提供外部使用
h声明m实现且让其他的类用使用。如果导入是UIKit类就使用UIKIT_EXTERN,如果是Founction使用关键词FOUNDATION_EXTERN。
//h声明
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
//m实现
NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
对于#define宏命名
单词全部的大写,单词之间用_分割。
#define RCT_EXPORT_SHADOW_PROPERTY(name, type) \
+ (NSArray<NSString *> *)propConfigShadow_##name { return @[@#type]; }
枚举类型
使用NS_ENUM、NS_OPTIONS语法,不使用enum语法(除非c语言),新的语法有更强的类型检查和代码补全。
typedef NS_ENUM(NSInteger, NetworkStatus) {
// Apple NetworkStatus Compatible Names.
NotReachable = 0,
ReachableViaWiFi = 2,
ReachableViaWWAN = 1
};
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
SDWebImageDownloaderUseNSURLCache = 1 << 2,
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
}
对于NS_OPTIONS类型多值用|连接,不能用+。
NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay
代码组织
使用#pragma mark - 进行分组
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - rewrite
- (void)fatherMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - Draw
- (void)drawRect:(CGRect)rect {}
#pragma mark - Events
- (IBAction)submitData:(id)sender {}
- (void)onBtnClicked:(id)sender {}
#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - Properties
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}
注释代码
应该尽量做到清晰可读,做到自解释。当需要注释时,注释应该更多地用来描述,为什么这样做。注释保持更新,历史的注释应该删除。
可能存在问题的代码应该加上// FIXME:的注释,并详细描述(注意用半角的冒号)。
需要添加逻辑的代码应该加上// TODO:,并详细描述。
不是源码文件创建者修改了代码,应该在修改的代码上加上// Modified:,并写明修改人、修改时间、修改原因等信息。
命名
- 必须尽量遵守苹果官方的命名规范,应该使用长的、描述清楚的函数名、变量名。应该使用三个字母的前缀来给类、常量进行命名。
- 控件命名中UILabel结尾加上Label,UIImageView结尾记上ImageView等等。
@property(nonatomic,strong) UILabel *userNameLabel;
函数(Method)
在函数签名中(函数声明或定义),在函数类型(-/+符号)后,必须有一个空格。参数之间也要有一个空格。参数的keyword不要为空。除了第一个参数,后面的参数的keyword尽量不要加with、and等连词的前缀,keyword更不应该直接用with、and。
//应该
- (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;
//不应该
-(void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height; // Never do this.
初始化方法
返回值应该使用instancetype来代替之前的id。
- (instancetype)init {
self = [super init];
if (self) {
// ...
}
return self;
}
单例模式
单例模式应该使用dispatch_once来保证创建时的线程安全。
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Error处理
当函数返回NSError的引用,调用时要根据返回值进行判断,而不是NSError的值,因为苹果的一些api会往NSError中写一些垃圾数据,如果根据NSError进行判断可能会有问题。
ARC中严重不推荐使用try catch,可能内存泄露。
NSError *error;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
黄金路径(GoldenPath)
当if判断有多层嵌套,应该尽量使用黄金路径,即不符合条件的分支先返回,这样可以避免分支嵌套。
- (void)someMethod {
if (!condition1) {
return;
}
//Do something1
if (!condition2) {
return;
}
// Do something 2
}
尤达表达式
不要使用尤达表达式。
if ([myValue isEqual:@42]) {}
三元运算符
三元运算符, ?: , 应该只在它能提高代码整洁性而且逻辑清晰的情况下使用。判断条件应该只有一个,多于一个的判断条件应该用if或临时变量来写。
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);
布尔值
- 使用YES和NO来表示布尔值。
- true和false除了CoreFoundation和C、C++代码中,不应该被使用。
- nil被判断为NO,没有必要与nil进行比较。
- BOOL值可以最大可以达到8位,而YES只是被设置为1,尽量不要跟YES比较。
//应该
if (someObject) {}
if (![anotherObject boolValue]) {}
//不应该
if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.
- 如果布尔类型的属性名是一个形容词,那么属性虽然可以忽略is 前缀,但最好仍提供带is前缀的getter方法。
@property (assign, getter=isEditable) BOOL editable;
使用NSInteger
Apple的UIKit等代码一般都是用的NSInteger。NSInteger在32位系统是 int,64位系统是long 。系统的代码用的是 NSInteger 的话,你使用int的话,可能不够大而造成崩溃。
摘录
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html
http://www.aichengxu.com/ios/7566968.htm
http://reviewcode.cn/article.html?reviewId=9
http://www.cnblogs.com/netfocus/p/3896118.html
http://www.cocoachina.com/ios/20170105/18515.html
https://github.com/objc-zen/objc-zen-book