OC里面的小陷阱

OC 用的时间也不短了,很多时候总是出一些蛋疼的问题让人一头火,却不知道问题出在哪里,翻来覆去的找BUG,到最后却发现其实就是一条小阴沟,OC出来很久了,是一款强大的语言,但是再强大也会有弱点缺陷,今天就说几个比较常见的陷阱。

首先 一个最常见的例子:

 if (-0.5 <= x <= 0.5) return0;

上眼一看,肯定是觉得是用来判断x是不是在-0.5到0.5之间,但其实不是这么回事。

上面的判断跟这个 if ((-0.5 <= x) <= 0.5)  return0; 基本是一样的。

在C语言中,一个比较表达式的值是一个整型,要么是0,要么是1,这是C没有内建的bool类型遗留下来的问题。SO 当X和-0.5比较的时候,结果只有0或者1,却不是X的值,这样一来第二次比较就没有了意义。想想看,这个判断只有当X<-0.5的时候才会执行。

再来说第二个 Nil

OC有个特点,那就是对nil发送消息不会发生任何事情,却只是简单的返回0。别的语言立马基本上来说不是被禁止,就是报错,但是在OC里面是没有的,这个小坑在很多时候让我们“死去活来”。

又是一个常见的语句

[nil isEqual:@"x"];

在这里给nil发送消息总是返回0,等于NO,达到了我们的要求。BUT

[nil isEqual:nil];

这个也是NO! what? wait,wait! why? 原因很简单,就是给nil发送消息根本不管怎样都是返回的0,那么一直都是NO,所以用isEqual判断的话,nil永远不等于任何东西,包括它自己,但是大部分情况下,我们的比较都是可以满足我的要求的,但是有些时候还是让我们能疼上好一阵子。

那么[@"x" isEqual:nil]; 这样呢,可能是no,也可能是异常,还有干脆崩溃,给一个没有明确告知可以接受nil为参数的方法传递nil是不明智的,并且, isEqual并没有表明可以接受nil。

很多的Cocoa类都有一个compare:方法,返回的是NSOrderedSame、NSOrderedAscending、NSOrderedDescending这个三个兄弟。

那么我们来试试吧

[nil compare:nil]

返回了0,哇,cool,这真是极好的。那么

[nil compare:@"x"]

又返回了0,这是怎么回事,因为compare还是认为nil和任何东西都是相等的。

那么当我们有用到比较的时候,一定要检查一下是否会有nil的出现,出现了该怎么办。

第三个 宏

<span style="font-size:10px;">STAssertEqualObjects([obj method], @[ @"expected" ], @”Didn’t get the expected array”);</span>
但是当我们的数组中包含两个对象的话

STAssertEqualObjects([obj methodTwo], @[ @"expected1", @"expected2" ], @”Didn’t get the expected array”);

代码无法编译通过,并且出现了奇怪的错误,why?

问题在于STAssertEqualObjects 是个宏,宏是由预处理器展开的,而预处理器是个古老又傻X的程序,它不懂语法,它只是造按逗号分开参数,那么问题就出来了,这个宏里面不止三个参数,而是四个,因为数组中的两个对象被分开了,成了[obj methodTwo] @[@"expected1" @"expected2"] @"Didn't get the expe"。那么出错也是自然了。

这就会出现问题了,那么多参数该怎么办呢,让我们来看两个宏:

#define Block_copy(…) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
#define Block_release(…) _Block_release((const void *)(__VA_ARGS__))
这是Apple的Block_copy和 Block_release 两个宏,宏在理论是只接受单一的参数,但是如果声明称可变参数就可以避免这个问题,通过...作为参数,并使用__VA_ARGS__来指代参数。

第四个 属性合成

@interface MyClass : NSObject {
NSString *_myIvar;
}
@property (copy) NSString *myIvar;
@end
@implementation MyClass
@synthesize myIvar;
@end
看似没有问题,但是 很不幸,这段代码会默默的忽略掉_myIvar并且合成一个新的不带前缀下划线的变量myIvar。如果你的代码中直接使用了ivar,它的值会和代码中直接使用属性的值不一样。很混乱!

@synthesize合成的变量名字的规则有点怪异。如果你通过 @synthesize myIvar = _myIvar;来指定变量名字,那么当然它用的是你所指定的任何名字。如果你没有指定变量名,那么它会合成一个与属性名相同名字的变量。如果你干脆连@synthesize也一起省略了,那么它会合成一个名字和属性名相同,但是带一个前缀下划线的变量。除非你需要支持32位的Mac,你现在最好的选择就是避免显示地为属性声明对应的变量。让@synthesize创建该变量,并且如果你搞错了名字,你会得到一个好的编译警告,而不是难以理解的行为。

第五个 字符串长度

