-------------------------------------编码原则-------------------------------------
- 需求是暂时的,只有变化才是永恒的,
面向变化编程
,而不是面向需求编程; - 不要过分追求技巧,从而降低
程序的可读性
; - 简洁的代码可以让bug无处藏身,要
写出明显没有bug的代码
,而不是没有明显bug的代码
; - 先解决问题,再考虑将来的扩展问题。
-------------------------------------编码原则-------------------------------------
代码格式化
- 使用系统自带的格式化工具,选中要格式化的代码后使用
control + i
快捷键进行代码格式化
代码片段
- 将CodeSnippets文件夹放到这里
/Users/xxxx/Library/Developer/Xcode/UserData
下面,重启Xcode即可 - 代码片段使用说明
代码片段快捷键 | 片段说明 |
---|---|
sk_mark | 快速添加mark注释 |
sk_mark_controller_methods | 控制器中方法布局 |
sk_mark_single_lifeCycle | 控制器view生命周期方法汇总 |
sk_mark_single_intial | 页面初始化相关方法汇总 |
sk_mark_single_network | 网络请求相关方法 |
sk_mark_single_override | 重载的方法 |
sk_mark_single_eventResponse | 事件和响应方法汇总 |
#pragma mark - <UITableViewDelegate> | 代理方法汇总 |
sk_mark_single_public | 对外公开的方法汇总 |
sk_mark_single_private | 私有方法汇总 |
sk_mark_single_gettersAndSetters | 属性集中营 |
sk_view_Define | 自定义view |
sk_pro_assign | 快速定义assign属性 |
sk_pro_copy | 快速定义copy属性 |
sk_pro_strong | 快速定义strong属性 |
sk_pro_weak | 快速定义weak属性 |
sk_block_var | 定义block变量,直接定义属性或成员变量 |
sk_block_typedef | 定义block类型 |
sk_yy_pro_whiteList | 属性白名单,当不参与转换的属性个数远远超过参与转换的属性个数时可以用此方法来指定哪些属性需要参与字典转模型 |
sk_yy_property_blackList | 在黑名单中属性“不参与”字典转模型 |
sk_yy_propertyMapper | 属性映射 |
sk_yy_array_objectMapper | 指定数组属性中的实体类型 |
sk_request_multiple | 多个请求优雅处理 |
sk_single_obj | 实现单例对象 |
开发中遇到的规范问题总结
- 引用的头文件需要做归类处理,见下面关于在Controller和View中头文件的分类规范
- 正确使用空格,不要有多余的空格
- 属性定义要有合适的空格
@property (nonatomic, copy, readonly) NSString *userName;
- 方法定义要有合适的空格
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { }
- 属性与属性间不要有空格
@property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource; @property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
- 方法与方法之间必须有一个空格的间隙
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- 做好属性
归类
,eg: 视图属性放在一起,数据属性放在一起等 - 头文件引入规范
- 代理引入书写规范,超过三个以上的代理就要按如下格式
@interface HCConversationListViewController ()
<
UITableViewDelegate,
UITableViewDataSource,
MGSwipeTableCellDelegate,
HCIMConversationListManagerDelegate
>
- 警告必须去除
- 注释规范,不要自己敲,直接使用快捷键
command+option+/
生成 - 私有方法命名规范,私有方法统一使用
下划线开头
- 使用
代码片段来统一规范
- 少用宏,多用常量
- if层级嵌套不允许超过
三级
,要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回
-
类
不能超过1000行
,方法
不能超过100行
- 使用局部变量接收一个链式调用的属性值,再用这个局部变量再去完成后面的逻辑
// 先用一个临时变量接收常常的属性值
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
// 再在下面使用keyWindow,不要使用[UIApplication sharedApplication].keyWindow
- 多处使用的字符串需要定义成常量,不允许直接使用字符串
- 字典
不允许采取@{}
的方式定义,使用NSMutableDictionary
的方式定义 - 不要在Controller里进行数据加工,放到Manager里
- 不要直接使用int,而应该使用NSInteger
-
在头文件里说明类的用途
- 删除无用的注释代码
- 同样的功能不能写多份
- 不要在init和dealloc函数中使用属性
-
subView
的初始化放到getter中去做,getter和setter
全部都放在类的最后
少用storyboard、xib
- 万不得已不要用继承,优先考虑组合
团队Xcode 开发统一开发字体
- 这里推荐
JetBrains Mono
,据说 JetBrains Mono 是最适合程序员的字体之一 - JetBrains Mono字体下载
- Mac直接选择系统自带的
字体册
程序设置字体,设置如下
- 另外一种安装方法:将下载的字体压缩文件的
zip后缀改成otf
,然后双击安装
即可
名词定义
-
大驼峰
式命名:每个单词的首字母
都采用大写
字母 -
小驼峰
式命名:第一个单词
首字母小写
,剩下单词的首字母大写
命名规范
- 命名原则
- 明确表达含义,尽量做到不需要注释也能了解其作用,
若做不到,就加注释
; - 使用全称,尽量不使用缩写(
使用规范的缩写,不要自创
); - 公共接口不仅命名要规范,也要添加上注释,方便使用者快速了解接口的含义,最好在注释里
给出使用示范
;
- 明确表达含义,尽量做到不需要注释也能了解其作用,
类名
- 采取
大驼峰
式命名 - 示例:XXHomeViewController,
XX是项目前缀
,不属于命名范畴,这里不要产生误解
私有变量
- 私有成员变量在.m文件中声明
-
以_开头
,小驼峰式命名,示例:NSString *_privateVar;
或者在扩展类中定义属性,二者选其一即可
property变量
-
小驼峰
式命名,示例:userName
- @property和左括号间
有空格
,有括号和类型间有空格
,括号累不修饰符之间有空格
,星号和变量名之间没有空格
,属性的修饰关键字推荐排序:原子性,内存管理,读写权限(默认是可读写)
,示例: - iOS中不建议使用
atomic
修饰符
@property (nonatomic, copy, readonly) NSString *userName;
-
NSString、NSArray、NSDictionary、block属性
应该使用copy
关键字;
宏常量命名
-
#define
预处理定义的宏常量全部大写,单词间用_
分隔,示例:
#define MD_SCREEN_BOUNDS [[UIScreen mainScreen] bounds]
- 宏定义中如果包含表达式或变量,表达式或变量必须用
小括号括起来
,示例:
#define MD_SCALING ([[UIScreen mainScreen] bounds].size.width/375.f) // 缩放比例
类型常量命名
- 对于
仅在.m文件中
使用的常量要使用static
进行定义,命名方面以字符k
开头,示例:static
NSTimeIntervalconst
kAnimationDuration = 0.3; - 对于定义于
.h文件
中的常量,对外部可见,则以定义该常量所在类的类名开头
(仿照苹果风格),在头文件中使用UIKIT_EXTERN
声明,在.m文件
中定义其值,示例:
//在.h文件中:
UIKIT_EXTERN NSString * const UIApplicationStatusBarOrientationUserInfoKey;
//在.m文件中:
NSString * const UIApplicationStatusBarOrientationUserInfoKey = @"xxxxxx";
block
- 使用
dispatch_block_t
替代void (^XXXBlock)()
这类型的block
枚举
- 与类的命名规则一致,采取
大驼峰
式命名,示例:UIControlState - 枚举内容的命名需要以该枚举的类型名称开头,示例:
UIControlState
Normal -
NS_ENUM
定义通用枚举,NS_OPTIONS
定义位移枚举,详见位移枚举NS_OPTIONS详解
delegate
- 用
Delegate
做后缀,示例:UIScrollViewDelegate
- 用
@optional
修饰可选实现
的方法,用@required(默认值)
修饰必须实现
的方法 - 类的实例
必须
为代理方法的参数之一- 如果只有
一个
参数,方法名要符合实际含义,示例:- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
- 如果有
两个及以上
参数,以类的名字开头,以表明此方法是属于哪个类的,示例:- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- 如果只有
- 当你的
代理的方法过多
, 可以拆分数据部分
和逻辑部分
, 数据部分用DataSource
做后缀,示例:UITableViewDataSource,逻辑部分以Delegate
做后缀,示例:UITableViewDelegate - 代理定义中使用
did
和will
来表达已发生的变化 或 将要发生的变化
方法
- 方法名用小驼峰式命名
- 方法名不要使用
new
作为前缀 - 不要使用
and
来连接属性参数 - 如果方法描述两种独立的行为,使用
and
来串接它们 - 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐
// ------------- 不要使用 and 来连接属性参数 -------------
// 错误示范
- (void)openURL:(NSURL *)url andOptions:(NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *)options andCompletionHandler:(void (^ __nullable)(BOOL success))completion {
}
// 正确示范
- (void)openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenExternalURLOptionsKey, id> *)options completionHandler:(void (^ __nullable)(BOOL success))completion {
}
// ------------- 表示对象行为的方法示范 -------------
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
}
// ------------- 对象初始化方法定义示范 -------------
+ (instancetype)arrayWithObject:(id)anObject {
}
- (instancetype)initWithObject:(id)anObject {
}
// ------------- 如果方法描述两种独立的行为,使用and来串接它们 -------------
- (void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (NS_NOESCAPE ^)(KeyType key, id obj, BOOL *stop))block {
}
// ------------- 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐 -------------
- (void)addObserver:(id)observer
selector:(SEL)aSelector
name:(nullable NSNotificationName)aName
object:(nullable id)anObject {
}
代码注释
注释规范
- 优秀的代码大部分是可以自描述的,完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助。
- 即使代码有自描述,以下三种情况也必须写好注释:
- 公共接口(注释是用来快速告诉阅读代码的人,当前类实现什么功能)
- 涉及到专业知识的代码(注释要说明实现原理和思想)
- 容易产生歧义的代码(
严格来说,容易让人产生歧义的代码是不允许存在的
) - 备注:除了上述三种情况,如果别人还是只能依靠注释才能读懂你的代码,就要反思你写的代码了
- 对于注释的内容,相对于
做了什么
,更应该说明为什么这么做
import注释
- 如果有一个以上的
import
语句,就要对这些头文件进行分组 - Controller中
// 分类
// 工具类
// Controllers
// Views
// Models
// Managers
- View中
// 分类
// 工具类
// Views
// Models
属性注释
- 使用快捷键
command+option+/
即可自动生成注释模板,直接看示例:
/// 账户名称
@property (nonatomic, copy) NSString *account;
方法声明的注释
- 使用快捷键
command+option+/
即可自动生成注释模板,直接看示例:
/// <#Description#>
/// @param anObject <#anObject description#>
/// @param index <#index description#>
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
}
代码块注释
- 单行的用
//+空格
开头 - 多行的采用
/** */
注释
TODO
- 使用
//TODO:
来标记一些未完成
的地方,这里的 TODO 和 // 之间的空格就不需要
了
代码格式
指针 * 的位置
- 靠近变量,示例:NSString
*_userName
;
方法的声明(定义)
- 在 - 、+ 和 返回值 之间留一个空格,方法名和返回值之间不留空格,方法名和参数之间不留空格,示例:
- (
void)insertSubview
:(UIView *)view
atIndex:(NSInteger)index {
};
代码缩进
- 在 Xcode > Preferences > Text Editing 将 Tab 和 Indent都设置为
4个空格
- 多个方法的声明(定义),Method1 与 Method2 之间空一行
- 一元运算符与变量之间
没有空格
,示例:!varName; - 二元运算符与变量之间
必须有空格
,示例:(逻辑运算) ? 选项1 : 选项2;
不要出现
一行多句代码
controller中的方法分组
#pragma mark - Life Cycle Methods
// 这里是控制器的生命周期方法汇总
// 不要在viewDidLoad方法里写view的初始化逻辑,封装到属性中,这里只做addSubViews的操作
#pragma mark - Intial Methods
// 这里是初始化方法汇总
#pragma mark - Network Methods
// 这里是网络请求方法汇总
#pragma mark - Override Methods
// 这里是重载的父类的方法汇总
#pragma mark - Event Response Methods
// 这里是事件方法汇总,比如:按钮点击事件,手势响应方法,定时器响应方法等
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
// 这里是代理相关的方法,可以是【系统代理方法】或者【自定义的代理方法】
// 这里【必须】写上代理名称,方便别人查看
#pragma mark - Public Methods
// 这里是对外公开的方法汇总
#pragma mark - Private Methods
// 一般情况这种私有方法分类是不应该出现的,如果一个方法实在在上面的分类中找不到放的位置就放到这里吧
#pragma mark - getters and setters
// 这里是一些属性的读写方法,之所以放到最后是为了不至于因为这里的属性太多导致主要代码被挤到很后面
- 控制器中视图的生命周期的各个阶段适合做的事情
- viewDidload
- 在此方法里
只做addSubview
的事情,subView的初始化放到getter中去做,这样职责比较清晰 -
不要
在updateViewConstraints
方法里做add constraints
,建议在此方法创建
Constraints并添加
,但是最好将创建并添加Constraints
封装到一个方法(如:- (void)layoutPageSubviews{ }
)中,然后在viewDidload
调用此方法,避免viewDidload
里代码行数太多
- 在此方法里
- viewWillAppear
- 更新Form数据
-
缺点
:pop回来也会更新数据,会导致网络请求增加
- viewWillLayoutSubviews
or
viewDidLayoutSubviews- 在这里设置布局
- viewDidAppear
- 做Notification的监听之类的事情
- 如果控制器view是
UIScrollView及其子控件
,那么viewWillLayoutSubviews
和viewDidLayoutSubview
会调用
的非常频繁
,因为UIScrollView及其子控件在导航控制器
中受contentInset
属性的影响导致布局改变,就会调用viewWillLayoutSubviews
和viewDidLayoutSubview
- viewDidload
view中的方法分组
#pragma mark - Life Cycle Methods
// 这里是view的生命周期方法汇总
#pragma mark - Intial Methods
// 这里是初始化方法汇总
#pragma mark - Override Methods
// 这里是重载的父类的方法汇总
#pragma mark - Event Response Methods
// 这里是事件方法汇总,比如:按钮点击事件,手势响应方法,定时器响应方法等
#pragma mark - Public Methods
// 这里是对外公开的方法汇总
#pragma mark - Private Methods
// 一般情况这种私有方法分类是不应该出现的,如果一个方法实在在上面的分类中找不到放的位置就放到这里吧
#pragma mark - getters and setters
// 这里是一些属性的读写方法,之所以放到最后是为了不至于因为这里的属性太多导致主要代码被挤到很后面
大括号写法规范
- 首先看一下苹果默认生成的代码中大括号的写法
- (void)viewDidLoad {
[super viewDidLoad];
}
- 对于其他使用场景左括号跟在第一行后边,留一个空格,如下示范
if (<#condition#>) {
<#statements#>
} else {
<#statements#>
}
while (<#condition#>) {
<#statements#>
}
编码规范
if语句
- 不要使用过多的分支,
要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回
,示例:
-
条件过多,过长的时候应该换行
-
条件语句的判断建议:变量在右,常量在左
for语句
- 示范
for (<#type *object#> in <#collection#>) {
<#statements#>
}
- 尽量使用
for in
循环语句
switch语句
- 示范
switch (<#expression#>) {
case <#constant#>:
<#statements#>
break;
default:
break;
}
- 对于switch语句中的各个case中的业务
逻辑语句大于一句
的需要使用大括号阔起来
switch (<#expression#>) {
case <#constant#>: {
<#statements#>
}
break;
default: {
}
break;
}
-
使用枚举类型时,不能有default分支
,因为在switch
语句使用枚举类型的时候,如果使用了default
分支,新增枚举类型时就无法通过编译器来检查到新增的类型 -
除了
使用枚举类型以外,都必须
有default分支
函数
- 一个函数只做一件事(单一原则)
- 对输入参数的正确性和有效性进行检查,参数错误立即返回,
要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回
- 如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数
- 将函数内部比较复杂的逻辑提取出来作为单独的函数
去除iOS项目中无用的警告
切记不是所有警告都可以直接忽略的
- 只屏蔽
无关紧要
的警告,目的是让我们更加快速找到需要修复的警告 - 有些警告是代码编写本身有误,可能引起Bug,需要及时修复
执行代码诊断命令
- 在Xcode中运行
Build & Analyze(⌘⇧B)
后你会得到的惊人结果 - 这是Clang 的更细腻,更深沉一面的功能
- Clang是 C / Objective-C 的前端的 LLVM 编译器。
- Clang对 Objective-C 的语义和语法有着深刻的理解,而且更重要的原因是现在 Objective-C 已经是这样一个有能力的语言了。
打开Treat Warnings as Errors
配置
- 在
Build Settings
搜索Treat Warnings as Errors
并设置为YES - 设置
-Weverything
标志 - 这样设置后大部分项目是无法编译过的,可以有针对性的进行优化,我个人支持这个建议,并鼓励其他开发者更严肃的对待编译警告
去除pod库的警告
- 在podfile文件中添加如下配置
# 忽略所有警告
inhibit_all_warnings!
# 忽略指定库的警告
pod 'AFNetworking', :inhibit_warnings => true
- 然后执行:
pod install
警告:Pointer is missing a nullability type specifier
- 苹果提供了两个宏来去除此类警告
- 使用此宏包住多个属性使其具备
nonnull
(不可空),然后仅对需要nullable
(可空)的改下就行
NS_ASSUME_NONNULL_BEGIN
coding...
NS_ASSUME_NONNULL_END
- 使用方法:在
.h文件
的头部和尾部添加两个宏
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface XXXModel : NSObject
@end
NS_ASSUME_NONNULL_BEGIN
- 使用了上面的宏后出现警告
Null passed to a callee that requires a non-null argument
,对需要nullable
(可空)的改下就可以去除此警告
打开Treat Incompatible Pointer Type Warnings as Errors
开关
- 开启后类型不兼容会报错,而不是警告
去除directory not found for option
警告
- 在
TARGETS
选在对应的项目 - 在
Build Settings
里找到Library Search Paths
和Framework Search Paths
删除找不到的路径即可
去除This block declaration is not a prototype
警告
- 在
Build Settings
里找到Strict Prototypes
设置为NO即可
关闭工程中指定类型的警告的设置步骤
- 选中指定类型的警告,选择
Reveal in Log
,如下图操作
-
则会显示下图
-
[-Wshorten-64-to-32]
中括号中的就是警告类型 -
-W
表示打开
指定类型的警告 -
-Wno-
表示关闭
指定类型的警告 - 将
-W
换成-Wno-
变成-Wno-shorten-64-to-32
- 将此类型的警告的忽略配置添加到
Other Warning Flags
中,如下图
如何避免误使用高版本API导致的崩溃问题
- 打开
-Wunguarded-availability
在调用高版本API时候报warning,为避免warning过多而忽视,用-Werror-unguarded-availability
标记强制编译不过
- 如果代码本身安全(使用了
respondsToSelector:
保护),可以用下面两种方式去除警告- 方式一
// 忽略警告:误使用高版本API版 #define K_Warning_Ignored_Start_ApiCheck _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wunguarded-availability\"") // 这里写忽略警告的code #define K_Warning_Ignored_End_ApiCheck _Pragma("clang diagnostic pop")
- 方式二
@available
if (@available(iOS 8.0, *)) { // 这里写忽略警告的code }
- 针对pod需要在
.podspec
文件中添加compiler_flags
配置