Realm SDK로 관심사별 로직을 간단명료하게 분리하세요.

Realm Database series header

이 글은 Realm 모바일 데이터베이스에 대한 시리즈 글입니다. 여러 플랫폼에서 데이터베이스를 사용할 수 있는 Realm의 멀티 플랫폼 기능과 높은 기기 간 코드 재사용성에 대해 알아봅니다.

혹시 시리즈 시작 글을 놓치셨다면 첫 번째 글과 성능 향상과 메모리 사용량 최소화에 최적화된 Realm API를 소개하는 두 번째 글, 플랫폼 간 코드 공유를 다룬 세 번째 글을 먼저 보시길 추천합니다.

다음 시리즈에 대한 한글 번역은 아직 진행 중으로, 미리 보고 싶은 분들은 영문 원문인 작은 단위 알림을 기반으로 부분적인 UI 업데이트를 제공할 수 있게 하는 Realm SDK를 다룬 다섯 번째 글, 마지막으로 이같은 기능이 모여 어떻게 최신 모바일 애플리케이션을 만드는지에 대한 여섯 번째 글을 참고해 주세요.


서드 파티 SDK를 사용하면 개발에 도움이 될 수도 있고 특정 방향으로 유도할 수도 있습니다. 예를 들어 탁월한 기능을 가진 일체형 라이브러리를 사용해서 기본 앱 로직 일부를 빠르게 구현할 수도 있지만, 더 많은 부작용을 야기할 수도 있겠죠.

Realm 모바일 데이터베이스는 데이터를 저장하는 방법을 제공할 뿐 아니라 좋은 방향으로 구현하는 데에도 도움을 줍니다. Swift, Java, JavaScript, C# 등 많은 프로그래밍 언어에서 사용할 수 있는 유연한 SDK를 제공하며, 데이터 객체를 정밀하게 조정할 수 있고 변화에 반응하거나 서버와 통신하는 고차원 API도 제공합니다.

자신의 제품에 어떤 SDK나 서드 파티 라이브러리 혹은 서비스를 포함하려면 해당 제품에서 목표하는 것과 동일한 퀄리티를 가져야 합니다.

관심 분리

시작하기에 앞서 이전 시리즈에서 기능에 대한 예시로 사용해온 간단한 프로젝트를 다시 소개하겠습니다. GitHub 저장소에서 클론 하거나 다운로드해서 살펴볼 수 있습니다.

이제 Realm Swift SDK가 예시 프로젝트에서 어떻게 관심을 분리하는지 간략히 알아보겠습니다. 독자들이 여러 다른 아키텍처를 사용할 테니 특정 아키텍처에 국한해서 설명하지는 않겠습니다. 코드를 살펴보고 자신의 프로젝트에 가장 적합한 형태로 적용해 보세요.

이런 개발 뉴스를 더 만나보세요

예제 프로젝트는 iOS, macOS, tvOS, watchOS, 네 개의 플랫폼을 타깃으로 하는 단일 Xcode 워크스페이스입니다.

데모 프로젝트 플랫폼

Realm Swift SDK가 이들 플랫폼을 지원하므로 이들 사이에 데이터 레이어 코드를 공유할 수 있습니다. 이전 시리즈에서 Favorite 데이터 엔티티를 살펴보면서 오직 FoundationRealmSwift 프레임워크에만 종속됨을 알 수 있었습니다.

import Foundation
import RealmSwift

class Favorite: Object {
    dynamic var symbol = "💖"
    var symbolIndex: Int? {
        return Favorite.symbols.index(of: symbol)
    }
    static let symbols = ["💖", "🚀", "🤕"]
    static let noSymbolIndex = -1
}

따라서 데이터 엔티티 클래스는 일반적인 Foundation 타입과 Realm 자체만을 사용하므로 그대로 플랫폼 사이에서 재사용될 수 있습니다.

이제 데이터 엔티티 클래스를 더 자세히 살펴보겠습니다.

본질적으로 이 프로젝트에서 빌드하는 모든 앱은 리스트 기반 애플리케이션입니다. 플랫폼에 따라 좀 다르게 보일 수는 있지만 기본 구조는 다음과 같습니다.

  • 저장소 리스트를 보여주고 필터링할 수 있는 기본 화면 (watch 앱에서는 필터링 기능 제외)
  • 특정 저장소 세부 내용을 보여주는 화면 + 가능한 경우 즐겨 찾기 기능

기본 화면은 아이템 목록을 보여줍니다. iOS 프로젝트는 UITableView를, macOS 프로젝트는 NSTableView를, tvOS는 UICollectionView를 사용하고 있습니다. iOS 스토리보드는 다음과 같습니다.

리스트 iOS 앱 스토리보드

watchOS 앱은 다음과 같습니다.

List watchOS App Storyboard

각 프로젝트가 사용하는 특정 UI 구성 요소와 관계없이 최종적으로 리스트를 제공하기 때문에 “저장소 리스트를 보여주는” 로직을 타깃에 특화된 UI 코드에서 분리할 수 있습니다.

