《Effective OC2.0》接口与API设计

第15条:用前缀避免命名空间冲突

要点:

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

第16条:提供"全能初始化方法"

把可为对象提供必要信息以便其能完成工作的初始化方法叫做"全能初始化方法"

例如编写一个矩形的类:

@interface Rectangle : 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;
    }
}

经常有人会使用[[Rectangle allc] init]创建,此时我们也应该重写init方法,设定默认值

@implementation Rectangle

- (instancetype)init {
    return [self initWithWidth:5.0f andHeight:10.0f];
}
- (id)initWithWidth:(float)width andHeight:(float)height {
    if (self = [super init]) {
        _width = width;
        _height = height;
    }
    return self;
}

@end

现在假设要创建个Square类,令其称为Rectangle的字类,那么新类的初始化方法应该怎么写呢?因为Square表示正方形,所以宽度高度应该相等,所以我们这样初始化:

@implementation Square
- (instancetype)initWithDimension:(float)dimension {
    return [super initWithWidth:dimension andHeight:dimension];
}
@end

上述方法是Square类的全能初始化方法,调用了父类的全能初始化方法
但是可能有傻子会调用父类的全能初始化方法

Square *square = [[Square alloc] initWithWidth:10.0f andHeight:20.0f];

并不是我们想要的,于是引入了类继承时需要注意的一个重要问题:如果字类的全能初始化方法与超类中的不同,那么总应重写父类的全能初始化方法。

@implementation Square
- (instancetype)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];
}
@end
 这样写的好处是我们即便是调用了Square的`init`来初始化,也能正常工作,因为Rectangle里面重写了`init`方法,并以默认值为参数,调用了该类的全能初始化方法,由于已经在字类里面重写了,实际上执行的是Square类里面的代码,而此代码又回调用本类的全能初始化方法,因此一切正常,调用者不可能创建出宽高不相等的正方形。我们也可以重写`init`方法,并在其中以合理的默认值来调用:
- (id)init {
    return [self initWithDimension:5.0f];
}

每个字类的全能初始化方法都应该调用其基类对应的方法,并逐层向上。

要点:

  1. 在类中提供一个全能初始化方法,并与文档里指明。其他初始化方法均应调用此方法。
  2. 若全能初始化方法与超类不同,则需重写超类中对应方法。
  3. 如果超类的初始化方法不适用于子类,那么应该重写这个超类方法,并在其中抛出异常。

第17条:实现description方法

比如一个代表个人信息的类

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;

- (id)initWithFirstName: (NSString *)firstName lastName: (NSString *)lastName;
@end

@implementation EOCPerson
- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
    if ((self = [super init])) {
        _firstName = [firstName copy];
        _lastName = [lastName copy];
    }
    return self;
}
//该类的description方法通常可以这么实现
- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">", [self class], self, _firstName, _lastName];
}
@end

在我们调用该类后

EOCPerson *person = [[EOCPerson alloc] initWithFirstName:@"Bob" lastName:@"Smith"];
NSLog(@"person = %@",person);

在这里插入图片描述
如果不实现description方法,打印出来的内容只有在你想判断两指针是否真的指向同一个对象时,这种信息才有用
在这里插入图片描述
在实现decription方法时,没有固定规则可循,应根据当前对象来决定在description方法里打印何种信息。

第18条:尽量使用不可变对象

默认情况下,属性是可读可写的(read-write),这样设计出来的类都是可变的
不过,一般情况下我们要建模的数据未必是需要改变的,比如对象源自网络服务时,这种对象没必要修改其内容,即使修改了,修改后的内容也不会返回服务器
比如一个处理地图上景点的类

@interface EOCPointOfInterest : NSObject
 
@property (nonatomic, copy, readonly) NSString *identifier;
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, assign, readonly) float latitude;
@property (nonatomic, assign, readonly) float longitude;
 
- (instancetype)initWithIdentifier:(NSString *)identifier
								title:(NSString *)title                   
                         	 latitude:(float)latitude
                          	longitude:(float)longitude;
 
@end

对象中的值都经由网络服务获取,用网络服务所提供的数据创建好某个对象之后就不用改变其值了,为了将该类做成不可变的类,需要把所有属性都声明为readonly,此时对象的属性值可以读出,但是无法写入
如果有人试着改变属性值,编译的时候就会报错

第19条:使用清晰而协调的命名方式

方法命名

把方法名起的稍微长一点,可以保证其能准确传达出方法所执行的任务,但是也不能长的太过分
比如设定一个长方形时

- (id)initWithSize:(float)width :(float)height;

