예전에 제가 다른 프로젝트들을 했을 때는 디바이스에 직접 저장하는 CoreData를 사용하거나 MySQL를 활용한 웹서버를 이용해서 데이터를 저장했었는데요. 둘 다 SQL 계열의 DB라서 많이 익숙했었습니다. 하지만 현재 프로젝트에서는 Firebase에서 제공하는 Firestore를 활용하고 있습니다. 이 DB는 NoSQL이라서 조금씩 배워가면서 하고 있습니다.
이번 포스팅에는 제가 단어들이 저장된 DB에서 특정 문자열을 포함한 단어를 fetch 해오는 기능을 만들고자 합니다. 2가지 방법으로 구현하고 제가 최종적으로 어떤 방법을 선택했는지 소개하겠습니다.
첫 번째 방법은 약간 무식(?) 방법입니다. DB 그냥 단어를 전부 다 가져옵니다. 그리고 나서 filter를 통해서 해당 문자열을 포함한 단어를 찾는 방법입니다. 코드는 아래와 같습니다.
// Examples를 검색하는 함수
static func fetchExamples(_ query: String, completionHandler: @escaping ([WordExample]?, Error?) -> Void) {
Constants.Collections.examples
.getDocuments { snapshot, error in
if let error = error {
completionHandler(nil, error)
}
guard let documents = snapshot?.documents else { return }
let examples = documents
.compactMap { try? $0.data(as: WordExample.self) }
.filter { $0.meaningText.contains(query) }
completionHandler(examples, nil)
}
}
두 번째는 DB에서 필터링을 하도록 하는 것입니다. 아쉽게도 Firestore는 특정 문자열을 포함하는 데이터만 fetch 해 올 수 있는 메소드는 제공하지 않는 것 같습니다. 하지만 약간의 우회적인 방법으로 100% 1번과 동일한 기능은 아니지만 비슷한 기능을 구현할 수 있습니다.
String을 “<, >, =” 등이 비교연산자로 비교하면 아스키코드를 기준으로 비교합니다. 따라서 query ~ query + “힣" 사이의 모든 문자열을 반환하게 하면 검색기능을 구현할 수 있습니다. 마지막 범위에 “힣"을 붙이는 이유는 이 Character가 가장 큰 아스키 코드를 가지기 때문입니다. 코드는 아래와 같습니다.
// Examples를 검색하는 함수
static func fetchExamples(_ query: String, completionHandler: @escaping ([WordExample]?, Error?) -> Void) {
Constants.Collections.examples
.whereField("meaningText", isGreaterThanOrEqualTo: query)
//👉 원하는 문자열 보다 크고
.whereField("meaningText", isLessThan: query + "힣")
//👉 원하는 문자열 + "힣" 보다는 작거나 같은 문자열
.getDocuments { snapshot, error in
if let error = error {
completionHandler(nil, error)
}
guard let documents = snapshot?.documents else { return }
let examples = documents
.compactMap { try? $0.data(as: WordExample.self) }
.sorted(by: { $0.used > $1.used })
completionHandler(examples, nil)
}
}
이렇게 하면 DB에서 원하는 데이터만 filter해서 보내주므로 네트워크 비용이 절약되고 앱에서 filter를 할 필요도 없으므로 성능도 개선됩니다.
하지만 이 방법은 정확히 이야기하면 query를 포함한 모든 문자열을 반환하게 하는 방법이 아닙니다. query로 시작하는 문자열을 반환하도록 하는 방법입니다. 즉 만약에 query가 “사과"라면 “사과 나무”라는 문자열은 반환하기만 “햇사과"라는 문자열은 반환하지 않습니다.
저는 방법 2를 택했습니다. 이 API는 자주 호출되는 API입니다. 따라서 매번 모든 단어를 다 불러오는 것은 곤란합니다. 물론 단어들을 한번만 불러오고 메모리에 저장해두고 사용하는 방법도 있겠지만 단어를 불러온 이후 추가한 단어는 검색할 수 없다는 점에서 한계가 있습니다.
따라서 비록 검색 기능에 한계가 있는 방법이지만 자주 호출해도 부담이 없는 2번 방법을 선택하는 것이 적절하다고 생각했습니다.