1 格式 1.1 【必须】代码组织 使用
#pragma mark -
将各 protocol 实现函数、功能相近的函数分组排放。函数定义前空一行。
#pragma mark - Initial Methods
#pragma mark - Override Methods
#pragma mark - Private Methods
#pragma mark - Public Methods
#pragma mark - Notifications
#pragma mark - Event Handlers
1.2 【推荐】换行
一行代码不应超过 150 个字符
,超过应该换行。豁免场景:不计算字符串内容的长度。
- (id<UIAdaptivePresentationControllerDelegate>)
adaptivePresentationControllerDelegateForViewController:(UIViewController *)viewController;
- (void)presentWithAdaptivePresentationControllerDelegate:
(id<UIAdaptivePresentationControllerDelegate>)delegate;
1.3 【推荐】函数长度
如果一个函数除空行和注释以外的内容超过了80 行
,则可以思考,能否在不破坏程序结构的前提下,对函数进行拆分。
驼峰式命名:Upper camel case
类名:应该包含一个名词,该名词能清楚的表明类(或类的对象)的描述或者行为。跨应用使用的类和协议必须使用合适的前缀(例如:GTMSendMessage)。
协议名:通用的方式是使用动名词来命名协议。例如:NSLocking
分类名称前缀,表明分类属于哪个项目或模块,如NSString (GTMParsing)
分类的方法前缀,避免和系统库/其他项目/其他模块的方法名称冲突,如gtm_myCategoryMethodOnAString:
文件的扩展名及其意义如下:
.h C/C++/Objective-C 的头文件
.m Objective-C 实现文件
.mm Objective-C++实现文件
.hpp C++头文件
.cpp 纯 C++的实现文件
.c 纯 C 的实现文件
alloc:分配、dealloc:销毁、alt:轮流,交替、calc:计算
pboard:粘贴板(仅对常量)、horiz:水平 、vert:竖直
init:初始化、func:函数、msg:消息、info:信息、rect:矩形
Temp:临时、暂时、nib:interface builder 文档
计算机行业中存在一些首字母缩写词,推荐全大写(优先级高于驼峰命名法!!!)。常见的一些首字母缩写词如下:
ASCII、PDF、XML、HTML、URL、RTF、HTTP、TIFF
JPG、PNG、GIF、LZW、ROM、RGB
CMYK、MIDI、FTP、JSON、OS、ID
宏命名请使用蛇式命名:shouty snake case
,将全部字母大写并合理使用下划线分割单词。同时,类 C 函数风格的命名也是允许的。
#define QQ_DEBUG_BUILD ... // GOOD
#define QQ_ASSERT_GT(X, Y) ... // GOOD, 宏风格
#define QQAssertGreaterThan(x, y) ... // GOOD, 函数风格,参数遵循驼峰命名
#define kIsDebugBuild ... // AVOID
#define unless(X) if(!(X)) // AVOID
对于 Xcode 生成的头文件,默认会生成以#define filename_h
命名的宏来防止多重包含。如:
// QQSharedDefine.h
#ifndef QQSharedDefine_h // 该宏来防止多重包含
#define QQSharedDefine_h
...
#endif /* QQSharedDefine_h */
2.6 【推荐】方法名
返回布尔值的 getter 命名应以 is/can/should 等开头,但属性名不应包含 is/can/should。
@property (nonatomic, getter=isGlorious) BOOL glorious;
- (BOOL)isGlorious;
BOOL isGood = object.glorious; // GOOD.
BOOL isGood = [object isGlorious]; // GOOD.
BOOL isGood = object.isGlorious; // AVOID.
2.7 【必须】变量与属性名
局部变量
和属性
命名首字母小写,采用驼峰命名法。
文件范围
或全局变量
使用 g
作为前缀!!!
static int gGlobalCounter;
常量(const
全局和静态变量)应使用驼峰命名法,不要使用#define
宏来定义常量。整型常量,尽量使用
const
或者枚举;浮点型常量,使用 const
定义。
错误处理需要定义常量时,推荐使用错误相关的类型 NSErrorDomain
和错误相关的枚举宏 NS_ERROR_ENUM
:
extern NSErrorDomain const QQServiceErrorDomain;
NS_ERROR_ENUM(QQServiceErrorDomain) {
QQServiceErrorFileNotFound = -9000,
QQServiceErrorTimeout = -9001,
};
枚举使用 NS_ENUM
。
typedef NS_ENUM(NSInteger, QzoneFeedType) {
QzoneFeedTypeFriends = 0,
QzoneFeedTypeHomepage,
QzoneFeedTypeBlog,
};
位掩码使用 NS_OPTIONS
。
typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
NYTAdCategoryAutos = 1 << 0,
NYTAdCategoryJobs = 1 << 1,
NYTAdCategoryRealState = 1 << 2,
NYTAdCategoryTechnology = 1 << 3
};
2.8 【推荐】通知和异常
通知使用NSNotificationName
作为类型,常量标识,其名称以这种方式组成:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
UIKIT_EXTERN NSNotificationName const NSApplicationDidBecomeActiveNotification
UIKIT_EXTERN NSNotificationName const NSWindowDidMiniaturizeNotification
异常名称由全局NSString
对象标识,以这种方式组成:[Prefix] + [UniquePartOfName] + Exception(每部分首字母大写)
NSColorListIOException
NSColorListNotEditableException
NSDraggingException
3 注释
3.1 【推荐】文件注释
必须包含文件名,作者,创建时间,版权等信息,可以使用 Xcode 工程的默认模板。对文件内容的基本描述。
// QQObj.h
// 消息对应的数据结构
// Created by NAME on 2019/07/30
// Copyright (c) 2019年 Tencent. All rights reserved.
//
3.2 【推荐】声明部分的注释
函数接口应加以注释,以描述函数功能与参数定义,以及其他模块,文件的关系。属性,成员变量,协议等的声明必要时要加上注释。
如果已经在文件头部详细描述了接口,可以直接说明 “完整的描述请参见文件头部”。
对外暴露的所有接口都应该有注释来解释它的作用、参数、返回值。
对外暴露的接口应该在注释中说明线程安全性
。如果类的实例可以被多个线程访问,记得注释多线程条件下的使用规则。
注:接口设计需要经可能的便于UT !!!(不要写无参数无返回值的接口)
3.3 【推荐】实现部分的注释重要或复杂逻辑必须加上注释。
// Set the property to nil before invoking the completion handler to
// avoid the risk of reentrancy leading to the callback being
// invoked again.
CompletionHandler handler = self.completionHandler;
self.completionHandler = nil;
handler();
行尾注释应与代码分开至少 2 个空格,并保持对齐。
[self doSomethingWithALongName]; // Two spaces before the comment.
[self doSomethingShort]; // More spacing to align the comment.
4 函数与方法
4.1 【必须】基本原则
参数个数越少越好,多于 6 个参数
时建议考虑重构。
函数的边界(参数的要求、返回值的范围、是否返回为空)要在注释中写明,且在代码中明确检查,包括断言及if
判断。
事件方法要写参数(如:xxxx:(UIButton *)sender )
4.2 【必须】修饰属性的修饰:readonly
、nonull
、nullable
、null_resettable
(get
不为空,set
可为空)、__null_unsepecified
(不确定是否为空)
__kindof
:当前类 or 其子类
属性:推荐使用上下文相关的非下划线关键字,例如 nonnull
和 nullable
。
其他场景:推荐使用 _Nullable
和 _Nonnull
关键字。
NS_ASSUME_NONNULL_BEGIN // Nonnull Audited Regions
@interface MOClass ()
// 声明属性修饰(必须)
@property (nonatomic, copy, readonly, nullable) NSString *aString;
@property (nonatomic, copy, readonly) NSString * _Nullable aString;
// 方法 返回值 和 参数 修饰(必须)
- (nullable NSString *)methodWithString:(nullable NSString *)aString;
- (NSString * _Nullable)methodWithString:(NSString * _Nullable)aString;
NSArray<GTMBook *> *_Nullable GTMLoadBooksFromFile(NSString *_Nonnull path);
@end
NS_ASSUME_NONNULL_END
可以使用区域设置( NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
)或可空性变量修饰符修饰参数。
注:弃用 __nullable
和 __nonnull
(苹果为了避免与第三方库潜在的冲突,把 __nullable
和 __nonnull
改成了_Nonnull
/_Nullable
)
4.3 【必须】
nil
检查
字符串判空:QLSafeString(str) (str?:@"")
、QNBSafeString(str) (str?str:@"")
nil
检查只用在逻辑流程中,避免逐行代码地在对象发消息前进行 nil
检查。对 nil
发送任何消息都是可以的。
存入NSArray
和NSDictionary
的数据要判空:!= nil && != NULL
建议使用点语法来访问或者修改 OC 类的属性,访问其他 OC 方法时首选方括号方式。
init
相关方法和 dealloc
里面不要用点语法!!!
// 使用 Xcode 7 及以上版本的所有项目都应该使用 Objective-C 轻量级泛型表示法来表明容器包含的对象。
@property (nonatomic, copy) NSArray<Location *> *locations;
@property (nonatomic, copy, readonly) NSSet<NSString *> *identifiers;
NSMutableArray<Location *> *mutableLocations = [otherObject.locations mutableCopy];
// 如果类型比较复杂,请考虑使用 typedef 来保持可读性。
typedef NSSet<NSDictionary<NSString *, NSData *> *> TimeZoneMappingSet;
TimeZoneMappingSet *timeZoneMappings = [TimeZoneMappingSet setWithObject:...];
// 如果类型不确定,使用 id 来声明。
@property (nonatomic, copy) NSArray<id> *unknowns;
4.6 【必须】异常的使用
(1)可以使用 @try/@catch/@finally/@throw
来进行异常处理。
(2)也可以通过返回值(nil
, NULL
, NO
或者 错误码
)
(3)或者传递一个 NSError
对象来返回错误。
鉴于使用异常的代价较高(安装包、退堆栈带来的性能开销,此外还可能引发内存泄露),条件允许时,应该优先使用 NSError
对象或者返回错误码形式,但对于第三方组件的代码,在使用时,应使用 @try/@catch
进行异常保护
对于后台返回的数据以及文件中读取的数据,应进行足够的校验与异常保护。包括但不限于对数据类型、长度进行校验,使用 @try/@catch
进行序列化,反序列化过程的保护等。
if-else
结构不能超过四层
。条件分支中最快路径代码要放在最前面,可以有多个return
。所有的for
,if
,while
等语法结构主体都必须用花括号,即使主体代码只有一行。
5.2 【可选】BOOL
陷阱
将常规整数值转换为 BOOL
,请使用三元运算符返回 YES
或 NO
值。对
BOOL
使用逻辑运算符 (&&
, ||
和 !
) 是可以的,其返回值可以安全转换为 BOOL
,无需三元运算符。
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}
// AVOID:
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait; // AVOID.
}
- (BOOL)isValid {
return [self stringValue]; // AVOID.
}
永远不要直接将 BOOL
变量与 YES
比较,返回值可能不如你所愿。BOOL
定义为signed char
,因此它可能具有除 YES
(1
) 和 NO
(0
) 之外的值。也没有必要将 BOOL 值与 NO 比较,使用if
以及!
进行判断会使代码更为直观。
BOOL great = [foo isGreat];
if (great) { } // GOOD.
if (![someObject boolValue]) {} // GOOD.
// AVOID:
BOOL great = [foo isGreat];
if (great == YES) { } // AVOID. 永远别这么做
if ([someObject boolValue] == NO) { } // AVOID
6 类与对象
当创建NSString
, NSDictionary
,NSArray
,和NSNumber
类的不可变实例时,都应该使用字面量。
对于需要继承你的类的人来说,明确指定初始化方法十分重要。这样他们就可以只重写一个初始化方法(可能是几个)来保证他们的子类的初始化方法会被调用。这也有助于将来别人调试你的类时,理解初始化代码的工作流程。
// 禁用 无效的 初始化方法
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithCoder NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
// 指定初始化方法
- (instancetype)initWithFrame:(CGRect)frame
type:(NSInterger)type NS_DESIGNATED_INITIALIZER;
6.3 【必须】初始化函数简洁
6.4 【必须】保持公共 API 简单
7 Cocoa 相关
7.2 【必须】视图布局
避免在界面布局中使用magic number,应使用能够说明用途的常量。
建议在界面布局时使用相对布局,例如:
使用目标view
在父view
中的相对位置使用目标view
与相关view
中的相对位置使用目标view
与相邻view
中的相对位置 当访问一个 CGRect
的 x
,y
, width
, height
时,应该使用CGGeometry
函数代替直接访问结构体成员。
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
8 单测相关
8.1、单例的mock
不能直接mock
单例的,会引起mock
冲突。
推荐的写法:
id center = OCMPartialMock([[QLLoginCenter alloc] init]); // 每次mock alloc 一个单例
OCMStub([[center classMethod] sharedInstance]).andReturn(center); // mock 它的 sharedInstance 方法
8.2、测试待Assert的代码:
BOOL executed = NO;
@try {
executed = YES;
NSUInteger invalidCount = [vc numberOfUnreadMessagesWithID:@"invalidBlockID"];
XCTAssertEqual(invalidCount, 0);
} @catch (NSException *exception) {
XCTAssertNotNil(exception);
}
9 补充: 9.1、extern用:FOUNDATION_EXPORT 9.2、更新布局
这个不可以直接调用layoutSubviews
,可以用setNeedLayout
,如果等不到下一次刷新可以调用layoutIfNeeded
- (void)layoutSubviews {
[super layoutSubviews];
// TODO
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (button.superview == self.view) {
[button sizeToFit]; // 可以这样获得自适应size
}
}
9.4、synthesize/dynamic
// 系统默认实现
@synthesize propertyName = _propertyName;
// @dynamic 阻止自动合成
9.5、判断是否实现了指定协议的方法
[MOClass conformsToProtocol:@protocol(MOLockingProtocol)];
[vc conformsToProtocol:@protocol(MOLockingProtocol)];
9.6、IOC:inversion of control
控制反转
如:Cell
持有VM
,但是VM
不持有Cell
;当VM
需要通知Cell
更新时,可以先注册Block
,在需要时调用就好,就不会导致互相依赖这样高耦合的代码,即控制反转。
自身的头文件
系统库的头文件
开源第三方库的头文件
内部第三方库的头文件
模块内的头文件
项目内的头文件
不同类型的头文件中间最好空行,同类型的头文件尽量按照字母顺序排列
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)