问题是这么来的…
我写了一个类方法返回了一个NSArray
+(NSArray *)validOperator
{
return @[@"+", @"-", @"*", @"/", @"%", @"(", @")", @"="];
}
然后我想判断某个操作符是否在上面返回的数组中
-(BOOL)isOperator:(NSString *)ch
{
if ([[Calculate validOperator] containsObject:ch]) {
return YES;
}else
return NO;
}
这时候问题就来了,即使ch在数组中,方法返回的也是NO。
然后我在网上搜索了一下…
然后我在网上搜了一下,大概找到问题的原因了。
苹果的文档对[NSArray containsObject:] 是这么说的:
This method determines whether anObject is present in the receiver by sending an isEqual: message to each of the receiver’s objects (and passing anObject as the parameter to each isEqual: message).
所以问题应该是比较的是对象的引用(地址?),而不是对象的值。
[见补充3]
找到原因了下面就要解决问题了
看到StackOverflow上这个人遇到的问题跟我差不多,他采纳的答案中提到了两种方法,还给了一个关于实现-isEqual:和-hash重载的问题链接。
you will either need to send [collection containsObject:] an instance of a variable it contains (e.g. A, B, or C), or you will need to override the [NSObject isEqual:] method in your Fruit class.
最后问题来了
- 请问上面那个回答中提到的第一种方法可行吗?我发送的ch原本定义的是char,然后使用了stringWithUTF8String:转化成NSString。
- 关于实现-isEqual:和-hash的重载,我看了上面提到的问题链接,有-isEqual:的重载模版,但是-hash的重载还是没说清楚的样子,请问能不能给个有关的例子可以学习一下。
刚接触iOS开发,希望各位路过的好心人能解答一下,万分感谢!
补充1
对了,我也考虑过不使用containObject:的,就是用NSString的isEqualToString方法,把数组里的元素拿出来。
-(BOOL)isOperator:(NSString *)ch
{
NSArray *optrArray = [Calculate validOperator];
for (NSString *optr in optrArray) {
NSLog(@"optr:%@ ,ch:%@", optr,ch);
if ([optr isEqualToString:ch]) {
return YES;
}
}
return NO;
}
还是不行(╥﹏╥)
补充2
好吧,我发现我的问题在哪里了,不是containObject:的问题,我要比较的还是NSString的两个对象,所以问题是传递的变量ch上。我发现,如果直接传一个@"+",不管使用containObject:还是用isEqualToString的方法都没问题,但是按下面的代码就有问题了:
//inputString是从textfield获取的一个NSString
for (int i = 0; i < [inputString length]; i++) {
char ch = [inputString characterAtIndex:i];
NSString *str = [NSString stringWithUTF8String:&ch];
if (![self isOperator:str]) {
……
}
……
}
痛定思痛,追根溯源,找到了症结:
char ch = [inputString characterAtIndex:i];
NSString *str = [NSString stringWithUTF8String:&ch];
对,问题就在stringWithUTF8String:这个方法上,暂时先把这个方法放放,把它改成stringWithFormat:的方法如下:
NSString *str = [NSString stringWithFormat:@"%c", ch];
然后,世界终于清静了……containObject:和isEqualToString应该可以随便用,都是NSString没什么好害羞的。今天太晚了,明天等程序写完了再研究下用stringWithUTF8String:是怎么样出错的。至于stringWithUTF8String:,因为它会在str末尾会包含"\n"= =
补充3
2015.1.13
上面那里之前没说清楚(大概自己之前也没好好理解==),楼下有小伙伴指出来了。今天又仔细看了下觉得应该是这样的,首先containsObject:方法的完整定义是:
- (BOOL)containsObject:(id)anObject
苹果给的文档里说的是,当判断anObject是否在当前的NSArray中的时候,是通过调用isEqual:这个方法来判断的,即对于NSArray中的每个对象都会调用一次isEqual:anObject,如果返回YES自然就是说这个数组包含anObject。
那这个时候问题可能就来了,比如说这个问题。究其原因的话还在于isEqual:这个方法。
If two objects are equal, they must have the same hash value.
所以isEqual:本质是在比较两个对象的hash。苹果也说了:
This last point is particularly important if you define isEqual: in a subclass and intend to put instances of that subclass into a collection. Make sure you also define hash in your subclass.
之前那个问题就属于这种需要额外注意的情况。
其实这里可以引出一个关于怎么判断相等的问题,鉴于如楼下的小伙伴说的这问题确实是写得太长了不如写博客了==这里就不细细说啦,找到一篇讲得特别棒的文章强烈推荐给大伙儿。
总之,就是在比较两个对象是否相等或者是判断一个对象是否在一个collection里面的时候,就要特别注意:
- 对于普通的NSObject比较用isEqual:方法比较hash,上面那个文章的Thompson大叔说比较的是内存地址(我觉得挺有道理的不然普通的两个对象还在哪里有可比性==)
- 对于Foundation里面的NSObject的子类如NSString、NSData、NSDate等(详细地还是看文章哈,反正没有NSArray╮(╯▽╰)╭),有定义相应的判断方法,所以用的话尽量采用子类中的比较方法
- 对于自己定义的类的话,如果要用到比较,就要重新实现isEqual:和hash方法,实现的方法还有栗子什么Thompson大叔也说了,嗯不然会很容易悲剧的╮(╯_╰)╭
补(tu)充(cao)4
2015.1.13
关于楼下的朋友说的自问自答真的不是故意的==
不过确实是自己把楼歪了,楼会歪的原因是,当初的bug跟提出的问题其实没什么关系…
不过今天又来仔细研究了当初被莫名提出的问题,觉得受益匪浅,嘿嘿:D
关于那两个问题
问题一
原题主的代码是这样的:
方法一:
感觉这应该是可以的,毕竟如果就是A, B, C本身的话在内存空间的地址应该是一样的。但是这么做的话觉得这代码也没什么意思了,我用A, B, C初始化了一个collection,然后我又来判断A, B, C是不是在这个collection里面==
方法二:
这应该是正解,详见补充3。
问题二
补充3里面NSHipster的文章里面有详细讲到,下面都是节选翻译自动脑补的。
继承自NSObject的子类要Override这个isEqual:方法要做以下几件事:
如果是自己定义的子类:
最后,重载hash的时候还有一个trick,即用到了XOR,大叔是这么说的,
嗯,就是对性能优化什么的有好处啦,关于这个他推荐了这篇文章。
关于po主的真正Bug
使用stringWithUTF8String:造成比较错误,因为它会在str末尾会包含"\n"。
改成stringWithFormat:的方法即可。
详见补充2.
补充
话说真不是故意自问自答的(答题时间可以证明po主的清白!╮(╯▽╰)╭)