相同的字符串,用不同的方式表示,会有不同的长度。

write(fd, [string UTF8String], [string length]);
这个问题在于当write需要一个字节数的时候,NSString是以utf-16编码为单位计算长度的。仅当字符串中只包含 ASCII字符的时候,这两个数才会相等(这就是为什么人们如此经常写这种错误代码却能侥幸无事)。当字符串中一旦包含非ASCII字符,例如重音字符, 它们就不再相等。请一直使用相同的表示法来计算你正在操作的字符串长度:
const char *cStr = [string UTF8String];
write(fd, cStr, strlen(cStr));
第六个 强制转换成BOOL类型

看下这段用于检查一个对象指针是否是空的代码:

<span style="font-size:10px;">- (BOOL)hasObject
{
return (BOOL)_object;
}</span>
<span style="font-family: 'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif; background-color: rgb(250, 250, 250);"><span style="font-size:10px;">一般来说,它能正常工作。但,大概6%的概率,它会在_object不为nil的情况下返回NO。出什么事了?</span></span>

BOOL,很不幸,它不是布尔类型。这是它的定义:

<span style="font-size:10px;">typedef signed char BOOL;</span>
<span style="color: rgb(37, 37, 37); font-family: 'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif; line-height: 28px; background-color: rgb(250, 250, 250);"><span style="font-size:10px;">这是另一个很不幸的从C没有布尔类型的时候遗留下来的问题。Cocoa早在C99的_Bool出现前,将它自己的“布尔“类型定义 为signed char,也就是一个8位的整数。当你将一个指针转转为整型时,你将得到指针本身的数值。当你将指针转换成小整型的时候,那么你将得到指针的低位部分的数 值。当指针看起来像这样:</span></span>
<span style="color: rgb(37, 37, 37); font-family: 'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif; line-height: 28px; background-color: rgb(250, 250, 250);"><span style="font-size:10px;">这是另一个很不幸的从C没有布尔类型的时候遗留下来的问题。Cocoa早在C99的_Bool出现前,将它自己的“布尔“类型定义 为signed char,也就是一个8位的整数。当你将一个指针转转为整型时,你将得到指针本身的数值。当你将指针转换成小整型的时候,那么你将得到指针的低位部分的数 值。当指针看起来像这样:
</span></span>

转成BOOL就会得到:

1
01110000

这个值非0,也就是说它是被正确计算的。那么问题是什么?问题在于如果指针看起来像这样:

1
….110011000000000

那么转成BOOL就会得到:

1
00000000

这个值是0,也就是NO,即使指针本身不是nil。啊哦!

这个发生的频率有多高?BOOL类型有256个可能的值,而NO只占其中一个。所以我们可以简单的假设它发生的概率是1/256。但Objective-C的对象在分配内存的时候是对齐的,一般来说是16位对齐。也就是说指针的最低4位一直都是0(有些地方会利用它们来对指针进行标记),故转换成BOOL后,只有4位的值是会变化的。那么所有位都为0的可能性就变成了1/16,也就是大概6%。

安全的实现这个方法,需要和nil进行一个显示的对比:

<span style="font-size:10px;">- (BOOL)hasObject
{
return _object != nil;
}</span>
如果你想耍点小聪明,并使代码变得难以阅读,可以连续使用两次!操作符。!!结构有时被称为C语言的布尔转换操作符,虽然这只是它的一部分功能。
- (BOOL)hasObject
{
return !!_object;
}

倒数第一个!根据_object是否为nil产生一个1或者0的值。第二个!再将它转为正确的值,如果_object为nil,则产生1,否则产生0。

你应该坚持使用!= nil的版本。

最后一个 丢失方法参数

如果你在调用tableview某个方法,例如

- (id)tableView:(NSTableView *) objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
return [dataArray objectAtIndex: rowIndex];
}
当运行的时候,NSTableView开始抱怨说你没有实现这个方法,但是它真的在的啊,我们错了嘛?是的,朋友我们还是没有计算机仔细的。

仔细看看,发现第一个参数没有了,这在很多C#转OC的朋友遇到过很多次了。但是少了参数为啥能编译呢?

原因在于OC允许空的选择符部分。上面声明的并不是一个丢失了一个参数的名叫 tableView:objectValueForTableColumn:row: 的方法。而是声明了名叫 tableView::row: 的方法,并且它的第一个参数名叫objectValueForTableColumn. 这是一个相当不愉快的方法来键入一个方法的名字,并且如果你在一个编译器无法提示你方法丢失的情况下犯了这个错,你可能就要花上相当长的时间用于调试这个问题。

最后附上一副很出名的对联:

上联:为系统而生,为框架而死,为bug奋斗一辈子!

下联:吃符号的亏,上大小写的当,最后死在需求上!

横批:苦逼程序猿

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值