如何在Objective C中检查一个字符串是否只包含字母数字字符?

47

我正在开发一个小型的iPhone项目,需要检查输入的用户名是否只包含字母数字字符?(A-Z, a-z, 0-9)。我该如何进行检查?


在 Swift 中:https://dev59.com/WlsV5IYBdhLWcg3wngGy - ma11hew28
8个回答

86

如果您不想为这个任务引入正则表达式库...

NSString *str = @"aA09";
NSCharacterSet *alphaSet = [NSCharacterSet alphanumericCharacterSet];
BOOL valid = [[str stringByTrimmingCharactersInSet:alphaSet] isEqualToString:@""]; 

4
顺便提一下,这只检查字符串两端的字符,而不是整个字符串。 - ragamufin
4
此方法将返回一个新的字符串,该字符串通过删除接收者两端包含在给定字符集中的字符来创建。 - ragamufin
1
OP的问题是确保字符串仅包含字母数字字符。我猜这是一种间接的方法,所以当然,它完成了任务,我会给你这个。不过,对我来说,更适合的是类似于stringByReplaceCharacterSet的东西。 - ragamufin
1
这是一种浪费CPU周期和RAM的方式,如果你担心这个问题的话。最好的方法是扫描字符串,直到找到一个非字母数字字符。 - sudo
4
不正确。NSCharacterSet.alphanumericCharacterSet包含的字符比[a-zA-Z0-9]多得多,例如变音符(如é)和上标符号(如²)。https://dev59.com/eHI-5IYBdhLWcg3wy7wd#1q6hEYcBWogLw_1bEMAh - ma11hew28
显示剩余3条评论

62

这将会起作用:

@implementation NSString (alphaOnly)

- (BOOL) isAlphaNumeric
   {
    NSCharacterSet *unwantedCharacters = 
       [[NSCharacterSet alphanumericCharacterSet] invertedSet];
    
    return ([self rangeOfCharacterFromSet:unwantedCharacters].location == NSNotFound) ? YES : NO;
    }

@end

11
逻辑比较的结果已经是一个布尔值,所以 ? YES : NO 并不是必要的。 - Chuck
1
我知道,我只是想看看,这样我以后不会读到它时觉得漏掉了什么。 - NSResponder
6
虽然这个解决方案没有被发帖者标记为答案,但我喜欢它比标记为答案的那个更好。它非常实用,而且可以很容易地进行扩展以测试其他字符集。点赞。 - scottbates22
1
艾伯特,你可以放心,CoreFoundation团队比那更聪明。 - NSResponder
4
不正确。NSCharacterSet.alphanumericCharacterSet包含比[a-zA-Z0-9]更多的字符,例如变音符(如é)和上标(如²)。https://stackoverflow.com/questions/1671605/how-to-check-if-a-string-only-contains-alphanumeric-characters-in-objective-c/1671671#comment59629685_1671671 - ma11hew28
显示剩余3条评论

9
NSCharacterSet基于的答案并不能给出您在处理日文等文本时所期望的结果,通常会声称它们包含字母数字字符-测试的核心是“只有字母或数字”,而日语(等)字符被视为“字母”。
如果您想检查拉丁字符与外语(例如日语)之间的区别,则来自“如何确定NSString是否基于拉丁字符?”的答案可能会有所帮助:
BOOL isLatin = [myString canBeConvertedToEncoding:NSISOLatin1StringEncoding];

NSASCIIStringEncoding 可以替代 NSISOLatin1StringEncoding 以进一步限制有效字符。您也可以使用 NSCharacterSet 进行测试,以排除特殊字符,如 !、# 等。


特殊字符如“#”、“@”等也可以转换为拉丁编码,因此该方法仅适用于拉丁与特定语言环境的检查。 - Sergiy Salyuk

9
你可以使用这个正则表达式库来进行ObjectiveC编程。使用以下正则表达式进行匹配:
^[a-zA-Z0-9]*$

1
为什么说这是过度设计?这是最简单的解决方案,易于阅读,而且字符串验证正是正则表达式的用途。 - Soviut

5

我进行了一些相当广泛的性能测试,并且在选择如何验证您的字母数字字符串时需要考虑几个因素。首先,当然,您可能甚至不关心性能。如果您的应用程序很少验证字符串,或者甚至只验证一次,任何能够给出您想要的行为的方法都可以接受。除此之外,这是我的性能测试结果。

对于自定义字符集(例如仅包含字母数字字符,而不包含Unicode字符或标记),初始运行最快的方法是:

