选择与你公司、应用程序或者皆有关联的名称作为类的前缀,并在代码中均使用这一前缀。
若自己所开发的 App 使用到第三方库,则应为其的名称加上前缀。

1. 提供初始化方法

如果创建类实例的方式不止一种,那么这个类就会有多个初始化方法。不过要在其中选中一个作为全能初始化方法,并做到其他初始化方法都调用它。

比如说,编写一个表示矩形的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import <Foundation/Foundation.h>
@interface EOCRectangle : NSObject
@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;
@end
// 我们可能会提供一个初始化方法
-(id)initWithWidth:(float)width andHeight:(float)height {
if (self = [super init]) {
_width = width;
_height = height;
}
return self
}
// 为了避免外界直接调用 init 方法初始化
// 可以重写 init 并指定默认值
- (id)init {
return [self initWithWidth: 5.0f andHeight: 10.0f];
}
// 或者直接抛出异常,当然通常不建议这样做
- (id)init {
@throw [NSException exceptionWithName: NSInternalInconsistencyException
reason: @"请使用便利初始化方法" userInfo: nil];
}

假设我们现在要创建一个 EOCSquare 类,令其成为 EOCRectangle 的子类,下面是子类的初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "EOCRectangle.h"
@interface EOCSquare : EOCRectangle
- (id)initWithDimension:(float)dimension;
@end
@implementation EOCSquare
- (id)initWithDimension:(float)dimension {
return [super initWithWidth: dimension andHeight: dimension];
}
// 如果子类的初始化方法与父类名称不同,总应该重写父类的全能初始化方法
- (id)initWithWidth:(float)width andHeight:(float)height {
float dimension = MAX(width, height);
return [self initWithDimension: dimension];
}
// 此例中也要重写 init 方法
- (id)init {
return [self initWithDimension: 5.0];
}
@end

这里有一个特别的情况,某个类可能存在两个全能初始化方法。比如我们让 EOCRectangle 遵守 NSCoding 协议,从而实现此类的序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@interface EOCRectangle : NSObject <NSCoding>
@property (nonatomic, assign, readonly) CGFloat width;
@property (nonatomic, assign, readonly) CGFloat height;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
@end
@implementation EOCRectangle
// 其余初始化方法与之前代码一样
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
/
if (self = [super init]) {
_width = [aDecoder decodeFloatForKey:@"width"];
_height = [aDecoder decodeFloatForKey:@"height"];
}
return self;
}
@interface EOCSquare : EOCRectangle <NSCoding>
@end
@implementation EOCSquare
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
// 如果 super 遵循了 NSCoding 协议,需要调用超类的 initWithCoder 方法。
if (self = [super initWithCoder:aDecoder]) {
// specific initializer
}
return self;
}
@end

因此可以看到,在自定义类时需要提供一个全能初始化方法,并尽量在文档中指出,其他初始化方法均应调用此方法。
如果全能初始化方法与超类不同,应该重写超类中对应的方法。
如果超类的初始化方法不适合与子类,那么可以选择重写超类的这个方法,并在其中抛出异常。

2. 实现 description 方法

调试程序时,我们往往希望在控制台打印出更丰富的信息。而在某个类构建需要打印到日志的字符串时,会收到 description 消息,我们通过重写此方法就可以自定义描述信息了。下面这个例子展示了一种不错的输出格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, %@>",
[self class],
self,
@{@"width":@(_width),
@"height": @(_height),
}];
}
// 输出
22:52:11.691 Ads[8785:2103515] <EOCRectangle: 0x600000036600, {
height = 10;
width = 5;
}>

3. 尽量使用不可变对象

这点和 swift 中的应用一样,只有在必要的时候才将对象定义为可变。而在 OC 中声明属性时默认是 readwrite 的。