我们前面的4篇文章,已经将Effective OC 的前两章节讲完了,接下来几篇文章,主要会讲我们在平时编码中的一些习惯,一些细节问题的处理,包括接口和API的设计,协议delegate和分类的使用等。
用前缀避免命名空间的冲突
因为OC和其他语言不同,没有命名空间这一说法,因此,我们在给类起名时,要设法避免潜在的命名冲突。发生命名冲突时,我们经常会看到这样的错误:
duplicate symbol _OBJC_METACLASS_$_XXXXX in:
build/something.o
build/something_else.o
duplicate symbol _OBJC_CLASS_$_XXXXX in:
build/something.o
build/something_else.o
上面的情况,意思为有两个地方都实现了名为XXXXX的类,这种情况,往往出现在,我们引用多个第三方库,第三方库之间命名冲突、或者是我们自己工程中的命名与第三方库冲突。
所以我们在编写类名时,一般都会加上跟自己工程相关的一些前缀,而且在xcode中是有一个功能,让我们在新建类时,自动给我们加上前缀:
这样在我们新建类时,默认就会有一个PPS的前缀:
注意:
如果你正在编写第三方库,供别人使用,那么请一定要为你的所有类名加上你自定义的前缀,这样,别人用你的SDK才不会出现上面的冲突情况,这一点非常重要。
提供“全能初始化方法”
首先,我们来解释一下什么是全能初始化方法:
举个例子吧
在NSDate类中,初始化方法有下面这几种:
- (instancetype)init;
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti;
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
在上面的几个初始化方法中,(instancetype)initWithTimeIntervalSinceReferenceDate:是全能初始化方法,意思就是
其他的初始化方法,都是最终都是调用它,生成了NSDate类。
为什么要引入全能初始化方法这个概念呢?在我们平时编码中,经常在生成一个类时,传入一些参数,才能使这个类进行正常的工作,如果我们设计时,这个类有很多的初始化方法,然而进行一段时间编码过后,我们发觉需要修改这个类的底层数据,就是生成这个类必须要使用到的一些数据,那么,这么多的初始化方法,我们都得修改,如果我们有一个全能初始化方法,那么我们只需要改动这个全能初始化方法,底层数据就已经改变。
实例
这里还是引入一个例子:
比如我们要编写一个表示矩形的类:
#import
@interface EOCRectangle : NSObject
@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;
@end
这里,为什么将长宽设置成readonly,我们在后面一篇文章中讲,这样一来,我们就需要提供一个初始方法来设置这两个参数:
- (instancetype)initWithWidth:(float)width
andHeight:(float)height;
-(instancetype)initWithWidth:(float)width andHeight:(float)height{
if (self = [super init]) {
_width = width;
_height = height;
}
return self;
}
这里我们会碰到一个问题,如果有人直接用
[[EOCRectangle alloc] init];
这个方法来初始化,我们 的长宽不是没办法设置了,那这个类肯定不能正常工作,我们不希望看到这种情况发生,通常,我们有两种方法处理:
第一,在init方法中,传入默认的值,就是讲类必须的参数的默认值传入,形成一个类
-(instancetype)init{
return [self initWithWidth:5.0f andHeight:10.0f];
}
第二种方法,是我们不希望开发者调用init方法,这样类就不能正常工作,我们可以在init方法中,抛出异常,但是一般我们不这样处理,在OC中,只有发生严重错误时,我们才抛出异常
-(instancetype)init{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"不允许调用这个初始化方法,请调用initWithWidth:andHeight:方法" userInfo:nil];
}
在继承中处理全能初始化方法
如果我们现在需要创建一个正方形类EOCSquare类,让他继承自EOCRectangle类,这很正常,正方形的长宽必须相等。那么我们需要提供一个传入边长的初始化方法。
- (instancetype)initWithDimension:(float)dimension;
在实现中:调用父类的方法,传入相同的长宽,即是一个正方形
- (instancetype)initWithDimension:(float)dimension{
return [super initWithWidth:dimension andHeight:dimension];
}
然而,即使我们提供了传入边长的初始化,方法,调用者还是可能会调用initWithWidth:andHeight:或者init方法来初始化,这是我们不愿意看到的,于是就有一个重要的结论:
如果子类的全能初始化方法和父类的全能初始化方法不同,那么总是应该覆写父类的全能初始化方法
-(instancetype)initWithWidth:(float)width andHeight:(float)height{
float dimension = MAX(width, height);
return [self initWithDimension:dimension];
}
这时,我们发现,不管调用者调用了initWithWidth:andHeight:还是init方法,都能够正常初始化EOCSquare了,因为如果开发者调用initWithWidth:andHeight:,那么因为我们EOCSquare覆写了,所以调用的是EOCSquare的方法,如果调用者调用了init方法,那么最终调用到的还是EOCSquare的全能初始化方法。
但是,我们一般不覆写父类的全能初始化方法,这样显得毫不合理,改变父类的全能初始化方法逻辑,所以我们一般这样处理,在子类中,覆盖父类的全能初始化方法,并且抛出异常
-(instancetype)initWithWidth:(float)width andHeight:(float)height{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"不允许调用这个初始化方法,请调用initWithDimension:方法" userInfo:nil];
}
这样一来,我们还需要覆写init方法
-(instancetype)init{
return [self initWithDimension:5.0f];
}
这样,我们就认为,开发者在初始化正方形时,只能传入相应的边长,如果传入长宽,那么认为是调用者自己犯了错误。
总结
- 在类中提供一个全能初始化方法,并指明其他初始化方法都需要调用全能初始化方法
- 若全能初始化方法不同,则子类应该覆写超类的全能初始化方法
- 如果超类的全能初始化方法不适用于子类,那么应该在子类中覆写超类的全能初始化方法,并且在其中抛出异常
实现description
在调试程序时,我们经常需要将对象的信息打印出来,最常用到的方式就是:
NSLog(@"object = %@",object);
在打印数组或者字典上,这样是很好用的:
NSArray *arr = @[@"111",@"222"];
NSLog(@"arr = %@",arr);
//打印出
arr = (
111,
222
)
但是如我们在自定义的类中,就不会像刚才那样输出了,输出的往往是这样:
EOCRectangle *rectangle = [[EOCRectangle alloc] initWithWidth:10 andHeight:5];
NSLog(@"rectangle = %@", rectangle);
//打印输出
rectangle =
输出的是一堆内存地址
这样一点也不好调试,要想输出对象的信息,我们需要在类中,重写description方法,在写的时候,我们可以借助NSDictionary的输出格式
-(NSString *)description{
return [NSString stringWithFormat:@"%@:%p,%@",
[self class],
self,
@{
@"width":@(_width),
@"height":@(_height),
}];
}
这样我们的输出就是这样的:
rectangle = EOCRectangle:0x17000b2b0, {
height = 5;
width = 10;
}
简单明了
还有一个方法:debugDescription
这个方法,是我们在调试的时候,打断点,在xcode的控制台输出的时候,需要打印的东西
-(NSString *)debugDescription{
return @"po 的时候打印我";
}
我们在类EOCRectangle重写了debugDescription方法,然后再控制台打印
这下明白了吧,好,今天先到这里。
欢迎您扫一扫上面的微信公众号,我将同步发送博客内容!