NSCharacterSet *alphanumericSet = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"];
NSString *result = [self stringByTrimmingCharactersInSet:alphanumericSet];

return [result isEqualToString:@""];

如果您可以使用像[NSCharacterSet alphanumericCharacterSet]这样的预计算字符集,则此方法最快:

NSCharacterSet *alphanumericSet = [NSCharacterSet alphanumericCharacterSet];
alphanumericSet = alphanumericSet.invertedSet;
NSRange range = [self rangeOfCharacterFromSet:alphanumericSet];

return (range.location == NSNotFound);

如果您需要多次运行这些验证,则使用dispatch_once将字符集缓存到静态变量中会非常有帮助。在这种情况下,如果您确信可以吸收初始编译时间,那么使用正则表达式实际上是自定义字符集最快的方法:

static NSRegularExpression *alphanumericRegex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    alphanumericRegex = [NSRegularExpression regularExpressionWithPattern:@"^[a-zA-Z0-9]*$" options:NSRegularExpressionCaseInsensitive error:nil];
});
NSUInteger numberOfMatches = [alphanumericRegex numberOfMatchesInString:self options:0 range:NSMakeRange(0, self.length)];

return (numberOfMatches == 1);

如果你不想使用正则表达式,那么自定义集合版本的缓存rangeOfCharacterFromSet比缓存stringByTrimmingCharactersInCharacterSet:方法更快:

static NSCharacterSet *alphanumericSet;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    alphanumericSet = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"];
    alphanumericSet = alphanumericSet.invertedSet;
});

NSRange range = [self rangeOfCharacterFromSet:alphanumericSet];

return (range.location == NSNotFound);

对于预先计算好的集合,缓存的 rangeOfCharacterFromSet: 方法再次是最快的:

static NSCharacterSet *alphanumericSet;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    alphanumericSet = [NSCharacterSet alphanumericCharacterSet];
    alphanumericSet = alphanumericSet.invertedSet;
});

NSRange range = [self rangeOfCharacterFromSet:alphanumericSet];

return (range.location == NSNotFound);

提供给大家的信息是,不管是否使用缓存,isSupersetOfSet:方法都是最慢的。看起来isSupersetOfSet:很慢。

NSCharacterSet *stringSet = [NSCharacterSet characterSetWithCharactersInString:self];
NSCharacterSet *alphanumericSet = [NSCharacterSet alphanumericCharacterSet];

return [alphanumericSet isSupersetOfSet:stringSet];

我没有对底层的 CFCharacterSet 函数进行任何测试。


非常好的答案。使用 [NSCharacterSet alphanumericCharacterSet] 时有很多小细节需要注意。简单而不太优雅的 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" 正是我需要的,以避免在字母数字检测中出现中文/日文字符。 - JeremyDay

3
- (BOOL)isAlphaNumeric
{
     NSCharacterSet *s = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'. "];
     s = [s invertedSet];
     NSRange r = [self rangeOfCharacterFromSet:s];
     if (r.location == NSNotFound) {
         return NO;
     } else {
       return YES;
    }
}

具有添加/删除空格等新字符的灵活性

附:此方法可复制/粘贴在NSString类别中


4
我认为你可以将最后一段代码更改为 return r.location == NSNotFound; - agmezr
你的代码中应该是 r.location != NSNotFound,也就是说,如果在不需要的集合中没有找到,则为字母数字。我认为你把它倒过来了。 - Duck

2
我真的很喜欢RegexKit Lite框架。它使用ICU正则表达式库,该库已经包含在OSX中,并且支持Unicode。
NSString *str = @"testString";
[str isMatchedByRegex:@"^[a-zA-Z0-9]*$"]; // strict ASCII-match
[str isMatchedByRegex:@"^[\p{L}\p{N}]*$"]; // unicode letters and numbers match

1
你可以使用在 iOS 3.2 中引入的 NSString 正则表达式功能:
- (BOOL)isAlphanumeric:(NSString *)string {
    return [string rangeOfString:@"^[a-zA-Z0-9]+$" options:NSRegularExpressionSearch].location != NSNotFound;
}

这太过于原始了,因为:1)正则表达式并没有完全覆盖Unicode字符集,只涵盖了更简单的子集。2)除了在此处指定的字符之外,Unicode还包含许多"字母数字"字符。 - Motti Shneor
噗哈哈。是的,它有限制,但这是故意为之的,因为这正是OP要求的,即“A-Z,a-z,0-9”。顺便说一下,当你想要字母数字时,通常你明确地不想要所有其他Unicode字符。这完全取决于使用情况。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接