背景
前些阵子遇到一个需求,一个文本剪辑器,发布时需要统计除 Emoji 外的字数,于是求助度娘,以 ”NSString 是否包含 Emoji“ 为关键字找到不同的文章都给出了一模一样的解决方案,但实际测试中,这个方案随着 iOS 版本的更迭早已不再适用
解决方案
在这篇文章里面: iOS - 判断 NSString 字符串是否包含 Emoji
原理
想做 Emoji 判断首先需要要有两个前置的知识储备,Unicode 编码方式和原理,以及 NSString 和 Unicode 的关系。
Unicode
限于篇幅,关于 Unicode 的简介和发展历程可以参考我的这篇文章:Unicode 简介与发展历程,里面有着详细的 UTF 编码原理介绍。
此外,网络上面也有着许多和 Unicode 相关的大佬们的文章可以阅读。Unicode 官网里面更有着非常详细的介绍和细节。
NSString 与 UTF
在了解了 Unicode 和 UTF 编码原理之后,只需要再了解 NSString 使用了那种编码方式就很容易找出解决方案。而 NSString 的编码方式在 NSString.h 的头文件中有明确的说明:
/* The unichar type represents a single UTF-16 code unit in an NSString. Although many human-readable characters are representable with a single unichar, some such as Emoji may span multiple unichars. See discussion above.
*/
typedef unsigned short unichar;
在明确了 NSString 使用 UTF-16 的编码方式之后,结合 UTF-16 的编码原理,可以得到 NSString 和码点之间的互相的转换关系如下:
/*
Nsstring 转换为码点
*/
- (int)stringToCodePoint:(NSString *)string {
const unichar hs = [string characterAtIndex:0];
if (0xd800 <= hs && hs <= 0xdbff) {
if (string.length > 1) {
const unichar ls = [string characterAtIndex:1];
const int codepoint = (((int)hs - 0xd800) * 0x400) + ((int)ls - 0xdc00) + 0x10000;
return codepoint;
} else {
return 0;
}
} else {
return (int)hs;
}
}
/*
码点转换为 NSString
*/
- (NSString *)stringFromCodePoint:(int)codePoint {
if (codePoint <= 0xFFFF)
{
return [[NSString alloc] initWithBytes:&codePoint length:sizeof(codePoint) encoding:NSUTF8StringEncoding];
}
else if (0x10000 <= codePoint && codePoint <= 0x10FFFF)
{
int symbol = ((((0x808080F0 | (codePoint & 0x3F000) >> 4) | (codePoint & 0xFC0) << 10) | (codePoint & 0x1C0000) << 18) | (codePoint & 0x3F) << 24);
return [[NSString alloc] initWithBytes:&symbol length:sizeof(symbol) encoding:NSUTF8StringEncoding];
}
else
{
return nil;
}
}
了解了 NSString 和 Unicode 码点之间的转换关系,就可以理解上面解决方案里面提供的方案。
题外话
实际上,Emoji 的编码方式远比我们想象的要复杂得多,尽管上述的解决方案可以解决问题,但是在做码点和 NSString 的互相转化过程中,发现 Emoji 除了有单码点表示的方法之外,还有多码点组合字符的形式。
这篇文章里面会详细的介绍 Unicode 与 Emoji 的组合字:Unicode 和 Emoji 中的组合字符
网友评论