이 방법으로 프로젝트를 세 개의 깔끔하게 구분되는 레이어로 분리할 수 있습니다.

1. 데이터 저장 및 관찰 레이어

데이터를 저장하고 검색하는 것은 Realm에서 처리합니다. API는 Apple의 Foundation에만 의존성을 가지므로 Object 서브클래스나 엔티티를 처리하는 다른 코드를 메인 프로젝트로부터 쉽게 추상화해서 분리할 수 있습니다.

또한, 이 레이어는 Realm 데이터베이스를 사용하는 코드와 긴밀하게 통합되는 데이터 모델의 모든 특성을 구현합니다.

예를 들어 Repository 객체는 앱에서 Realm에 저장할 모든 데이터 속성을 포함합니다.

class Repository: Object {
    dynamic var id: Int = 0
    dynamic var stars: Int = 0
    dynamic var url: String = ""
    ...
}

하지만 Realm 데이터베이스에서 모든 저장소를 가져오는 등의 동작은 몇 가지 비즈니스 규칙을 따라야 합니다.

static func all(searchTerm: String? = nil) -> Results<Repository> {
    let realm = try! Realm()
    return realm.objects(Repository.self)
        .filter("name contains[c] %@", searchTerm ?? "")
        .sorted(byProperty: "stars", ascending: false)
}

저장소를 즐겨 찾기 하거나 해제하는 등의 다른 동작도 예제 프로젝트에서 확인할 수 있습니다.

2. 공유 애플리케이션과 프리젠테이션 로직

이 레이어는 특정 플랫폼에 국한되지 않도록 독립적으로 추상화할 수 있는 애플리케이션 로직을 구현합니다. 앞서 “저장소 리스트를 보여주는” 로직에 대해서 말했지만, 이 레이어 역시 “데이터 모델 변화에 반응하고” 네트워크 연결을 합니다.

ReposListPresenter 클래스는 저장소 리스트를 보여주는 역할을 합니다. 데이터 레이어와 통신해서 저장소 리스트를 불러오고 해당 리스트를 최신으로 유지합니다.

모든 프로젝트 타깃을 위한 데이터 소스로 사용할 수 있도록 플랫폼에 특화된 몇몇 프로토콜을 사용합니다. (선호하는 아키텍처에 따라 일반적인 “데이터 소스” 프로토콜을 만들고 이를 플랫폼에 특화된 프로토콜과 연결할 수도 있습니다.)

플랫폼에 특화된 코드를 포함한 실제 UI를 업데이트해야 하므로 ReposListPresenter는 각 타깃이 자신의 코드에서 UI 업데이트를 처리할 수 있도록 일반 콜백을 제공합니다.

func loadRepos(searchFor term: String? = nil, updated: @escaping (RealmCollectionChange<Results<Repository>>) -> Void) {
    refreshToken?.stop()
    repos = Repository.all(searchTerm: term)
    refreshToken = repos?.addNotificationBlock(updated)
}

loadRepos(searchFor:updated:)는 일치하는 저장소를 Realm 데이터베이스에서 찾고 결과 집합을 위한 알림 구독을 생성하며, 변경 알림을 updated 매개 변수 클로저와 바인드 합니다.

3. 뷰와 뷰 제어

마지막으로 타깃별 UI 코드를 살펴볼까요?

각 타깃은 해당 플랫폼에 맞는 각각의 스토리보드가 있습니다. 각 플랫폼의 뷰 컨트롤러 블래스는 모두 ReposListPresenter를 사용하며 콜백 안에서 자신의 UI를 업데이트하는 코드를 구현합니다.

예를 들어 macOS 앱은 NSTableView API를 사용합니다.

reposPresenter.loadRepos(searchFor: sender?.stringValue ?? "", updated: { changes in

    switch changes {
        case .initial:
            self.tableView.reloadData()
            
        case .update(_, let deletions, let insertions, let updates):
            self.tableView.beginUpdates()
            self.tableView.insertRows(at: IndexSet(insertions), withAnimation: .slideDown)
            self.tableView.reloadData(forRowIndexes: IndexSet(updates), columnIndexes: IndexSet(integer: 0))
            self.tableView.removeRows(at: IndexSet(deletions), withAnimation: .slideUp)
            self.tableView.endUpdates()

        default: break
    }

})

NSTableView API는 여러 열을 가진 테이블 처리와 macOS에서의 업데이트 애니메이션을 제공합니다.

한편 tvOS 뷰 컨트롤러는 TV 화면에 컬렉션뷰를 사용해서 저장소를 표시하므로 UICollectionView API를 사용합니다.

