NSString 的 isLike: 到底做了什么?

What does NSString's isLike: actually do?

我试图弄清楚 isLike:NSString 的实际作用,但遇到了麻烦。苹果的own documentation很含糊:

Returns a Boolean value that indicates whether the receiver is "like" another given object.

...

The default implementation for this method provided by NSObject method returns NO. NSString also provides an implementation of this method, which returns YES if the receiver matches a pattern described by object.

它提到了 "pattern",但是通过一些基本的测试,它似乎没有使用正则表达式。这种情况下的模式格式到底是什么?

header NSScriptWhoseTest.h 提供了更多信息:

@interface NSObject (NSComparisonMethods)
...

- (BOOL)isLike:(NSString *)object;
    // argument should be a string using simple shell wildcards (* and ?).
    // (e.g. "Stev*" or "N?XT").
    // Returns NO if receiver is not an NSString.

- (BOOL)isCaseInsensitiveLike:(NSString *)object;

@end

正如其他人发布的那样,Apple 的文档没有详细描述 [NSString isLike:] 的行为,如 here:

所示

The default implementation for this method provided by NSObject method returns NO. NSString also provides an implementation of this method, which returns YES if the receiver matches a pattern described by object.

正如其他人所说,它可能基于 NSPredicate。如果是这样,很可能使用 NSComparisonPredicate 和运算符类型 NSLikePredicateOperatorType,如 here:

所述

NSMatchesPredicateOperatorType

A full regular expression matching predicate.

Available in OS X v10.4 and later.

NSLikePredicateOperatorType

A simple subset of the MATCHES predicate, similar in behavior to SQL LIKE.

Available in OS X v10.4 and later.

尽管功能可能只是正则表达式的一个简单子集,但语法肯定不同。我今天在 OS X 10.10.5 上本地测试了以下内容:

- (NSString *)escapeString:(NSString *)value {
    return [value stringByReplacingOccurrencesOfString:@"\" withString:@"\\"];
}

- (void)is:(NSString *)value like:(NSString *)pattern note:(NSString *)note {
    NSLog(@"[@\"%@\" isLike:@\"%@\"] == %@ // %@",
            [self escapeString:value],
            [self escapeString:pattern],
            ([value isLike:pattern] ? @"true" : @"false"),
            note);
}

- (void)testAll {
    // each note contains result on OS X 10.10.5 on 20160503
    [self is:@"foo" like:@"f*" note:@"true, '*' wildcard works like file globbing, not RE"];
    [self is:@"foo" like:@"foo*" note:@"true, '*' is zero or more"];
    [self is:@"foo" like:@"f?o" note:@"true, '?' wildcard works like file globbing, not RE"];
    [self is:@"foo" like:@"f?" note:@"false, not more then one"];
    [self is:@"foo" like:@"f?oo" note:@"false, not less than one"];
    [self is:@"foo" like:@"Foo" note:@"false, is case-sensitive (also see isCaseInsensitiveLike:)"];
    [self is:@"foo" like:@"[Ff]oo" note:@"true, supports character classes"];
    [self is:@"foo" like:@"[^F]oo" note:@"false, does not support RE negation in character classes"];
    [self is:@"foo" like:@"[a-z]oo" note:@"true, supports ranges"];
    [self is:@"foo" like:@"[[:lower:]]oo" note:@"false, does not support POSIX named classes"];
    [self is:@"]oo" like:@"[]]oo" note:@"false, does not support ']' as first character in a class"];
    [self is:@"]oo" like:@"[\]]oo" note:@"true, backslash to escape interpretation of ']' as end of class"];
    [self is:@"[oo" like:@"\[oo" note:@"true, backslash to escape interpretation as start of class"];
    [self is:@"-oo" like:@"[x\-z]oo" note:@"true, supports escape of '-' in character classes"];
    [self is:@"?oo" like:@"\?oo" note:@"true, escape with backslash"];
    [self is:@"foo" like:@"\?oo" note:@"false, this is not just wildcard matching"];
    [self is:@"*oo" like:@"\*oo" note:@"true, escape with backslash"];
    [self is:@"foo" like:@"\*oo" note:@"false, this is not just wildcard matching"];
    [self is:@"\foo" like:@"\\*oo" note:@"true, escape backslash with another backslash"];
}

这段代码产生了这些结果:

[@"foo" isLike:@"f*"] == true // true, '*' wildcard works like file globbing, not RE
[@"foo" isLike:@"foo*"] == true // true, '*' is zero or more
[@"foo" isLike:@"f?o"] == true // true, '?' wildcard works like file globbing, not RE
[@"foo" isLike:@"f?"] == false // false, not more then one
[@"foo" isLike:@"f?oo"] == false // false, not less than one
[@"foo" isLike:@"Foo"] == false // false, is case-sensitive (also see isCaseInsensitiveLike:)
[@"foo" isLike:@"[Ff]oo"] == true // true, supports character classes
[@"foo" isLike:@"[^F]oo"] == false // false, does not support RE negation in character classes
[@"foo" isLike:@"[a-z]oo"] == true // true, supports ranges
[@"foo" isLike:@"[[:lower:]]oo"] == false // false, does not support POSIX named classes
[@"]oo" isLike:@"[]]oo"] == false // false, does not support ']' as first character in a class
[@"]oo" isLike:@"[\]]oo"] == true // true, backslash to escape interpretation of ']' as end of class
[@"[oo" isLike:@"\[oo"] == true // true, backslash to escape interpretation as start of class
[@"-oo" isLike:@"[x\-z]oo"] == true // true, supports escape of '-' in character classes
[@"?oo" isLike:@"\?oo"] == true // true, escape with backslash
[@"foo" isLike:@"\?oo"] == false // false, this is not just wildcard matching
[@"*oo" isLike:@"\*oo"] == true // true, escape with backslash
[@"foo" isLike:@"\*oo"] == false // false, this is not just wildcard matching
[@"\foo" isLike:@"\\*oo"] == true // true, escape backslash with another backslash

所以 isLike: 似乎支持 ?* 就像用反斜杠 \ 来逃避特殊解释的文件 globbing。它还支持字符 classes with [] with ranges defined with -.反斜杠转义开头 [,反斜杠转义 class.

中的 ]-