背景在垃圾短信过滤应用 SMSFilters 中,需要使用 Jieba 分词库来対短信进行分词,然后使用 TF-IDF 来进行处理` 分词库是 C++ 写的,这就意味着需要在Swift中集成 C++ 库。在官方文档 “Using Swift with Cocoa and Objective-C” 中,Apple只是介绍了怎么将 Swift 代码跟 Objective-C 代码做整合,但是没有提C++,后来在官方文档中看到了这样一段话:You cannot import C++ code directly into Swift. Instead, create an Objective-C or C wrapper for C++ code.也就是不能直接导入 C++ 代码,但是可以使用 Objective-C 或者 C 对 C++ 进行封装。所以项目中使用 Objective-C 做封装,然后在 Swift 中调用,下面就是这个过程的实践,Demo 代码见 SwiftJiebaDemo。整合过程分成三步:引入C++文件;用 Objective-C 封装;在 Swift 中 调用 Objective-C;引入C++文件Demo中使用的是"结巴"中文分词的 C++ 版本 yanyiwu/cppjieba。将其中的 include/cppjieba 和依赖 limonp 合并,并加入 dict 中的 hmm_model 和 jiaba.dict 作为基础数据,并暴露 JiebaInit 和 JiebaCut 接口://// Segmentor.cpp// iosjieba//// Created by yanyiwu on 14/12/24.// Copyright (c) 2014年 yanyiwu. All rights reserved.//#include “Segmentor.h”#include <iostream>using namespace cppjieba;cppjieba::MixSegment * globalSegmentor;void JiebaInit(const string& dictPath, const string& hmmPath, const string& userDictPath){ if(globalSegmentor == NULL) { globalSegmentor = new MixSegment(dictPath, hmmPath, userDictPath); } cout << FILE << LINE << endl;}void JiebaCut(const string& sentence, vector<string>& words){ assert(globalSegmentor); globalSegmentor->Cut(sentence, words); cout << FILE << LINE << endl; cout << words << endl;}以及//// Segmentor.h// iosjieba//// Created by yanyiwu on 14/12/24.// Copyright (c) 2014年 yanyiwu. All rights reserved.//#ifndef iosjieba__Segmentor#define iosjieba__Segmentor#include <stdio.h>#include “cppjieba/MixSegment.hpp”#include <string>#include <vector>extern cppjieba::MixSegment * globalSegmentor;void JiebaInit(const std::string& dictPath, const std::string& hmmPath, const std::string& userDictPath);void JiebaCut(const std::string& sentence, std::vector<std::string>& words);#endif /* defined(iosjieba__Segmentor) */目录如下:$ tree iosjiebaiosjieba├── Segmentor.cpp├── Segmentor.h├── cppjieba│ ├── DictTrie.hpp│ ├── FullSegment.hpp│ ├── HMMModel.hpp│ ├── HMMSegment.hpp│ ├── Jieba.hpp│ ├── KeywordExtractor.hpp│ ├── MPSegment.hpp│ ├── MixSegment.hpp│ ├── PosTagger.hpp│ ├── PreFilter.hpp│ ├── QuerySegment.hpp│ ├── SegmentBase.hpp│ ├── SegmentTagged.hpp│ ├── TextRankExtractor.hpp│ ├── Trie.hpp│ ├── Unicode.hpp│ └── limonp│ ├── ArgvContext.hpp│ ├── BlockingQueue.hpp│ ├── BoundedBlockingQueue.hpp│ ├── BoundedQueue.hpp│ ├── Closure.hpp│ ├── Colors.hpp│ ├── Condition.hpp│ ├── Config.hpp│ ├── FileLock.hpp│ ├── ForcePublic.hpp│ ├── LocalVector.hpp│ ├── Logging.hpp│ ├── Md5.hpp│ ├── MutexLock.hpp│ ├── NonCopyable.hpp│ ├── StdExtension.hpp│ ├── StringUtil.hpp│ ├── Thread.hpp│ └── ThreadPool.hpp└── iosjieba.bundle └── dict ├── hmm_model.utf8 ├── jieba.dict.small.utf8 └── user.dict.utf8接下来开始在项目中集成。首先创建一个空项目 iOSJiebaDemo,将 iosjieba 加入项目中。单页应用SwiftJiebaDemo添加 SwiftJiebaDemo添加 iosjieba:见代码: https://github.com/qiwihui/Sw...C++ 到 Objective-C 封装这个过程是将 C++ 的接口进行 Objective-C 封装,向 Swift 暴露。这个封装只暴露了 objcJiebaInit 和 objcJiebaCut 两个接口。//// iosjiebaWrapper.h// SMSFilters//// Created by Qiwihui on 1/14/19.// Copyright © 2019 qiwihui. All rights reserved.//#import <Foundation/Foundation.h>@interface JiebaWrapper : NSObject- (void) objcJiebaInit: (NSString *) dictPath forPath: (NSString *) hmmPath forDictPath: (NSString *) userDictPath;- (void) objcJiebaCut: (NSString *) sentence toWords: (NSMutableArray *) words;@end//// iosjiebaWrapper.mm// iOSJiebaTest//// Created by Qiwihui on 1/14/19.// Copyright © 2019 Qiwihui. All rights reserved.//#import <Foundation/Foundation.h>#import “iosjiebaWrapper.h”#include “Segmentor.h”@implementation JiebaWrapper- (void) objcJiebaInit: (NSString *) dictPath forPath: (NSString *) hmmPath forDictPath: (NSString *) userDictPath { const char *cDictPath = [dictPath UTF8String]; const char *cHmmPath = [hmmPath UTF8String]; const char *cUserDictPath = [userDictPath UTF8String]; JiebaInit(cDictPath, cHmmPath, cUserDictPath); }- (void) objcJiebaCut: (NSString ) sentence toWords: (NSMutableArray ) words { const char cSentence = [sentence UTF8String]; std::vector<std::string> wordsList; for (int i = 0; i < [words count];i++) { wordsList.push_back(wordsList[i]); } JiebaCut(cSentence, wordsList); [words removeAllObjects]; std::for_each(wordsList.begin(), wordsList.end(), [&words](std::string str) { id nsstr = [NSString stringWithUTF8String:str.c_str()]; [words addObject:nsstr]; });}@end见代码: https://github.com/qiwihui/Sw...Objective-C 到 Swift在 Swift 中调用 Objecttive-C 的接口,这个在官方文档和许多博客中都有详细介绍。加入 {project_name}-Bridging-Header.h 头文件,即 SwiftJiebaDemo_Bridging_Header_h,引入之前封装的头文件,并在 Targets -> Build Settings -> Objective-C Bridging Header 中设置头文件路径 SwiftJiebaDemo/SwiftJiebaDemo_Bridging_Header_h。//// SwiftJiebaDemo-Bridging-Header.h// SwiftJiebaDemo//// Created by Qiwihui on 1/15/19.// Copyright © 2019 Qiwihui. All rights reserved.//#ifndef SwiftJiebaDemo_Bridging_Header_h#define SwiftJiebaDemo_Bridging_Header_h#import “iosjiebaWrapper.h”#endif / SwiftJiebaDemo_Bridging_Header_h */将使用到 C++ 的 Objective-C 文件修改为 Objective-C++ 文件,即 将 .m 改为 .mm: iosjiebaWrapper.m 改为 iosjiebaWrapper.mm。见代码:https://github.com/qiwihui/Sw…使用使用时需要先初始化 Jiaba分词,然后再进行分词。class Classifier { init() { let dictPath = Bundle.main.resourcePath!+"/iosjieba.bundle/dict/jieba.dict.small.utf8" let hmmPath = Bundle.main.resourcePath!+"/iosjieba.bundle/dict/hmm_model.utf8" let userDictPath = Bundle.main.resourcePath!+"/iosjieba.bundle/dict/user.dict.utf8" JiebaWrapper().objcJiebaInit(dictPath, forPath: hmmPath, forDictPath: userDictPath); } func tokenize(_ message:String) -> [String] { print(“tokenize…”) let words = NSMutableArray() JiebaWrapper().objcJiebaCut(message, toWords: words) return words as! [String] }}控制台输出结果:可以看到,测试用例 小明硕士毕业于中国科学院计算所,后在日本京都大学深造 经过分词后为〔拼音〕[“小明”, “硕士”, “毕业”, “于”, “中国科学院”, “计算所”, “,”, “后”, “在”, “日本”, “京都大学”, “深造”],完成集成。见代码: https://github.com/qiwihui/Sw…遇到的问题由于自己对于编译链接原理不了解,以及是 iOS 开发初学,因此上面的这个过程中遇到了很多问题,耗时两周才解决,故将遇到的一些问题记录于此,以便日后。“cassert” file not found将 .m 改为 .mm 即可。compiler not finding <tr1/unordered_map>设置 C++ Standard Library 为 LLVM libc++参考: mac c++ compiler not finding <tr1/unordered_map>warning: include path for stdlibc++ headers not found; pass ‘-std=libc++’ on the command line to use the libc++ standard library instead [-Wstdlibcxx-not-found]Build Setting -> C++ Standard Library -> libstdc++ 修改为 Build Setting -> C++ Standard Library -> libc++use of unresolved identifier这个问题在于向项目中加入文件时,Target Membership 设置不正确导致。需要将对于使用到的 Target 都勾上。相关参考: Understanding The “Use of Unresolved Identifier” Error In Xcode参考SwiftArchitect 对问题 “Can I have Swift, Objective-C, C and C++ files in the same Xcode project?” 的回答SwiftArchitect 对问题 “Can I mix Swift with C++? Like the Objective - C .mm files” 的回答在Swift代码中整合C++类库