在开发应用程序的时候,总是不可避免的会用到他人的代码,或者自己的代码被他人所利用,所以要把代码写的更清晰一点,方便其他开发者能够迅速而方便地将其集成到他们的项目里。
Objective-C没有内置的命名空间机制(namespace),所以命名的时候需要设法避免潜在的命名冲突,否则就很容易重名了。如果发生命名冲突,那么应用程序的链接过程就会出错。比无法链接更糟糕的是在运行期载入了含有重名类的程序库,这时候就会有"重名符号错误",很可能导致整个程序崩溃。
为了避免这种问题,我们可以变相实现命名空间:为所有的名称加上这个程序相关的前缀。需要注意的是,Apple宣称保留使用所有“两字母前缀“的权利,所以自己选用的前缀应该是3个字母。
如果在第三方库的基础上进行了开发后再次封包,则需要将所有的文件前缀修改成和第三方库之前匹配的前缀,才可以封包给其他人使用。
example:EOCLibrary里扩展了XYZLibrary的东西,那么就要把XYZLibrary的相关前缀改成EOC的前缀。
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
在上面的代码里,initWithTimeIntervalSinceNow
就是全能初始化方法,其他所有的初始化方法都要调用它,所以只有该方法才会存储内部数据,当内部数据改变的时候,仅需要改变全能初始化方法即可。
调试程序的时候,经常需要打印并查看对象信息,一般有两种方法:
//EOCPerson.h
@interface EOCPerson : NSObject
@property(nonatomic, copy) NSString *firstName;
@property(nonatomic, copy) NSString *lastName;
@end
//EOCPerson.m
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p,\"%@ %@\">",
[self class], self, _firstName, _lastName];
}
//test
EOCPerson *person = [EOCPerson new];
person.firstName = @"Bob";
person.lastName = @"Smith";
NSLog(@"person = %@", person);
/**
person =
**/
如果要输出很多互不相同的信息,可以借助NSDictionary
:
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p,%@>",
[self class],
self,
@{@"firstName":_firstName,
@"lastName":_lastName}
];
}
po
指令输出,那么就应该实现debugDescription
方法,和Description
方法一样。readonly
属性,那么有人试着改变属性值,编译器就会报错。这样只能在对象内部进行修改。readonly
改为readwrite
。其实是等于在内部使用Extension的方式来实现。在对象外部,也可以通过KVC的方式来设置这些属性。//EOCPointInterest.h
@interface EOCPointInterest : NSObject
@property (nonatomic, copy, readonly) NSString *identifier;
@end
//EOCPointInterest.m
#import "EOCPointInterest.h"
@interface EOCPointInterest()
@property (nonatomic, copy, readwrite) NSString *identifier;
@end
@implementation EOCPointInterest
...
@end
//outter
[pointOfInterest setValue:@"GDGD" forKey:@"identifier"];
这样可以直接改动identifier
属性,即使没有于公共接口中公布这个方法,它依然可以违规的绕过本类的API。
id someResource = [someClass new];
if (/* check for error */) {
@throw [NSEXception exceptionWithName:@"Exception"
reason:@"ther is a error"
userInfo:nil];
}
[someResource doSomething];
[someResource release];
如果上述代码发生了异常之后,那么资源就不可能被释放掉了。如果要正确释放应该在抛出异常之前释放掉资源。
现在Objective-C采用的方法是尽量不抛出异常,如果抛出异常了无需考虑恢复问题,应用程序也应该退出。
如果不是那么严重的异常,一般可以令方法返回nil/0
,或者是使用NSError
,表明有错误发生。
NSError
的用法更加灵活,并且这个模型可以添加描述错误的原因。
NSError
的常见用法主要有两种:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (BOOL)doSomething:(NSError **)error;
//example
NSError *error = nil;
BOOL ret = [object doSomething:&error];
if (error) {
//handle the error
}
实际上使用ARC的时候,编译器会把方法签名中的NSError **
转换成NSError * __autoreleasing*
,也就是指针所指的对象在方法执行完毕后自动释放。
定义错误码的时候最好使用枚举的方式实现,并且最好在定义这些枚举的头文件里对每个错误类型详细说明。
NSCopying
协议,该协议只有一个方法:- (id)copyWithZone:(nullable NSZone *)zone;
NSZone
是以前开发程序时,会把内存分成不同的区,而对象会创建在不同的区,现在每个程序只有一个默认区,尽管必须实现这个方法,但是zone
参数不用担心。copy方法是由NSObject
实现,该方法只是以默认区为参数来调用。
example:
//EOCPerson.h
#import
@interface EOCPerson : NSObject
@property(nonatomic, copy) NSString *firstName;
@property(nonatomic, copy) NSString *lastName;
@end
//实现NSCopying的方法
- (id)copyWithZone:(NSZone *)zone {
EOCPerson *copy = [[self class] allocWithZone:zone];
copy.firstName = _firstName;
copy.lastName = _lastName;
return copy;
}
->
的语法来访问。@implementation EOCPerson {
NSMutableSet *_friends;
}
//如果要访问,那么就是
- (id)copyWithZone:(NSZone *)zone {
/* copy something*/
copy->_friends;
}
NSMutableCopying
的协议,如果要实现一个可变的拷贝,那么就需要实现该协议的方法:- (id)mutableCopyWithZone:(nullable NSZone *)zone;
对于不可变的NSArray与可变的NSMutableArray来说,下列关系成立:
[NSArray copy] => NSArray;
[NSArray mutableCopy] => NSMutableArray;
[NSMutableArray copy] => NSArray;
[NSMutableArray mutableCopy] => NSMutableArray;
在编写拷贝方法的时候,还需要注意一个问题那就是拷贝执行的是深拷贝还是浅拷贝。
Foundation
框架的所有collection
类在默认情况下都执行浅拷贝,因为collection
里可能存在无法拷贝的数据,或者并非需要深拷贝。- (id)deepCopy {
EOCPerson *copy = [[[self class] alloc] init];
copy.firstName = _firstName;
copy.lastName = _lastName;
return copy;
}