在调用时

EOCRectangle *aRectangle = [[EOCRectangle alloc] initWithSize:5.0f :10.0f];

会使开发者不清楚每个变量的含义,到底哪个是width,哪个是height
换一种命名方式就会好很多

- (id)initWithWidth:(float)width andHeight:(float)height;

清晰的方法名应该能让其他人更易读懂。

注意事项:
1.应该把表示参数类型的名词放在参数前面 像上个方法里的Width跟Height
2.不要使用str这种简称,应该写string这种全称
3.如果方法要在当前对象上执行操作,那么就应该包含动词,若执行操作时还需要参数,则应该在动词后面加上一个或多个名字
4.Bool属性应加上is前缀

类与协议的命名

比如NSMutableArraymutable放在array的前面,即表示是一种可变的数组。
比如从UIView类中继承自定义的子类,那么类名末尾的词必须是View,命名方式应该协调一致。
同理,要创建自定义的委托协议,名称中就应该包含委托发起方的名称,后面再加上Delegate一词。

第20条:为私有方法名加前缀

比如自定义的视图控制器里面可能保存着许多状态信息,你想编写一个方法,当视图出现在屏幕上的时候,用此方法把控制器里的所有状态重置一遍,由于这个方法是私有方法,不要在接口定义处声明,为了和公有方法分别开,可以为方法名加上前缀,具体使用哪种前缀可以根据个人喜欢来定,其中最好包括下划线跟字母p,表示private,笔者喜欢wxshuai_这个前缀

- (void)wxshuai_resetViewController {
	// reset state and views
}

不能写成

 1. (void)_resetViewController {
	// reset state and views
}

因为只有下划线的前缀是预留给苹果公司用的,UIViewController类本身已经实现了一个名叫_resetViewController的方法,所以如果只使用下划线作为前缀,很可能在无意间重写了超类的同名方法。

第21条:理解Objective-C错误模型

OC现在只在极其罕见的情况下抛出异常,异常抛出之后无需考虑恢复问题,应用程序也在此时退出,并且异常只应该用于极其严重的错误。
对于一般错误,OC采用的编程范式为:令方法返回nil或者0,或者使NSError,以表示有错误发生。
NSError对象封装的信息:
1、Error domain(错误范围,其类型为字符串)
错误范围,也就是产生错误的根源,通常用一个特有的全局变量来定义。例:NSURLErrorDomain来表示错误范围。
2、Error code(错误码,其类型为整数)
表明在某一特定的范围内可能会发生一系列相关错误,这些错误通常采用enum来定义。
例:HTTP状态码
3、User info(用户信息,其类型为字典)
有关些错误的额外信息,其中或许包含一段“本地化的描述”(localized description)。或许还含有导致该错误发生的另外一个错误,经由此种信息,可将相关错误串成一条“错误链”(chain of errors)。
在这里插入图片描述
在网络请求出错之后,会调用NSError处理错误,但是不一定非要回报错误, 具体干什么交给开发者处理。

第22条:理解NSCopying协议

如果想让自己所写的对象具有拷贝功能,需要实现NSCopying协议
协议只有一个方法,一般情况下我们会以浅拷贝的方法实现,但如果有必要的话也可以增加一个深拷贝的方法

- (id)copyWithZone:(NSZone *)zone;

现在每个程序只有一个区叫做默认区,实现这个方法不必担心其中的zone参数
若想某个类支持拷贝功能,需要遵从NSCopying协议,并实现协议方法,比如。

@interface EOCPerson : NSObject<NSCopying>
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;

- (id)initWithFirstName: (NSString *)firstName lastName: (NSString *)lastName;
@end

实现协议方法

- (id)copyWithZone:(NSZone *)zone {
	EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
	return copy;
}

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。 假设B复制了A,修改A的时候,看B是否发生变化:
如果B跟着也变了,说明是浅拷贝(修改堆内存中的同一个值) 如果B没有改变,说明是深拷贝(修改堆内存中的不同的值)

对应的copy的协议还有一个NSMutableCopying协议。用于返回可变的对象。如果在可变对象上调用copy协议方法,返回的就是不可变对象。
NSCopyingNSMutableCopying协议可以同时实现

对于不可变的NSArray跟可变的NSMutableArray来说,下列关系一直耿立
在可变对象上调用copy方法会返回另一个不可变类的实例,这样做是为了在可变版本跟不可变版本之间自由切换。

-[NSMutableArray copy] => NSArray
-[NSArray mutableCopy] => NSMutableArray
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

waxuuuu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值