让自己的宏避免这个问题,尽管它只对多参数宏的最后一个参数有效。
属性合成(Property Synthesis)
看下面这个类:
@interface MyClass : NSObject {
NSString *_myIvar;
}
@property (copy) NSString *myIvar;
@end
@implementation MyClass
@synthesize myIvar;
@end
没什么问题,是吗?ivar的声明和@synthesize在现在有点多余,但是没有关系。
很不幸,这段代码会默默的忽略掉_myIvar并且合成一个新的不带前缀下划线的变量myIvar。如果你的代码中直接使用了ivar,它的值会和代码中直接使用属性的值不一样。很混乱!
@synthesize合成的变量名字的规则有点怪异。如果你通过 @synthesize myIvar = _myIvar;来指定变量名字,那么当然它用的是你所指定的任何名字。如果你没有指定变量名,那么它会合成一个与属性名相同名字的变量。如果你干脆连@synthesize也一起省略了,那么它会合成一个名字和属性名相同,但是带一个前缀下划线的变量。
除非你需要支持32位的Mac,你现在最好的选择就是避免显示地为属性声明对应的变量。让@synthesize创建该变量,并且如果你搞错了名字,你会得到一个好的编译警告,而不是难以理解的行为。
被中断的系统调用
Cocoa代码一般坚持使用高级结构,但有时需要降低一些来处理POSIX时也很实用。例如,这些代码会向一个文件描述符中写入一些数据:
int fd;
NSData *data = …;
const char *cursor = [data bytes];
NSUInteger remaining = [data length];
while(remaining > 0) {
ssize_t result = write(fd, cursor, remaining);
if(result < 0)
{
NSLog(@”Failed to write data: %s (%d)”, strerror(errno), errno);
return;
}
remaining -= result;
cursor += result;
}
但是,这可能会失败,它失败的方式会很奇怪,并且是间歇性的。像这样的POSIX调用是可以被信号打断的。即使是应用当中在其他地 方处理的无害的信号,例如SIGCHLD、SIGINFO,都会导致这种情况发生。如果你使用了NSTask或者进行多线程的工作,SIGCHLD就会产 生。当write被信号打断的时候,它会返回-1,并且将errno设置为EINTR来表示这个调用被中断。上述代码将所有错误都当作是致命的,并往外 跳,尽管它仅仅是需要被重新调用。正确的代码应该单独检查这种情况,并重试该调用:
while(remaining > 0) {
ssize_t result = write(fd, cursor, remaining);
if(result < 0 && errno == EINTR)
{
continue;
}
else if(result < 0)
{
NSLog(@”Failed to write data: %s (%d)”, strerror(errno), errno);
return;
}
remaining -= result;
cursor += result;
}
字符串长度
相同的字符串,用不同的方式表示,会有不同的长度。这个是相当常见的但是确实有错的样例:
write(fd, [string UTF8String], [string length]);
这个问题在于当write需要一个字节数的时候,NSString是以utf-16编码为单位计算长度的。仅当字符串中只包含 ASCII字符的时候,这两个数才会相等(这就是为什么人们如此经常写这种错误代码却能侥幸无事)。当字符串中一旦包含非ASCII字符,例如重音字符, 它们就不再相等。请一直使用相同的表示法来计算你正在操作的字符串长度:
const char *cStr = [string UTF8String];
write(fd, cStr, strlen(cStr));
强制转换成BOOL类型
看下这段用于检查一个对象指针是否是空的代码:
- (BOOL)hasObject
{
return (BOOL)_object;
}
一般来说,它能正常工作。但,大概6%的概率,它会在_object不为nil???情况下返回NO。出什么事了?
BOOL,很不幸,它不是布尔类型。这是它的定义:
typedef signed char BOOL;
这是另一个很不幸的从C没有布尔类型的时候遗留下来的问题。Cocoa早在C99的_Bool出现前,将它自己的“布尔“类型定义 为signed char,也就是一个8位的整数。当你将一个指针转转为整型时,你将得到指针本身的数值。当你将指针转换成小整型的时候,那么你将得到指针的低位部分的数 值。当指针看起来像这样:
….110011001110000转成BOOL就会得到:
01110000这个值非0,也就是说它是被正确计算的。那么问题是什么?问题在于如果指针看起来像这样:
….110011000000000那么转成BOOL就会得到:
00000000这个值是0,也就是NO,即使指针本身不是nil。啊哦!
这个发生的频率有多高?BOOL类型有256个可能的值,而NO只占其中一个。所以我们可以简单的假设它发生的概率是1/256。但Objective-C的对象在分配内存的时候是对齐的,一般来说是16位对齐。也就是说指针的最低4位一直都是0(有些地方会利用它们来对指针进行标记),故转换成BOOL后,只有4位的值是会变化的。那么所有位都为0的可能性就变成了1/16,也就是大概6%。
安全的实现这个方法,需要和nil进行一个显示的对比:
- (BOOL)hasObject
{
return _object != nil;
}
如果你想耍点小聪明,并使代码变得难以阅读,可以连续使用两次!操作符。!!结构有时被称为C语言的布尔转换操作符,虽然这只是它的一部分功能。
- (BOOL)hasObject
{
return !!_object;
}
倒数第一个!根据_object是否为nil产生一个1或者0的值。第二个!再将它转为正确的值,如果_object为nil,则产生1,否则产生0。
你应该坚持使用!= nil的版本。
丢失的方法参数
假设你正在实现一个表格视图的数据源。你将这个加入到你的类的方法中:
- (id)tableView:(NSTableView *) objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
return [dataArray objectAtIndex: rowIndex];
}
于是开始运行应用,然后NSTableView开始抱怨说你没有实现这个方法。但是它明明就在那儿!
像往常一样,计算机是正确的。计算机是你的朋友。
认真点看,第一个参数丢失了。为什么这样也能编译呢?
原因在于Objective-C允许空的选择符部分。上面声明的并不是一个丢失了一个参数的名叫 tableView:objectValueForTableColumn:row: 的方法。而是声明了名叫 tableView::row: 的方法,并且它的第一个参数名叫objectValueForTableColumn. 这是一