这是Objective-C系列的第3篇。编程
为全部类名都加上适当的前缀数组
提供"全能初始化方法"安全
description方法bash
尽可能建立不可变对象;session
readonly
属性扩展为readwrite
;命名框架
参考使用清晰而协调的命名方式dom
copy函数
赶上以下错误吗?post
duplicate symbol _OBJC_METACLASS_$DuplicateTheClass in
build /somethings.o
build /something_else.o
复制代码
以上错误是因为在应用程序中两份代码都各类实现了DuplicateTheClass
的类,致使该类符号和元类各定义了两次。ui
避免此问题的惟一办法就是变相实现命名空间:为全部名称都加上适当前缀。
前缀能够是公司、应用程序或者功能模块,甚至是我的标识等关联的词汇。但要注意的是,Apple宣称其保留全部使用“两字母前缀”的权利,因此你本身选用的前缀应该是三个字母的。
还有一个要注意的问题是,类的实现文件中所用的纯C函数以及全局变量,在编译好的目标文件中,这些都是“顶级符号(top-level symbol)”。
同时,也要注意在引入第三方库的时候,注意别使得本身的命名与其余库命名重合。
设计类的时候,应充分使用属性来封装数据。而在使用属性时,则可将其声明为readonly
。默认的属性都是readwrite
。
在设计类的时候,将某些不须要改变的属性设置为readonly
,以防止被改动。
@interface HOReadOnlyProperty : NSObject
@property (nonatomic ,readonly ,copy)NSString *name;
@end
复制代码
在这里,虽然指定了readonly
,就没有设置方法,但仍是指定了内存管理语义,是为了之后方便将其改动为可读写属性。
有时可能想修改封装在对象内部的数据,可是却不想令这些数据为外人所改动。一般是在对象内部将readonly
属性从新声明为readwrite
。以下:
@interface HOReadOnlyProperty()
@property (nonatomic ,readwrite ,copy)NSString *name;
@end
@implementation HOReadOnlyProperty
@end
复制代码
这样就能够实现只在内部改变属性值了。
!可是!并不能保证外部不会改变属性值,由于仍然能够经过KVC实现改变。经过setValue:forKey:
来修改。
另外,在定义表示各类collection的那些属性时,通常会提供一个readonly
属性供外界使用,该属性返回一个不可变的set,而此set是内部那个可变set的一个拷贝。好比下面:
@interface HOPerson : NSObject
@property (nonatomic ,copy)NSString *name;
@property (nonatomic ,strong ,readonly)NSSet *friends;
@end
复制代码
实现文件:
@interface HOPerson()
{
NSMutableSet *_internalFriends;
}
@end
@implementation HOPerson
- (instancetype)initWithName:(NSString *)name
{
if (self = [super init]) {
_name = name;
_internalFriends = [NSMutableSet set];
}
return self;
}
- (NSSet *)friends
{
return [_internalFriends copy];
}
- (void)addFriend:(HOPerson *)person
{
[_internalFriends addObject:person];
}
- (void)removeFriend:(HOPerson *)person
{
[_internalFriends removeObject:person];
}
@end
复制代码
readonly
属性扩展为readwrite
;stringWithString
。除非前面有修饰语,好比localizedString
。属性的存取方法不遵循这种命名方式,由于通常认为这些方法不会建立新对象,即使有时返回内部对象的一份拷贝,咱们也认为那至关于原有的对象。这些存取方法应该按照其所对应的属性来命名;-(void)getCharacters:(unichar*)buffer range:(NSRange)aRange
中的range
表述的参数类型;lowercaseString
;str
这种简称,应该使用string
这样的全称;is
前缀。若是某方法返回非属性的Boolean值,那么应该根据其功能,选用has
或is
为前缀;应该为类与协议的名称加上前缀,以免命名空间冲突。同时添加View
,ViewController
,Delegate
,Protocol
等这样标识类与协议的名词。
一个类所作的事情一般都要比从外面看到的更多。编写类的实现代码时,常常要编写一些只在内部使用的方法。应该为这中方法加上某些前缀,这有助于调试,由于据此很容易就能把公共方法和私有方法区分开。
另外,前缀的存在便于修改方法名或方法签名,即这些有前缀的方法能够随时修改,而不用担忧会影响面向外界的那些API。
具体使用何种前缀可根据我的喜爱而定,可是最好包括下划线与字母p。p即private,下划线是为了与真正的方法名区分开。因此私有方法名会写成:
- (void)p_privateMethod {
/.../
}
复制代码
给私有方法的名称加上前缀,这样能够很容易地将其同公共方法区分开;
不要单用一个下划线前缀,由于这种方法是预留给苹果公司用的。
Objective-C 也有异常机制,可是在默认状况下不是“异常安全的(exception safe)”。这意味着:若是抛出异常,那么本应在做用于末尾释放的对象选择却不会自动释放了。
若是想生成“异常安全”的代码,能够经过设置编译标志来实现,不过这将引入一些额外的代码,在不抛出异常时,也照样要执行这部分代码。须要打开的编译器标志是:-fobjc-arc-exceptions。
上面说的是ARC的情形,那么在MRC呢?
即便MRC时,也很难写出抛出异常不会致使内存泄漏的代码。好比,先建立好某资源,使用完以后再将其释放,可是在释放以前若是抛出异常,这部分资源就不会被释放了。这种问题的解决方案之一,就是在抛出异常前先释放资源。这样的确能解决问题,不过万一资源过多,执行路径复杂,那么在抛出什么异常时该释放哪些资源就会杂乱无章。
针对上面的问题,Objective-C的解决办法是:只在极其罕见的状况下抛出异常,异常抛出以后,无须考虑恢复问题,并且应用程序也应该退出。也就是说,无须编写复杂的“异常安全”的代码了。
异常只应该应用于极其严重的错误。
对于那些“不那么严重的错误(nonfatal error,非致命错误)”,Objective-C所用的编程范式:令方法返回nil/0,或是使用NSError,以代表有错误发生。
好比,在init时,返回nil,意指初始化失败。
NSError的用法灵活,经由此对象,能够把致使错误的缘由回报给调用者。
-(instancetype)errorWithDomain:(NSErrorDomain)domain code:(NSInteger)code userInfo:(nullable NSDictionary *)dict;
复制代码
Error domain(错误范围,其类型为字符串)
即错误产生的根源,一般用一个特有的全局变量来定义。系统提供了一些,参考NSError.h头文件。
Error code(错误码,其类型为整数)
User info(用户信息,其类型为字典)
那么如何将错误告知调用者,即经过什么途径传递出去?
第一种常见用法是经过委托协议来传递此错误。
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
复制代码
另一种常见用法是:经由方法的“输出参数”返回给调用者。
- (BOOL)doSomething:(NSError **)error
{
if(/*此处是错误逻辑*/){
if(error) {
//必须先判断error,假如nil时,解引用会致使“段错误(segmentation fault)”,致使程序崩溃
*error = [NSError errorWithDomain:domain code:code userinfo:userinfo];
}
return NO;
} else {
return YES;
}
}
//调用
NSError *error = nil;
BOOL ret = [object doSomething:&error];
if(error){
//处理错误
}
复制代码
下面示例了一个domain以及code的代码:
//HOErrors.h
extern NSString *const HOErrorDomain;
typedef NS_ENUM(NSUInteger, HOError) {
HOErrorUnknown = -1,
HOErrorInternalInconsistency = 100,
}
//HOErrors.m
NSString *const HOErrorDomain = @"HOErrorDomain"
复制代码
使用对象时,常常须要拷贝。在Objective-C中,此操做经过copy方法完成。若是要想令某个类支持copy,须要实现NSCopying协议,该协议只有一个方法:
- (id)copyWithZone:(NSZone*)zone;
复制代码
其中,zone在以前开发中会据此把内存分红不一样的“区(zone)”,而对象建立在某个区里面。如今不用了,每一个程序只有一个区:默认区(default zone)。因此,不用管zone这个参数。
有时候,须要实现可变版本的拷贝,遵循NSMutableCopying协议,实现:
- (id)mutableCopyWithZone:(NSZone*)zone;
复制代码
在编写拷贝方法是,还要以为是执行“深拷贝(deep copy)”仍是“浅拷贝(shallow copy)”。深拷贝时,在拷贝对象自身是,将其底层数据也一并复制过去。Foundation框架中全部的collection类在默认状况下都执行浅拷贝,即只拷贝对象自己,而不复制其中数据。
通常状况下,咱们在实现拷贝时,遵循系统所使用的那种样式,即以浅拷贝实现“copyWithZone”方法。可是若是有必要的话,增长一个执行深拷贝的方法。以NSSet为例,该类实现深拷贝的方法:
- (id)initWithSet:(NSArray*)array copyItems:(BOOL)copyItems;
复制代码
若copyItems为YES,则该方法会向数组中的每一个元素都发送copy消息,用拷贝好的元素建立新的set,并返回给调用者。