reposPresenter.loadRepos(searchFor: sender?.text) { changes in

    switch changes {
        case .initial:
            self.collectionView?.reloadData()

        case .update(_, let deletions, let insertions, let updates):
            let cv = self.collectionView!

            cv.performBatchUpdates({
                cv.insertItems(at: insertions.map {IndexPath(row: $0, section: 0)})
                cv.reloadItems(at: updates.map {IndexPath(row: $0, section: 0)})
                cv.deleteItems(at: deletions.map {IndexPath(row: $0, section: 0)})
            }, completion: nil)

        default: break
    }
}

이 코드는 UICollectionView.performBatchUpdates(_:completion:)를 사용해서 데이터 변경 사항을 UI에 반영합니다. macOS 코드와 로직 상으로는 비슷하지만 문법이 조금 다릅니다.

변경 사항을 관찰하고 업데이트 클로저를 발동하는 것은 이미 추상화돼 있으므로 현재 레이어는 플랫폼에 특정된 UI 코드만을 다룹니다.

기본 기능만으로 반응형 앱 만들기 🚀

예제 프로젝트와 함께 얼마나 쉽게 관심을 분리할 수 있는지 확인해 봤습니다. 앱이 보다 복잡해지면 서로 다른 계층을 추상화해서 별도의 프레임워크로 분리하고 잠재적으로 다른 프로젝트에서도 재사용할 수 있습니다.

특히 Realm SDK를 사용하기 때문에 프로젝트의 초기 단계에서부터 반응형 아키텍처를 구현할 수 있다는 점이 핵심입니다.

Realm으로 메시지 주도 형 앱을 만들 수 있습니다. 예를 들어 ReposListPresenter는 변경 알림에만 반응하며, GitHubAPI 클래스는 네트워크 통신과 데이터베이스 내에 저장소를 업데이트하는 것에만 초점을 맞춥니다. 두 기본 클래스를 통해 데이터베이스의 쓰기와 읽기가 완전히 독립적으로 (필요시 다른 스레드에서) 일어나며 업데이트는 오직 Realm 알림으로만 실행됩니다.

또한 견고한 앱을 만들 수 있습니다. Realm SDK는 매우 견고하고 병합 시의 충돌이나 데이터 손실 등 데이터 저장과 관련된 문제를 쉽게 회피할 수 있습니다. 클래스를 쉽게 분리할 수 있으므로 데이터 쓰기 작업을 하는 클래스에서 벌어질 수 있는 문제가 프리젠터 클래스에 영향을 미치지 않습니다.

마지막으로 반응형 앱을 만들 수 있습니다. Realm 데이터베이스에 저장소의 로컬 캐시를 유지할 수 있으므로 앱을 열자마자 새로운 리스트가 백그라운드에서 로딩되는 동안에도 로컬에 저장된 저장소를 볼 수 있습니다. 업데이트된 데이터가 디스크에 저장되면 Realm이 변경 알림을 제공하므로 앱에서 아이템별 애니메이션과 함께 UI를 업데이트할 수 있습니다.

가장 중요한 것은 Realm SDK를 통해 프로젝트가 이미 반응형 앱으로 변신했으므로, 필요시 이 기본 구조를 사용해서 더 많은 기능을 구축할 수 있다는 점입니다. 예를 들어 iOS의 RxSwift나 안드로이드의 RxJava처럼 서드 파티 리액티브 프레임워크를 현재 프로젝트 코드에 쉽게 통합할 수 있습니다.

글이 마음에 드셨나요?

Realm SDK가 훌륭한 아키텍처를 구현하는 방법에 대해 알아봤습니다. Realm SDK는 강력하고 유연하므로 사용하고자 하는 특정 아키텍처가 무엇이든 상관없이 비즈니스 로직의 관심사를 분리하고 클래스를 분리해서 안전하게 데이터를 사용할 수 있습니다.

더 자세히 알고 싶은 분은 Realm 모바일 데이터베이스를 사용해 보세요.

다음 연재에서는 Realm의 정밀한 알림 기능을 알아보고, 왜 결과를 가져오는 컨트롤러가 필요하지 않은지도 설명하겠습니다.

다음 편에서 만나요! 👋

다음: Realm의 차별화 요소들 #5: 라이브 오브젝트와 정밀한 알림: Realm 업데이트 기능

General link arrow white

컨텐츠에 대하여

이 컨텐츠는 저자의 허가 하에 이곳에서 공유합니다.


Marin Todorov

Marin Todorov는 iOS 컨설턴트이자 퍼블리셔입니다. “iOS Animations by Tutorials”의 저자이며 iOS Animations by Emails” 뉴스레터를 운행하고 있습니다. 20년 넘게 Apple 관련 개발을 했으며 Monster Technologies와 Native Instruments 등의 회사에서 일하면서 4개 이상의 나라에서 거주했습니다. 또한 raywenderlich.com 튜토리얼 팀의 설립 멤버이기도 하죠. 코드 개발 이외에는 블로그 게재와 책 저술, 교육과 강연에 관심이 많으며, 코드를 오픈 소스화 하기도 합니다. Santiago 순례 경험도 있습니다.

4 design patterns for a RESTless mobile integration »

close