我们在写程序时,都希望其中一部分代码用于后续项目,又或者把某些代码发布出来,供他人使用。因此,我们构建项目的要注意:
15、用前缀避免命名空间冲突
- 因为Objective-C没有其他语言那种内置的命名空间机制(namespace)。
所以我们在起名时要设法避免潜在的命名冲突,否则就容易重名,发生命名冲突,程序链接过程就会出错。比如下面:
- 避免问题:
变相实现命名空间:为所有名称都加上适当前缀(一般是与公司或者应用程序有关联的名字)。命名时要注意:因为Apple宣传其保留使用所有“两字母前缀”的权利,那我们自己选的前缀应该是三个字母的。
16、提供“全能初始化方法”
- 所有对象都要初始化。但是创建类实例的方式有的时候不止一种。
例如:UITableViewCell,初始化时,需要指明其样式以及标识符,标识符区分不同类型的单元格。
这种对象的创建成本比较高,绘制表格的时候可依照标识符复用,提升程序效率。我们把这种可为对象提供必要信息以便能完成工作的初始化方法叫做“全能初始化方法” - 试一试
首先创建一个类“ZSCManager”然后给其添加2个只读的属性,这样外界就无法更改了,再提供初始化方法设置这两个属性:
#import <Foundation/Foundation.h>
@interface ZSCManager : NSObject
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *phone;
- (id)initWithManageName:(NSString *)name
phone:(NSString *)phone;
@end
//.m的实现
#import "ZSCManager.h"
@implementation ZSCManager
- (id)init {
return [self initWithManageName:@"未知名字" phone:@"110"];
}
- (id)initWithManageName:(NSString *)name
phone:(NSString *)phone {
if (self = [super init]) {
_name = name;
_phone = phone;
}
return self;
}
@end
注意:
设置默认值的那个init方法调用了“全能初始化方法”。
若是存储方式变了(比如名字和手机号放在某个结构中),则init与全能初始化方法设置数据的代码就要修改。
简单的情况没有太大问题,如果类的初始化方法有很多种,而且初始化的数据较为复杂,那么这样做就麻烦的多。很容易忘记修改某个初始化方法,造成初始化方法之间相互不一致。假如现在创建个名叫 ZSCLeader 的类,让其成为 ZSCManager 的子类。那么新类的初始化方法要怎么写呢?
#import "ZSCManager.h"
@interface ZSCLeader : ZSCManager
- (id)initWithLeaderName:(NSString *)name;
@end
//.m的实现
#import "ZSCLeader.h"
@implementation ZSCLeader
- (id)initWithManageName:(NSString *)name
phone:(NSString *)phone {
return [self initWithLeaderName:name];
}
- (id)initWithLeaderName:(NSString *)name {
return [super initWithManageName:name phone:nil];
}
@end
ZSCLeader类的全能初始化方法调用了超类的全能初始化方法,
再看 ZSCManager 类的实现,也会发现 ZSCManager 也调用了超类的全能初始化方法,因此说明:全能初始化的方法调用链一点要维系。
调用者创建 ZSCLeader 会通过 initWithLeaderName 或者 init的方法。所以要覆写超类的全能初始化方法,否则init方法创建就会有问题。有时候我们不想覆写超类的全能初始化方法,可以理解为:我们认为这是调用者自己犯错了。这样子,我们就需要覆写超类的全能初始化方法并抛出异常:
- (id)initWithManageName:(NSString *)name
phone:(NSString *)phone {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"不让你调用此方法,方法名字:initWithManageName:(NSString *)name phone:(NSString *)phone 或者 init" userInfo:nil];
}
然后调用init 或者超类的方法创建时,程序就会崩溃报错
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '不让你调用此方法,方法名字:initWithManageName:(NSString *)name phone:(NSString *)phone 或者 init'我们不想程序崩溃也想知道崩溃的原因,可以这样办:
@try {
// 可能会出现崩溃的代码
ZSCLeader *leader = [[ZSCLeader alloc] init];
}
@catch (NSException *exception) {
// 捕获到的异常exception
NSLog(@"%@",exception);
//控制台的输出
//不让你调用此方法,方法名字:initWithManageName:(NSString *)name phone:(NSString *)phone 或者 init
}
@finally {
// 结果处理
}
- 总结:
- 如果在类中提供一个全能初始化方法,在文档中指明。其他初始化方法均应调用此方法。
- 若全能初始化方法与超类不同,则需覆写超类中的对应方法。
- 如果超类的初始化方法不适用子类,那么应该覆写这个超类方法,并在其中抛出异常。
17、实现 description 方法
调试程序时,经常需要打印并查看对象信息。一般都是这样做
ZSCManager *manager = [[ZSCManager alloc] init];
NSLog(@"%@",manager);
可以看到输出为:
<ZSCManager: 0x610000026b40>
这样的信息不太有用。只有类的名字和指针地址。-
我们在类中覆写 description 方法。
- (NSString *)description { return [NSString stringWithFormat:@"<%@:%p ,\"name : %@ phone : %@\">",[self class],self,_name,_phone];
}
这次的输出变得不一样了,对我们也更有用:
<ZSCManager:0x6080000354e0 ,"name : 未知名字 phone : 110">
- 注意:
- 实现 description 方法返回一个有意义的字符串,用以描述该实例。
- 若想在调试时打印出更详尽的对象描述信息,则应该实现 debugDescription 方法。
#18、尽量使用不可变对象
- 设计类的时候,要充分运用属性来封装数据。
属性默认情况是“既可读又可写的”(readWrite)
一般来说,我们有些建模的数据未必需要改变。这样我们最好把其属性设置为readonly。
@property (nonatomic, readonly, copy) NSString *name;
如果想“暴力”修改,那就通过KVC:
[manager setValue:@"哈哈" forKey:@"name"];
#19、使用清晰而协调的命名方式
- 方法和变量名使用“驼峰式大小写命名法”
小写字母开头,其后每个单词首字母大写。
类名也用驼峰命名法,不过其字母要大写,而且前面通常有两三个前缀字母。
- 方法名要言简意赅,从左至右读起来要像个日常用语中的句子才好。
- 方法名里不要使用缩略后的类型名称。
- 给方法起名时的第一要务就是确保其风格与你自己的代码或所要集成的框架相符。
#20、为私有方法名加前缀
- 一个类所要做的事情通常都要比外面看到的更多。
编写类的实现代码时,经常要写一些只在内部使用的方法。
建议应该为这种方法的名称加上某些前缀。
1、有助于调试。
2、容易把公共方法和私有方法区别开。
- 不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的,建议用p_XXXX这样。p代表 “private”(私有的)。
#21、理解Objective-C错误模型
- 很多编程语言都有“异常”(exception)机制,Objective-C 也不例外。
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"XXX" userInfo:nil];
OC语言现在采用的方法:只在极其罕见的情况下抛出异常,异常抛出之后,无须考虑恢复问题,应用会立刻退出。不需要编写“异常安全”代码。
- Objective-C在出现“非致命错误”时。这样处理的:
令方法返回 nil/0,或者是使用NSError。
- 假如调用者发现方法的返回值是nil,就可以确定其中发生了错误。就可以采取相对应的措施。
- NSError 用法灵活。我们可以把导致错误的原因汇报给调用者。
NSError domain(错误范围,类型为字符串)
产生错误的根源,比如从URL中解析或取得数据时出错了,就会使用NSURLErrorDomain来表示错误范围。
Error code(错误码,类型为整数)
独有的错误代码,错误情况通常采用 enum 来定义。比如 HTTP 请求出错时,可能会把HTTP状态码设为错误码。
User info (用户信息,类型为字典)
关于错误的额外信息。
- 参考 AFNetworking 中对NSError的使用,直接把错误信息放在 NSError 对象里,经由“输出参数”返回给调用者。
#22、理解 NSCopying 协议
- 使用对象时经常需要拷贝它。
在Objective-C中,如果想令自己的类支持拷贝操作,就要实现 NSCopying 协议。协议里只有一个方法:
- (id)copyWithZone:(nullable NSZone *)zone
NSZone是什么呢?
以前开发程序时,会把内存分为不同的“区”(zone),对象会创建在某个区里面。
现在不用了,每个程序只有一个“默认区”,不用担心其中的 zone 参数。
- (id)copyWithZone:(nullable NSZone *)zone {
ZSCManager *copy = [[[self class] allocWithZone:zone] initWithManageName:_name phone:_phone];
return copy;
}
直接把待拷贝的对象交给“全能初始化方法”,令其执行所有初始化工作。
- 若是自定义的对象分为可变版本和不可变版本,那么就要同时实现 NSCopying 和 NSMutableCopying 协议。
- 复制对象时需要决定采用浅拷贝还是深拷贝,一般情况下执行浅拷贝。
深拷贝:在拷贝自身时,将其底层数据也一并复制过来。
浅拷贝:只拷贝容器对象本身,而不复制其中数据。
注意:容器内的对象未必都能拷贝,调用者也不一定想在拷贝容器的时候一并拷贝其中的每个对象。
因为:没有专门定义深拷贝的协议。
所以:具体执行方式由每个类来确定,只需要你自己决定自己写的类是否要提供深拷贝的方法。如果你所写的对象需要深拷贝,那么可以考虑新增一个专门执行深拷贝的方法。
### 接下来也将会继续整理。如果觉得有用请点个喜欢!
### 您的支持将是我继续写作的动力!谢谢。
[观“编写高质量iOS与OC X代码的52个有效方法”有感(一)· 熟悉Objective-C](//www.greatytc.com/p/0876e60d3160)
[观“编写高质量iOS与OC X代码的52个有效方法”有感(二)· 对象、消息、运行时](//www.greatytc.com/p/1aac4fb98888)
[观“编写高质量iOS与OC X代码的52个有效方法”有感(三)· 接口与API设计](//www.greatytc.com/p/5d9e61db2a01)
[观“编写高质量iOS与OC X代码的52个有效方法”有感(四)· 协议与分类](//www.greatytc.com/p/0944e1e276ff)