无标题文章
iOS代码规范
修订历史
版本号 修改内容修改日期修改人
1 1.0新建文档 2018.6.5 江文俊
说明
本文将以条款的形式逐条说明每个规范,每个条款会标记[强制]、[推荐]、[良好]三者
中的一个标签(如无标签,默认为[推荐]),其中:
[强制]:该条款需要在代码编写过程中严格使用。否则在个别情况下可能会导致程序行为异常甚至崩溃。
[推荐]:该条款或为Apple官方推荐,或为通用习惯,或明显增强一致性和可读性,推荐使用。
[良好]:该条款为可选使用,使用后能够带来更好的一致性,但违反的话不会给代码阅读带来太大影响,可根据个人习惯酌情选择。
标示符的命名采用PascalCase、CamelCase和UpperCase三种方式:
PascalCase:标示符的首字母和其后连接的每个单词的首字母大写,其他字母小写, 单词中间不使用其他字符,比如 CurrentViewState。
CamelCase:标示符的首字母小写,其后连接的每个单词的首字母大写,其他字母 小写,比如 currentViewState。
UpperCase:标示符中的所有字母均大写,两个词之间使用下划线连接,主要用于常 量定义中,比如 CURRENT_VIEW_STATE。
1. 命名规范
1.1. 通用
1.1.1. 清晰明确
使用明确并且简洁的名字,但是不能为了简洁而使得名字的含义不够明确。
推荐不推荐
insertObject:atIndex:insert:at:
removeObjectAtIndex: removeObject:
remove:
除了广泛使用和项目中明确约定的缩写,不使用其它缩写
推荐不推荐
destinationSelection destSel
setBackgroundColor: setBkgdColor:
1.1.2. 一致性
在不同的类中做相同事情的方法应该是用相同的名字。在不确定应该使用什么名字的时候,可以参考以前的头文件或者接口文档。
名称 说明
- (NSInteger)tag 在NSView, NSCell, NSControl中使用
1.2. 类(协议)名
类名遵循PascalCase方式,以三个大写字母作为前缀(双字母前缀为 Apple 的类预留), 后跟名词或名词短语
推荐不推荐
BBA BA
通用组件、类库使用三个字母的前缀
推荐不推荐
BBAPushManager PushManager
另一个好的类的命名规范:当你创建一个子类的时候,你应该把说明性的部分放在前缀和父类名的在中间。
举个例子:如果你有一个 ZOCNetworkClient 类,子类的名字会是ZOCTwitterNetworkClient (注意 "Twitter" 在 "ZOC" 和 "NetworkClient" 之间); 按照这个约定, 一个UIViewController 的子类会是 ZOCTimelineViewController.
1.3. 方法名
方法名称遵循CamelCase方式,私有方法不要使用“_”开头(避免和苹果的私有方法重名)。除非特别需要,不使用数字命名。
推荐不推荐
- (instancetype)init; - (instancetype)Init;
- (void)reloadData; - (void)_reloadData;
- (void)load; - (void)load1;
表示对象动作的方法以动词开头,不要在动词前面加do、does或者一些副词和形容词:
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem
如果方法返回一个对象或一个值,直接使用该返回值的名词形式作为方法名。不使用“get”开头命名这些方法,除非返回值是通过一个或多个参数返回的。
推荐不推荐
- (NSSize)cellSize; - (NSSize)calcCellSize;
- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; - (NSSize)getCellSize;
如果方法表示判断某种状态并返回 BOOL 变量,可以使用“should”、 “can”、“will”、“has”等修饰以明确方法的含义。
- (void)setCanHide:(BOOL)flag;
- (BOOL)canHide;
- (void)setShouldCloseDocument:(BOOL)flag;
- (BOOL)shouldCloseDocument;
在所有参数前面都使用描述词。
推荐不推荐
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag; - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
使参数名称前面的词正确的描述该参数,参数的名称能够帮助调用者正确赋值
推荐不推荐
- (id)viewWithTag:(NSInteger)aTag; - (id)taggedView:(int)aTag;
尽可能少用 "and" 这个词。它不应该用来阐明有多个参数。如果一个方法完成两个动作,使用"and"连接。
推荐不推荐
- (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes; - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height; - (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
对于 delegate 方法,以触发该方法的对象所属类型名称开头(去除前缀),并且该对象作为第一个参数,以标记该 delegate 方法的调用者。如果 delegate 方法没有其他参数,则上述类型名也可以出现在方法名称的最后,并且调用者对象作为唯一参数(同时应用于 类似 delegate 作用的 block 方法) 。
will表示即将发生事件
did表示已经发生事件
should为询问是否可以做某件事情
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
- (void)windowDidChangeScreen:(NSNotification *)notification;
- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
- (BOOL)windowShouldClose:(id)sender;
对于Category中的方法,应该在方法名前加上自己的前缀以及下划线。
推荐不推荐
- (NSString *)zoc_timeAgoShort; - (NSString *)timeAgoShort;
1.4. 函数名
函数遵循 PascalCase 方式,以大写前缀开头(类似类名),其他规则与方法相同。
float NSHeight(NSRect aRect)
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)
1.5. 属性和变量
属性(变量)名称遵循 CamelCase 方式,以名称或形容词命名(属性通常表示对象的某种状态),如果属性的含义是一个判断性的动作,在动词名称前面添加“should”、“can”、will”等词修饰,以区分对应的方法名称。
推荐不推荐
@property (strong) NSString *title; @property (strong) NSString *getTitle;
类的成员变量以下划线开头,其他同属性的命名规则,全部声明在.m文件中。如果一定要在.h中声明,要加上@private或者@protected
推荐不推荐
@implementation MyClass
{
BOOL _showsTitle;
}
@interface MyClass
{
BOOL _showsTitle;
}
1.6. 常量
1.6.1. 枚举类型
需要定义一组整型常量时建议使用枚举,其命名规则与“函数”的命名规则一致。
typedef NS_ENUM(NSInteger, UIDeviceOrientation){
UIDeviceOrientationUnknown,
UIDeviceOrientationPortrait,
UIDeviceOrientationPortraitUpsideDown,
UIDeviceOrientationLandscapeLeft,
UIDeviceOrientationLandscapeRight,
UIDeviceOrientationFaceUp,
UIDeviceOrientationFaceDown
};
typedef NS_OPTIONS(NSUInteger, NSSortOptions) {
NSSortConcurrent = (1UL << 0),
NSSortStable = (1UL << 4),
};
使用NS_ENUM和NS_OPTION定义枚举
1.6.2. 使用const定义的常量
非整型类型的常量可以使用 const 来定义,其命名规则与“函数”的命名规则一致,并以相关类名作为前缀。 (如果一个int类型的常量与其它常量无关时,也可以使用const定义)
推荐使用常量来代替字符串字面值和数字,这样能方便复用,而且可以快速修改而不需要查找和替换。常量应该用 static 声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用。常量应该在头文件中以这样的形式暴露给外部:
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
并在实现文件中为它赋值。
只有公有的常量才需要添加命名空间作为前缀。尽管实现文件中私有常量的命名可以遵循另外一种模式,你仍旧可以遵循这个规则。
推荐不推荐
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4; static const NSTimeInterval fadeOutTime = 0.4;
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification"; #define CompanyName @"Apple Inc."
static const CGFloat ZOCImageThumbnailHeight = 50.0f; #define magicNumber 42
1.7. 通知和异常
1.7.1. 通知
通知用一个全局的字符串对象定义,建议使用如下命名规则:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
例如:
NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification
1.7.2. 异常
异常用一个全局的字符串对象定义,建议使用如下命名规则:
[Prefix] + [UniquePartOfName] + Exception
例如:
NSColorListIOException
NSColorListNotEditableException
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException
2. 代码格式
2.1. 空格的使用
如无特别说明,以下所说空格仅指一个空格。
缩进使用 4 个空格。 永远不要使用 tab, 确保你在 Xcode 的设置里面是这样设置的。
关键字(比如 if、for、while、switch 等)后面使用空格。
推荐不推荐
if (view.hidden) if(view.hidden)
二元和三元运算符两侧各使用空格。如果有连续多个二元运算符,使用小括号和空格搭配。
推荐不推荐
int c = a + b; int c=a+b;
a += b; a+=b;
float w = (width / 2) + (offset * 3/4); float w = width/2+offset*3/4;
int max = (a > b) ? a : b; int max = (a>b)?a:b;
等号、双等号、大于小于号、位操作符两侧使用空格。
推荐不推荐
int max = (a > b) ? a : b; int max=(a > b) ? a : b;
BOOL equal = (a == b); BOOL equal=(a==b);
BOOL isOn = ((flag && FLAG_ON) == FLAG_ON); BOOL isOn = ((flag&&FLAG_ON)==FLAG_ON);
flag = FLAG_ON; | flag|=FLAG_ON;
if (a <= b) if (a<=b)
分号如果不是在一行结束,后面使用空格。
推荐不推荐
for (int i = 0; i < count; i++) for (int i=0;i
逗号后面使用空格。
推荐不推荐
NSLog(@”name is %@, size = %d”, name, size); NSLog(@”name is %@,size = %d”,name,size);
方法签名中+/-符号后面使用空格。
推荐不推荐
+ (instancetype)sharedInstance; +(instancetype)sharedInstance;
- (void)setValue:(id)value; -(void)setValue: (id)value;
对象和方法之间使用空格,方法连续调用时,后面的方法名和]之间添加空格。
推荐不推荐
UIView *view = [[UIView alloc] init]; UIView *view = [[UIView alloc]init];
[[[AYBManager manager] value] isEqual:v]; [[[AYBManager manager]value] isEqual:v];
一元运算符不使用空格。
推荐不推荐
int b = a++; int b = a ++;
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++ i)
括号两边不使用空格。
推荐不推荐
for (int i = 0; i < count; ++i) for ( int i = 0; i < count; ++i )
CFRelease(stringRef); CFRelease( stringRef );
NSString *s = [NSString string]; NSString *s = [ NSString string ];
NSDictionay *switch = @{@”on” : @YES}; NSDictionay *switch = @{ @”on” : @YES };
- (void)setValue:(id)value; - (void) setValue: (id)value;
方法参数的冒号两边不使用空格。
推荐不推荐
[para setValue:@”ayibang” forKey:@”name”]; [para setValue : @”ayibang” forKey : @”name”]
2.2. 空行的使用
如无特别说明,以下所说空行仅指一行空行。
一对大括号结束(如方法定义结束,条件或循环逻辑结束),添加空行。如果大括号结束时紧跟另一个条件分支,则不使用空行。
推荐不推荐
- (void)suspendRelatedViews
{
}
- (void)resumeRelatedViews
{
}
- (void)suspendRelatedViews
{
}
- (void)resumeRelatedViews
{
}
if (view.hidden)
{
...
} else {
...
}
if (view.hidden) {
...
}
else {
...
}
属性声明和方法声明之间,无关联的属性声明之间、方法声明之间,添加空行。
推荐不推荐
@property(nonatomic) BOOL touchEnabled;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
- (CGPoint)convertPoint:(CGPoint)p toView:(UIView *)view;
@property(nonatomic) BOOL touchEnabled;
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
- (CGPoint)convertPoint:(CGPoint)p toView:(UIView *)view;
方法内相对独立的代码块之间,添加空行。
_leftBtn = [[UIButton alloc] init];
[_leftBtn setTitle:@”back” forState:n];
_leftBtn.exclusiveTouch = YES
[self addSubview:_leftBtn];
_rightBtn = [[UIButton alloc] init];
[_rightBtn setTitle:@”OK” forState:n];
_rightBtn.exclusiveTouch = YES
[self addSubview:_rightBtn];
2.3. 大括号的使用
方法定义中最外层起止大括号各占一行。
推荐不推荐
- (instanctype)init
{
}
- (instanctype)init{
}
类声明或定义后面的大括号独占一行。
推荐不推荐
@interface BaseViewController : UIViewController
{
NSString *_timeStamp;
}
@interface BaseViewController : UIViewController{
NSString *_timeStamp;
}
其他情况下(如条件判断、循环、block 等),起始大括号跟在前面逻辑后面,以空格区分, 不另起一行,终止大括号独占一行。
推荐不推荐
if (view.hidden) {
}
else {
}
if (view.hidden)
{
}
else
{
}
3. 基本语句
3.1. 条件语句
nil和BOOL的检查,不要在条件语句里面把它和其他值比较,可以用感叹号来作为运算符。
推荐不推荐
if (someObject) { ... if (someObject == YES) { ...
if (![someObject boolValue]) { ... if (myRawValue == YES) { ...
if (!someObject) { ... if ([someObject boolValue] == NO) { ...
不要嵌套过多的if语句。
推荐:
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}
不推荐:
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}
当三元运算符的第二个参数(if 分支)返回和条件语句中已经检查的对象一样的对象的时候,可以省略。
推荐不推荐
result = object ? : [self createObject]; result = object ? object : [self createObject];
有些方法通过参数返回 error 的引用,使用这样的方法时应当检查方法的返回值,而非 error 的引用。
NSError *error = nil;
if (![self trySomethingWithError:&error]) {
// Handle Error
}
3.2. switch语句
当在 switch 语句里面使用一个可枚举的变量的时候,不要使用default
推荐:
switch (menuType) {
case ZOCEnumNone:
// ...
break;
case ZOCEnumValue1:
case ZOCEnumValue2:
// ...
break;
}
不推荐:
switch (menuType) {
case ZOCEnumNone:
// ...
break;
default:
// ...
break;
}
3.3. 字面值
使用字面值来创建不可变的 NSString, NSDictionary, NSArray, 和 NSNumber 对象。注意不要将 nil 传进 NSArray 和 NSDictionary 里,因为这样会导致崩溃。
推荐:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
不推荐:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
3.4. pragma
当你确定没有问题时,可以临时忽略编译器的警告。不要全局禁用警告。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[myObj performSelector:mySelector withObject:name];
#pragma clang diagnostic pop
- (NSInteger)giveMeFive
{
NSString *foo;
#pragma unused (foo)
return 5;
}
明确编译器警告和错误。使用假数据等临时代码时,必须插入警告。
- (NSInteger)divide:(NSInteger)dividend by:(NSInteger)divisor
{
#error Whoa, buddy, you need to check for zero here!
return (dividend / divisor);
}
- (float)divide:(float)dividend by:(float)divisor
{
#warning Dude, don't compare floating point numbers like this!
if (divisor != 0.0) {
return (dividend / divisor);
}
else {
return NAN;
}
}
4. 类
4.1. 类名
参考 1.2 节
4.2. 代码组织
#pragma mark - 是一个在类内部组织代码并且帮助你分组方法实现的好办法。 我们建议使用 #pragma mark - 来分离:
不同功能组的方法
protocols 的实现
对父类方法的重写
- (void)dealloc { /* ... */ }
- (instancetype)init { /* ... */ }
#pragma mark - View Lifecycle (View 的生命周期)
- (void)viewDidLoad { /* ... */ }
- (void)viewWillAppear:(BOOL)animated { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }
#pragma mark - Custom Accessors (自定义访问器)
- (void)setCustomProperty:(id)value { /* ... */ }
- (id)customProperty { /* ... */ }
#pragma mark - IBActions
- (IBAction)submitData:(id)sender { /* ... */ }
#pragma mark - Public
- (void)publicMethod { /* ... */ }
#pragma mark - Private
- (void)zoc_privateMethod { /* ... */ }
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }
#pragma mark - ZOCSuperclass
// ... 重载来自 ZOCSuperclass 的方法
#pragma mark - NSObject
- (NSString *)description { /* ... */ }
4.3. initialize方法
当子类未实现initialize方法时,会调用父类的initialize方法,所以initialize可能会被调用多次。
为了确保initialize只被调用一次,在initialize方法中使用dispatch_once()
+ (void)initialize {
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
// the initializing code
}
}
如果要确保是当前类调用initialize方法:
+ (void)initialize {
if (self == [NSFoo class]) {
// the initializing code
}
}
不要手动调用initialize方法,可以调用一些无害的方法来出发initialize方法:
[NSImage self];
4.4 Designated 初始化方法
使用NS_DESIGNATED_INITIALIZER声明Designated初始化方法。
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
当你希望提供额外的初始化逻辑的时候,尽量重载父类的Designated初始化方法。
推荐:
@implementation ZOCViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call to the superclass designated initializer
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization (自定义的初始化过程)
}
return self;
}
@end
不推荐:
@implementation ZOCViewController
- (id)init
{
// call to the superclass designated initializer
self = [super init];
if (self) {
// Custom initialization (自定义的初始化过程)
}
return self;
}
@end
自定义Designated初始化方法时:
定义你的 designated initializer,确保调用了直接超类的 designated initializer。
重载直接超类的 designated initializer。调用你的新的 designated initializer。
为新的 designated initializer 写文档。
例如:
@implementation ZOCNewsViewController
- (id)initWithNews:(ZOCNews *)news
{
// call to the immediate superclass's designated initializer (调用直接超类的 designated initializer)
self = [super initWithNibName:nil bundle:nil];
if (self) {
_news = news;
}
return self;
}
// Override the immediate superclass's designated initializer (重载直接父类的 designated initializer)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
// call the new designated initializer
return [self initWithNews:nil];
}
@end
4.4.2. instancetype
对所有返回类的实例的类方法和实例方法使用 instancetype 类型。
推荐不推荐
+ (instancetype)personWithName:(NSString *)name; + (id)personWithName:(NSString *)name;
- (instancetype)init; - (id)init;
4.5. 单例
如果可能,请尽量避免使用单例。然而,如果一定要用,请使用一个线程安全的模式来创建共享的实例。
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
4.6. 属性
4.6.1. 属性定义
除非特别需要,不显式的通过@synthesize 为属性指定成员变量。
推荐不推荐
@property (nonatomic, copy) NSString *name; @synthesize name = _name;
属性的参数应该按照下面的顺序排列: 原子性,读写 和 内存管理。必须使用 nonatomic,除非特别需要的情况
@property (nonatomic, readwrite, copy) NSString *name;
为了完成一个共有的 getter 和一个私有的 setter,你应该声明公开的属性为 readonly 并且在类扩展总重新定义通用的属性为 readwrite 的。
//.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
不对外公开的属性,声明在.m 文件的 Extension 中。
推荐不推荐
@interface Person()
@property (nonatomic, copy) NSString *name;
@end
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
4.6.2. 使用属性
操作属性时,在 init/dealloc/getter/setter 方法中,使用成员变量,其他情况下都通过属性 的 getter/setter 方法。
你应该总是使用 setter 和 getter 方法访问属性,除了 init 和 dealloc 方法。
你总应该用 getter 和 setter ,因为:
使用 setter 会遵守定义的内存管理语义(strong, weak, copy etc...) ,这个在 ARC 之前就是相关的内容。举个例子,copy 属性定义了每个时候你用 setter 并且传送数据的时候,它会复制数据而不用额外的操作。
KVO 通知(willChangeValueForKey, didChangeValueForKey) 会被自动执行。
更容易debug:你可以设置一个断点在属性声明上并且断点会在每次 getter / setter 方法调用的时候执行,或者你可以在自己的自定义 setter/getter 设置断点。
允许在一个单独的地方为设置值添加额外的逻辑。
你应该倾向于用 getter:
它是对未来的变化有扩展能力的(比如,属性是自动生成的)。
它允许子类化。
更简单的debug(比如,允许拿出一个断点在 getter 方法里面,并且看谁访问了特别的 getter
它让意图更加清晰和明确:通过访问 ivar _anIvar 你可以明确的访问 self->_anIvar.这可能导致问题。在 block 里面访问 ivar (你捕捉并且 retain 了 self,即使你没有明确的看到 self 关键词)。
它自动产生KVO 通知。
在消息发送的时候增加的开销是微不足道的。
永远不要在 init 方法(以及其他初始化方法)里面用 getter 和 setter 方法,你应当直接访问实例变量。这样做是为了防止有子类时,出现这样的情况:它的子类最终重载了其 setter 或者 getter 方法,因此导致该子类去调用其他的方法、访问那些处于不稳定状态,或者称为没有初始化完成的属性或者 ivar 。记住一个对象仅仅在 init 返回的时候,才会被认为是达到了初始化完成的状态。
同样在 dealloc 方法中(在 dealloc 方法中,一个对象可以在一个 不确定的状态中)这是同样需要被注意的。
当使用 setter getter 方法的时候尽量使用点符号。应该总是用点符号来访问以及设置属性。
推荐不推荐
view.backgroundColor = [UIColor orangeColor]; [view setBackgroundColor:[UIColor orangeColor]];
[UIApplication sharedApplication].delegate; UIApplication.sharedApplication.delegate;
4.6.3. 可变对象
任何可以用一个可变的对象设置的((比如 NSString,NSArray,NSURLRequest))属性的内存管理类型必须是 copy 的。
你应该同时避免暴露在公开的接口中可变的对象,因为这允许你的类的使用者改变类自己的内部表示并且破坏类的封装。你可以提供可以只读的属性来返回你对象的不可变的副本。
/* .h */
@property (nonatomic, readonly) NSArray *elements
/* .m */
- (NSArray *)elements {
return [self.mutableElements copy];
}
4.6.4. 懒加载
除非创建对象消耗非常大的资源,否则不要使用懒加载。
下面是使用延迟实例化的争议:
getter 方法应该避免副作用。看到 getter 方法的时候,你不会想到会因此创建一个对象或导致副作用,实际上如果调用 getter 方法而不使用其返回值编译器会报警告 “Getter 不应该仅因它产生的副作用而被调用”。
你在第一次访问的时候改变了初始化的消耗,产生了副作用,这会让优化性能变得困难(以及测试)
这个初始化可能是不确定的:比如你期望属性第一次被一个方法访问,但是你改变了类的实现,访问器在你预期之前就得到了调用,这样可以导致问题,特别是初始化逻辑可能依赖于类的其他不同状态的时候。
这个行为不是 KVO 友好的。如果 getter 改变了引用,他应该通过一个 KVO 通知来通知改变。当访问 getter 的时候收到一个改变的通知很奇怪。
4.7. 方法
4.7.1. 参数断言
你的方法可能要求一些参数来满足特定的条件(比如不能为nil),在这种情况下最好使用 NSParameterAssert() 来断言条件是否成立或是抛出一个异常。
声明方法时,对每一个参数加上nullable或者nonnull,在头文件中可以使用NS_ASSUME_NONNULL_BEGIN、NS_ASSUME_NONNULL_END声明一段代码都要求nonnull。
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController : UIResponder
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
-
@end
NS_ASSUME_NONNULL_END
4.7.2. 私有方法
永远不要在你的私有方法前加上 _ 前缀。这个前缀是 Apple 保留的。
4.8. Categories
我们的 category 方法前加上自己的小写前缀以及下划线。
- (id)zoc_myCategoryMethod
推荐不推荐
@interface NSDate (ZOCTimeExtensions)
- (NSString *)zoc_timeAgoShort;
@end
@interface NSDate (ZOCTimeExtensions)
- (NSString *)timeAgoShort;
@end
5. 注释
普通的业务代码文件必须在文件注释中写明作者,工具类等通用代码还要简要的说明一下代码的作用或者基本用法。
工具类要简要介绍功能:
//
// BannerTool.h
// BBAVideo
// 一个广告展示的工具类......
// Created by jiangwenjun on 15/11/24.
// Copyright (c) 2015年 ayibang. All rights reserved.
//
作者要写清楚:
//
// HomeViewController.h
// Ayibang
//
// Created by jiangwenjun on 15/7/13.
// Copyright (c) 2015年 ayibang. All rights reserved.
//
工具类等通用代码要给头文件的接口按照AppleDoc的格式写注释,方法和属性都应该提供文档。
/**
* Designated initializer.
*
* @param store The store for CRUD operations.
* @param searchService The search service used to query the store.
*
* @return A ZOCCRUDOperationsStore object.
*/
- (instancetype)initWithOperationsStore:(id)store
searchService:(id)searchService;