建议
- 为了项目的“整洁性”不建议同时使用多语言开发;
- 在导入第三方框架之前,充分考虑导入此框架的必要性和风险,遵循能不用第三方就不用的原则;
- 使用你能想到的最简单、清晰的、合理的方式解决问题,如果你写的代码复杂难懂,请不用思考了就是设计有问题,请换种方式。
一、头文件的导入(#import)
- 写法模板
#import "当前类头发文件"
#import <系统库>
#import <第三方库>
#import "其他类"
尽量按照先系统类 第三方类 自己写的类顺序导入 中间不能有空格
- 建议的写法
#import "YJHomeViewController.h" // 当前类头文件
// controllers
#import "YJLoginViewController.h"
// views
#import "YJCycleScrollView.h"
// models
#import "YJMessageModel.h"
// others
#import "UINavigationBar+Awesome.h"
- 不建议的写法
#import "YJHomeViewController.h" // 当前类头文件
#import "YJLoginViewController.h"
#import "YJCycleScrollView.h"
#import "YJMessageModel.h"
#import "UINavigationBar+Awesome.h"
二、@Class的写法
在.h文件中尽量使用@class,引用头文件
写法模板
@class class1, class2;
- 建议的写法
@class UIView, UIImage;
- 不建议的写法
@class UIPress;
@class UIPressesEvent;
三、@Interface的写法
写法模板
@interface 类名 : 父类 <协议1, 协议2>
@interface和类名中间一个空格
类名后紧跟“ : ”之后空格加上父类协议之间用,空格分割
- 建议的写法
@interface AppDelegate : UIResponder
- 不建议的写法
@interface AppDelegate:UIResponder
四、@property关键词的使用
对象 strong
基本变量 assign
Xib控件、代理用 weak
字符串、block使用 copy
对于一些弱引用对象使用 weak
对于需要赋值内存对象 copy
五、@property的写法
@property(关键词, 关键词) 类 *变量名称; // 注释
关键词用,空格分割 类前后空格
- 建议的写法
@property (nonatomic, copy) NSString *productID; // 产品标识
@property (nonatomic, copy) NSString *status; // 状态
- 不建议的写法
@property (nonatomic, copy) NSString * productID; // 产品标识
@property (nonatomic, copy) NSString * status; // 状态
六、控件尽量放到匿名分类中且分组
控件组
数据源组<建议带上模型类型>
交互变量组
- 建议的写法
@interface YJHomeViewController ()
@property (nonatomic, strong) UIView *navigationBarView; // 导航栏
@property (nonatomic, strong) YJCycleScrollView *advertiseView; // 广告栏
@property (nonatomic, strong) YJPopularRecommendView *popularView; // 热门推荐视图
@property (nonatomic, strong) NSMutableArray *adsArray; // 广告
@property (nonatomic, strong) NSMutableArray *popularInfoArray; // 热门推荐
@property (nonatomic, assign) NSInteger nowPage; // 当前页码
@property (nonatomic, assign) NSInteger selectedIndex;
@end
七、类的功能模块建议按以下方式分组
view的生命周期方法
初始化方法
代理
事件处理
网络请求
懒加载方法
- 建议的写法
#pragma mark - view的生命周期方法
#pragma mark - setup
#pragma mark -
#pragma mark -
#pragma mark - actions
#pragma mark - request dataSource
#pragma mark - setter and getter
八、const的使用
开头用k标识
k + 项目名前缀 + 作用名称 + 模块名(可不写)
- 建议的写法
static NSString *const kYJOpenAccountAgreement = @"yjlc://openAccount"; // 开户
static NSString *const kYJAddRechargeAgreement = @"yjlc://addRecharge"; // 充值
static NSString *const kYJAddWithdrawAgreement = @"yjlc://addWithdraw"; // 提现
定义外部可使用的const常量
- 声明
UIKIT_EXTERN NSString *const kNoticationUpdateCartList;
- 实现
NSString *const kNoticationUpdateCartList = @"kNoticationUpdateCartList";
对于只在.m内部声明的const,建议添加static
- 建议写法
static NSString *kInvestmentCellID = @"InvestmentCellID";
九、enum的定义
对应的enum写到对应的类中,方便寻找和使用
使用NS _ ENUM和NS _ OPTIONS进行定义
- 建议的写法
typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
UIViewAnimationCurveEaseInOut, // slow at beginning and end
UIViewAnimationCurveEaseIn, // slow at beginning
UIViewAnimationCurveEaseOut, // slow at end
UIViewAnimationCurveLinear,
};
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
- 不建议的写法
typedef enum {
GBAppRunDeveloperModeDebug,
GBAppRunDeveloperModePreRelease,
GBAppRunDeveloperModeRelease
}GBAppRunDeveloperMode;
十、三元操作符
简单的if else判断,尽量使用三目运算符,提高代码的简洁性和可读性
- 建议写法
NSInter result = a > b ? a : b;
- 不建议写法
NSInter result;
if (a > b) {
result = a;
} else {
result = b;
}
十一、单例
建议使用 dispatch _ once 来创建单例
- 建议写法
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
十二、init方法
返回类型应该使用instancetype而不是id
- 建议写法
- (instancetype)init {
if (self = [super init]) {
// ...
}
return self;
}
十三、写模块化的代码
好的模块化代码需要满足以下几点:
避免写太长的函数(函数长度最好都不超过40行,因为我的眼球不转的话,最大的视角只看得到40行代码);
制造小的工具函数,让逻辑看起来更清晰,提高代码复用率;
每个函数只做一件逻辑相关的事情,比如读文件函数就只做读取文件操作,写入文件函数就只做写入功能,绝对不掺合其他逻辑;
避免使用全局变量和类成员(class member)来传递信息,尽量使用局部变量和参数(减少对全局变量和类成员的维护成本和出错率);
十四、写可读的代码
有些人以为写很多注释就可以让代码更加可读,然而却发现事与愿违。注释不但没能让代码变得可读,反而由于大量的注释充斥在代码中间,让程序变得障眼难读。而且代码的逻辑一旦修改,就会有很多的注释变得过时,需要更新。修改注释是相当大的负担,所以大量的注释,反而成为了妨碍改进代码的绊脚石。
所以如何写可读的代码,我们首先要明白什么时候写注释,什么时候不用写注释。
什么时候写注释:
有时,你也许会为了绕过其他一些代码的设计问题,采用一些违反直觉的作法。这时候你可以使用很短注释,说明为什么要写成那奇怪的样子。(尽量没有)
后台返回的参数,有时确实需要写简短的注释来标明这个字段的意思。
网络请求的方法,我们有事需要标注方法的作用、参数和返回值。
当一块功能模块,需要严格按照某个业务逻辑执行才行时,可以使用适当的注释。
合理的使用TODO:,当你未完成此功能又急需去完成其它功能的时候,请放一个TODO:在这并解析清楚未完成的工作,保证自己不会忘掉,并在完成后删掉(这个很重要)。
- 建议写法
// 属性
@property (nonatomic, copy) NSString *userName; // 用户昵称
@property (nonatomic, copy) NSString *address; // 用户地址
// 网络请求
/**
* 获取订单详情
*
* @param orderId 订单编号
*
* @return 网络请求
*/
+ (NSURLSessionDataTask *)getOrderInfoById:(NSString *)orderId completionHandler:(void(^)(OrderModel *order, NSError *error))completionHandler;
// 有较强的业务逻辑及TODO:注释
- (void)checkUserAuthentication {
// 判断是否实名认证
if (![YJRuntime sharedInstance].currentUser.tpStatus) {
// TODO: 去实名认证
return;
}
// 判断是否实设置支付密码
if (![YJRuntime sharedInstance].currentUser.payPwdStatus) {
// TODO: 去设置支付密码
return;
}
// 判断是否绑定银行卡
if (![YJRuntime sharedInstance].currentUser.bankcardStatus) {
// TODO: 去绑定银行卡
return;
}
// TODO: ...
}
不写注释,也能写出可读性很强的代码:
使用有意义的函数和变量名字
// 如系统的present方法,不管方法名还是变量名都很好的表达了方法的功能和参数的意义
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion
// 有时候我们为了表达清楚某个方法和参数的意思,避免不了会写的很长
- (void)showWebViewControllerWithProductModel:(YJProductModel *)productModel {
// ...
}
局部变量应该尽量接近使用它的地方
- 建议的写法
- (void)loadDataSource {
...
...
int index = ...;
[self refreshViewByIndex:index];
...
}
- 不建议的写法
- (void)loadDataSource {
...
int index = ...;
...
...
[self refreshViewByIndex:index];
...
}
局部变量名字应该简洁明了
- 建议的写法
boolean success = [self deleteFileWithfilePath:...];
if (success) {
...
} else {
...
}
- 不建议的写法
boolean successInDeleteFile = [self deleteFileWithfilePath:...];
if (successInDeleteFile) {
...
} else {
...
}
把复杂的逻辑提取出去,做成“帮助函数”
- 建议的写法
// 把查询、更新和保存的代码分开了实现
- (BOOL)updateFileContentWithContent:(NSData *)content filePath:(NSString *)filePath {
// 1. 调用查找文件方法
// 2. 更新文件内容代码
// 3. 调用保存文件方法
}
- (NSData *)findContentWithFilePath:(NSString *)filePath {
// ...
}
- (BOOL)saveContentWithFilePath:(NSString *)filePath {
// ...
}
- 不建议的写法
// 把查询、更新和保存文件的所有代码写在一块了
- (BOOL)updateFileContentWithContent:(NSData *)content filePath:(NSString *)filePath {
// 1. 查找文件代码
// 2. 更新文件内容代码
// 3. 保存文件代码
// ...
}
把复杂的表达式提取出去,做成中间变量
- 建议的写法
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"123" forKey:@"OrderID"];
[defaults synchronize];
- 不建议的写法
[[[NSUserDefaults standardUserDefaults] setObject:@"123" forKey:@"OrderID"] synchronize];
在合理的地方换行
- 建议的写法
if (userName.length > 0 &&
[phone checkedPhoneNumber] &&
[password checkedPassword]) {
// ...
}
- 不建议的写法
if (userName.length > 0 && [phone checkedPhoneNumber] && [password checkedPassword]) {
// ...
}
十五、写直观的代码
写代码有一条重要的原则:如果有更加直接,更加清晰的写法,就选择它(即使它有时候看起来更长,更笨,也一样选择它)
- 建议的写法
// 案例: 如果需要多个嵌套进行判断,可以写成下面那样,不满足直接返回的方式
- (void)checkUserAuthentication {
if (![YJRuntime sharedInstance].currentUser.tpStatus) {
// TODO: 去实名认证
return;
}
if (![YJRuntime sharedInstance].currentUser.payPwdStatus) {
// TODO: 去设置支付密码
return;
}
if (![YJRuntime sharedInstance].currentUser.bankcardStatus) {
// TODO: 去绑定银行卡
return;
}
// TODO: ...
}
- 不建议的写法
if ([YJRuntime sharedInstance].currentUser.tpStatus) {
if ([YJRuntime sharedInstance].currentUser.payPwdStatus) {
if ([YJRuntime sharedInstance].currentUser.bankcardStatus) {
// TODO: ...
} else {
// TODO: 去绑定银行卡
}
} else {
// TODO: 去设置支付密码
}
} else {
// TODO: 去实名认证
}
十六、通知及监听的移除
如果类中监听了通知,应该在dealloc方法中移除对象监听
- 建议的写法
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- 不建议的写法
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:name1 object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:name2 object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:name3 object:nil];
}
十七、其它
1. 使用readonly修饰,不被外部修改的属性;
3. 方法命名的规范
- 如果不是写初始化方法不要用init进行开头
- 如果不是属性的set方法不要用set作为方法的前缀
4. 控件命名的规范
- 一定不要单单用首字母简写命名控件(特殊意义的除外,如WTO、RMB等),并且名字后一定要加上控件类型,例如: UILabel结尾加上Label,UIImageView结尾记上ImageView。
- 建议的写法
@property(nonatomic, strong) UILabel *userNameLabel;
- 不建议的写法
@property(nonatomic, strong) UILabel *userName;
5. 大括号{}不换行
- 建议的写法
if(YES) {
// ...
}
- 不建议的写法
if(YES)
{
// ...
}
6. 对于#define宏命名
单词全部的大写,且单词之间用“_”分割,最好加上项目前缀
建议的写法
#define NS_ENUM_AVAILABLE_MAC(_mac) CF_ENUM_AVAILABLE_MAC(_mac)
#define NS_ENUM_AVAILABLE_IOS(_ios) CF_ENUM_AVAILABLE_IOS(_ios)
不建议的写法
#define ZDScreenHeight [UIScreen mainScreen].bounds.size.height
#define ZDScreenWidth [UIScreen mainScreen].bounds.size.width
7. 局部变量
局部的变量在要初始化时,尽量设置默认值(对于一些对象判断是否赋值可以不进行初始化)
- 建议的写法
int index = 0;
- 不建议的写法
int index;
使用驼峰命名法,命名变量
- 建议的写法
UIViewController *viewController = [[UIViewController alloc] init];
// 如果实在是 不想写太长的参数名,可以写成首字母大写的形式,但语义一定要清楚
YJWKWebViewController *webVC = [[YJWKWebViewController alloc] initWithUrl:nil];
- 不建议的写法
UIViewController *viewcontroller = [[UIViewController alloc] init];
YJWKWebViewController *webvc = [[YJWKWebViewController alloc] initWithUrl:nil];
8. 对于NS_OPTIONS类型多个值用“|”连接不能用“+”
- 建议的写法
UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound
- 不建议的写法
UNAuthorizationOptionAlert + UNAuthorizationOptionBadge + UNAuthorizationOptionSound
9. block的命名
尽量和苹果的命名一致使用completion或CompletionHandle结尾,也可用Block命名作为参数名结尾。
- 建议的写法
typedef void(DidUpdateViewWithCompletionHandle)()
- 不建议的写法
typedef void(DidUpdateViewWithCallBack)()
10. 尽量少在initialize或load方法做一些初始化的事情
- 建议的做法
- (void)viewDidLoad {
[super viewDidLoad];
[[UITabBarItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName : GBCOLOR(153, 153, 153, 1.0)} forState:UIControlStateNormal];
[[UITabBarItem appearance] setTitleTextAttributes: @{NSForegroundColorAttributeName : GBCOLOR(255, 129, 55, 1.0)} forState:UIControlStateSelected];
}
- 不建议的做法
+ (void)initialize {
[[UITabBarItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName : GBCOLOR(153, 153, 153, 1.0)} forState:UIControlStateNormal];
[[UITabBarItem appearance] setTitleTextAttributes: @{NSForegroundColorAttributeName : GBCOLOR(255, 129, 55, 1.0)} forState:UIControlStateSelected];
}
11. 属性使用懒加载
- 建议的写法
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [[UILabel alloc] init];
_titleLabel.font = [UIFont systemFontOfSize:16];
_titleLabel.textColor = [UIColor darkGrayColor];
_titleLabel.textAlignment = NSTextAlignmentCenter;
}
return _titleLabel;
}
12. 给controller“瘦身”
对于页面的搭建尽量封装到独立的view中;
model(或viewModel)负责数据的请求和解析;
controller最好只做页面的跳转和交互操作;
合理利用分类,封装和扩展功能。
13. 多用类型常量,少用#define
- 建议的写法
static const NSTimeInterval kAnimationDuration = 0.3;
- 不建议的写法
#define ANIMATION_DURATION 0.3
14. 对于一些状态的判断,使用枚举表示不同的状态
尽量少用根据数字来直接判断不同的状态
- 建议的写法
typedef NS_ENUM(NSUInteger, HomeViewState) {
HomeViewStateNoData,
HomeViewStateFailure,
HomeViewStateItemList,
HomeViewStateBannerList
};
switch(state) {
case HomeViewStateNoData : {
// 显示没数据
break;
}
case HomeViewStateFailure : {
// 显示请求错误
break;
}
case HomeViewStateItemList : {
// 显示商品的列表
break;
}
case HomeViewStateBannerList : {
// 显示banner列表
break;
}
default :
break;
}
- 不建议的写法
if(state == 0) {
// 显示没数据
} else if(state == 1) {
// 显示请求错误
} else if(state == 2) {
// 显示商品的列表
} else if(state == 3) {
// 显示banner列表
} else {}
15. 对于一些自己不确定的可以使用try catch
对于不知道后台返回什么类型的,可以使用try catch (因为OC是运行时语法,可能array不一定是NSArray类型的)
- 建议的写法
int index = 0;
@try {
NSArray *array = obj[@"list"];
index = [array.firstObject intValue];
}
@catch {}
- 不建议的写法
// 如果后台返回list为字段 这段代码就崩溃了 可以使用try catch也可以用Model库 或者自己添加判断
int index = 0;
NSArray *array = obj[@"list"];
if(array.count > 0) {
index = [array.firstObject intValue];
}
16. 遍历方法的选择
- 如果只需要便利数组和字典尽量使用增强for循环方法
- 建议的写法
for(NSString *name in names) {
// ...
}
- 不建议的写法
for(int i = 0; i < names.lenght ; i ++) {
NSString *name = names[i];
// ...
}
- 需要便利字典和数组的内容 并且需要索引用block的遍历方法
- 建议的写法
[names enumerateObjectsUsingBlock:^(NSString * _Nonnull name, NSUInteger idx, BOOL * _Nonnull stop) {
// ...
}];
- 不建议的写法
for(int i = 0; i < names.lenght ; i ++) {
NSString *name = names[i];
// ...
}
17. 如果想进行缓存优先使用NSCache,不要使用NSDictionary进行缓存
- 建议的写法
NSCache *cache = [[NSCache alloc] init];
[cache setObject:object forKey:key];
- 不建议的写法
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:object forKey:key];
18. nil 和 BOOL 检查
- 建议的写法
if(name) {
// ...
}
if (isMyFriend) {
// ...
}
- 不建议的写法
if(name != nil) {
// ...
}
if(isMyFriend == YES) {
// ...
}
19. 数组和字典最好指定元素的类型
- 建议的写法
NSArray *names = [NSArray array];
- 不建议的写法
NSArray *names = [NSArray array];
20. 数组和字典的元素垂直写
- 建议的写法
NSArray *array = @[
@"a",
@"b",
@"b"
];
NSDictionary *dictionary = @{
@"a" : @"",
@"b" : @"",
@"c" : @""
};
- 不建议写法
NSArray *array = @[@"a", @"b", @"b"];
NSDictionary *dictionary = @{@"a" : @"", @"b" : @"", @"c" : @""};
21. 使用CGRect的函数获取值
- 建议的写法
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
- 不建议写法
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
22. 项目中的git分支功能介绍
- master:
- 主分支。只对应线上版本的代码。
- develop:
- 开发分支,永远是最新的代码,团队成员需要开始新需求的时候,都是在这个分支上拉取代码。
- fixbug:
- 修复线上bug的临时分支。代码从master上check下来,上线后merge到master和develop上去,并且会被删除掉。
- release:
- 发布版本时的临时分支,从develop分支上check下来,用于固定发布那一刻的代码版本,如果在发布期间有问题可以在此分支上进行修改。上线后会合并到master和develop上去,并且会被删除掉。
23. git使用
- 配置和克隆代码
1. 配置git名称: git config --global user.name "Bruce Li"
2. 配置git邮箱: git config --global user.email "[email protected]"
3. 查看配置信息: git config -l
4. 克隆代码:git clone "git地址"
- 代码管理
1. 查看文件状态:git status
2. 添加所有文件到暂缓区: git add .
3. 提交暂缓区的代码到本地: git commit -m"提交代码说明。"
4. 用rebase拉去代码:git pull origin 分支名称 --rebase
5. 把代码退到远程服务器:git push origin 分支名称
- 分支管理
1. 查看本地分支 git branch
2. 查看所有分支: git branch -a
3. 切换分支: git checkout 分支名称
4. 创建本地分支: git branch 分支名称
5. 把本地分支提交到远程服务器: git push origin 分支名称
6. 使用rebase合并分支:git rebase 需要和并的分支名称
7. 删除本地分支: git branch -d 分支名称
8. 删除远程分支: git push origin --delete 分支名称
- 标签(tag)管理
4. 查看当前的标签: git tag
5. 打标签:git tag -a "标签名" -m"当前标签的信息"
6. 把当前标签推送到远程: git push origin --tag
7. 删除本地标签:git tag -d 标签名
8. 删除远程标签: git push origin --delete 标签名
github地址:https://github.com/SilongLi