고통없이 오프라인 우선 모바일 앱 구축

Alexander Stigsen은 Realm의 공동 창립자이자 CEO입니다.

스마트 폰을 소지 한 사용자는 더 나은 연결을 원해야한다는 것은 보편적으로 인정되는 사실입니다. 수십억 달러의 인프라 투자와 끊임없는 기술 혁신에도 불구하고 연결된 시대의 본질적인 현실을 알아 차리는 데는 짧은 시간 밖에 걸리지 않습니다. 원할 때마다 네트워크 연결을 사용할 수있을 것이라고 가정 할 수는 없습니다. 모바일 개발자로서 무시하는 것이 편리한 진실입니다.

앱의 오프라인 상태는 처리하기에 혼란 스러울 수 있지만 문제는 기본적으로 잘못된 가정 (오프라인은 기본적으로 오류 상태)에서 시작됩니다. 이는 전용 이더넷 업 링크가있는 데스크톱 컴퓨터 용 앱을 구축했을 때 의미가 있습니다. 엘리베이터 문을 닫아서 앱이 완전히 쓸모 없게되거나 신뢰할 수있는 셀룰러 인프라가없는 곳에서 애플리케이션이 사용될 것으로 예상하는 것이 합리적 일 때는 말이되지 않습니다.

우리는 전 세계를 커버 할 수 없으므로 대안을 제공해야합니다. 오프라인을 먼저 생각해야합니다. 오프라인에서 유용하도록 앱을 디자인해야합니다. 인터넷을 사용할 수있을 때 인터넷을 최대한 활용하는 앱을 만들어야하지만 인터넷 액세스는 항상 일시적임을 이해해야합니다. 오프라인 상태와 관련된 현명한 디자인 결정을 내리고 이러한 오프라인 상태를 사용자가 이해할 수 있도록 만들어야합니다.

오프라인 우선 미래를 정의하기 위해 많은 작업이 수행되고 있습니다. 제가 일하는 회사 인 Realm은 오프라인 우선 모바일 앱을위한 실시간 플랫폼을 한동안 구축해 왔습니다. 모바일 데이터베이스와 Realm 모바일 플랫폼을 사용하면 거의 모든 모바일 장치에서 지능형 오프라인 우선 앱을 쉽게 만들 수 있습니다. A List Apart의 사람들은 특히 웹 앱에 대한 오프라인 우선 문헌에 엄청난 기여를했습니다. 그리고 주요 모바일 생태계의 개발자 커뮤니티는 자체적으로 인상적인 오픈 소스 솔루션을 제공하는 데 많은 시간을 보냈습니다.

다음은 오프라인 우선 모바일 앱을 빌드하는 방법에 대한 간략한 소개입니다. 마지막에 간단한 Swift 샘플 코드를 그려서 최소한의 오프라인 우선 앱이 어떻게 생겼는지 보여줄 것이지만 여기에서 제공하는 원칙과 문제는 모바일 앱 개발 작업을하는 모든 사람과 관련이 있습니다.

오프라인 우선을위한 디자인

항상 원했던 오프라인 우선 앱을 빌드하기 전에 온라인 상태 일 가능성이 매우 높은 데스크톱에 적합한 디자인 솔루션을 다시 검토해야합니다. 앱이 오프라인 및 온라인 상태를 처리 할 수있는 경우 수행 할 수있는 작업과 가능한 것을 사용자에게 표시하는 방법에 대한 질문이 있습니다.

오프라인에서 가능한 것을 정의

Twitter를 예로 들어 보겠습니다. 오프라인 상태에서 트윗을 게시하는 경우 오프라인 우선 Twitter 클라이언트는 두 가지 경로를 취할 수 있습니다. 다시 연결될 때까지 트윗을 대기열에 넣을 수 있습니다. 또는 트윗 봇처럼 즐겨 찾기와 같은 다른 작업을 대기열에 넣을 수 있더라도 트윗을 거부 할 수 있습니다.

Tweetbot이 오프라인 트윗을 차단하는 이유는 무엇입니까? 온라인으로 돌아올 때 쯤이면 트윗이 더 이상 관련이 없을 수 있기 때문일 수 있습니다. 이 문제를 해결하려면 아직 게시하지 않은 트윗 목록에 대한 새 UI를 만드는 것이 포함되지만 온라인에 가기 전에 편집하거나 삭제해야 할 수 있습니다. 반면에 당신이 트윗을 마음에 든다면, 더 많은 정보를 만나면 그것을 되돌릴 가능성이 거의 없으며 단순히 게시 대기 중임을 나타내는 것이 훨씬 덜 문제가됩니다.

오프라인 앱이 온라인 앱이 할 수있는 모든 것을 할 수는 없지만 유용하게 만들 수는 있습니다.

갈등 제거

변경 사항을 조정하기 위해 백엔드에서 사용하는 전략에 관계없이 앱은 두 개의 충돌하는 데이터 조각이있는 지점에 직면하게됩니다. 서버가 충돌했거나 사용자와 다른 사람이 오프라인으로 변경하여 이제 동기화를 원하기 때문일 수 있습니다. 어떤 일이든 일어날 수있다!

따라서 갈등을 예상하고 예측 가능한 방식으로 해결하기 위해 노력하십시오. 선택권을 제공합니다. 그리고 애초에 갈등을 피하십시오.

예측 가능하다는 것은 사용자가 일어날 수있는 일을 알고 있다는 것을 의미합니다. 사용자가 오프라인 일 때 한 번에 두 곳에서 편집 할 때 충돌이 발생할 수있는 경우 오프라인 일 때이를 경고해야합니다.

선택권을 제공한다는 것은 단순히 마지막 쓰기를 수락하거나 변경 사항을 연결하거나 가장 오래된 사본을 삭제하는 것을 의미하지 않습니다. 사용자가 적절한 것을 결정하도록하는 것을 의미합니다.

마지막으로, 가장 좋은 해결책은 애초에 갈등이 발생하지 않도록하는 것입니다. 이는 여러 소스의 새롭고 이상한 데이터가 충돌을 일으키지 않고 대신 원하는대로 정확하게 표시되도록 앱을 빌드하는 것을 의미 할 수 있습니다. 온라인 및 오프라인으로 전환되는 쓰기 앱에서는이 작업을 수행하기 어려울 수 있지만 공유 드로잉 앱은 동기화 될 때마다 드로잉에 새 경로를 추가하도록 설계 할 수 있습니다.

명시 적

사용자가 오프라인에서 할 수있는 작업을 정의하는 것은 한 가지입니다. 또 다른 문제는 사용자가 이해할 수있는 결정을 내리는 것과 관련이 있습니다. 데이터 및 연결 상태 또는 지정된 기능의 가용성을 성공적으로 전달하지 못하는 것은 처음에 오프라인 우선 앱을 구축하지 못한 것과 같습니다.

공유 메모 작성 앱이 문제를 설명합니다. 오프라인으로 전환했지만 공동 작업자가 부재 중에도 앱에서 편집을 계속할 것으로 예상하는 경우 사용자가 만족할 때까지 계속 입력하도록 허용하는 것만으로는 충분하지 않습니다. 다시 연결되면 발생한 갈등에 놀라게됩니다.

대신 사용자가 올바른 결정을 내릴 수 있도록 도와주세요. 앱의 상단 표시 줄의 색상이 변경되어 서버 연결이 끊어진 것을 확인한 경우 발생할 수있는 상황을 알 수 있습니다. 병합 충돌입니다! 대부분의 경우 괜찮을 수 있으며 앱의 UI는 온라인으로 돌아올 때 예기치 않은 충돌을 해결하는 데 도움이 될 수 있습니다. 그러나 여러 사람이 앱을 편집 할 때 연결이 끊어지면 충돌 위험이 훨씬 더 크다는 것을 아는 것이 도움이되지 않을까요? “연결이 끊어졌지만 다른 사람들이 편집 중이었습니다. 계속 편집하면 충돌이 발생할 수 있습니다.” 사용자는 계속할 수 있지만 위험을 알고 있습니다.

디자인 문제와 솔루션에 대해 끝없이 작성하는 것은 쉽지만 우리가 사용해야 할 도구에서 너무 멀어지기 전에 오프라인 우선 모바일 앱을 구축하는 것이 어떤 것인지 확인하는 것이 도움이 될 수 있습니다.

Realm으로 오프라인 우선 앱 구축

기본적인 오프라인 우선 앱의 아키텍처는 화려하지 않습니다. 앱에서 데이터를 유지하는 방법 (기기 내 데이터베이스 사용), 서버와 통신하기위한 프로토콜 (필요한 경우 직렬화 및 역 직렬화 코드 포함), 동기화 된 데이터가 존재할 수있는 서버가 필요합니다. 권한이있는 사람에게 배포됩니다.

먼저 iOS 앱 내에서 Realm 모바일 데이터베이스를 시작하는 방법을 설명하겠습니다 (코드는 Android 앱에서 크게 다르지 않습니다). 그런 다음 서버에서 가져와 로컬 Realm 데이터베이스에 저장하는 코드를 직렬화 및 역 직렬화하는 전략을 제시합니다. 마지막으로 실시간으로 동기화되는 공동 작업 목록 앱에서 모든 작업을 함께 수행하는 방법을 보여 드리겠습니다.

Realm 모바일 데이터베이스

Realm을 시작하는 것은 쉽습니다. Realm 모바일 데이터베이스를 설치 한 다음 클래스를 만들어 스키마를 정의합니다. Realm은 객체 데이터베이스이기 때문에 클래스를 만들고, 일부 객체를 인스턴스화하고, 해당 객체를 write블록 으로 전달 하여 디스크에 유지하는 것만 큼 ​​간단 합니다. 직렬화 또는 ORM이 필요하지 않으며 Apple의 핵심 데이터보다 빠릅니다.

다음은 모델의 핵심과 가능한 가장 기본적인 할 일 목록 앱입니다 (새 작업을 만들 때마다 다시 컴파일해야 함).

import RealmSwift

class Task: Object {

   dynamic var name

}

class TaskList: Object {

   let tasks = List()

}

let myTask = Task()

myTask.task

let myTaskList = TaskList()

myTaskList.tasks.append(myTask)

let realm = Realm()

try! realm.write{

            realm.add([myTask, myTaskList])

}

거기에서 다음을 중심으로 더 완벽하게 기능하는 앱을 빌드하는 데 많은 시간이 걸리지 않습니다 TableViewController.

import UIKit

import RealmSwift

class TaskListTableViewController: UITableViewController {

   var realm = try! Realm()

   var taskList = TaskList()

   override func viewDidLoad() {

       super.viewDidLoad()

       print(Realm.Configuration.defaultConfiguration.fileURL!)

       // Here, you could replace self.taskList with a previously saved TaskList object

       try! realm.write {

           realm.add(self.taskList)

       }

       // add navbar +

       navigationItem.setRightBarButton(UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.add, target: self, action: #selector(displayTaskAlert)), animated: false)

   }

   func displayTaskAlert() {

       // make and display an alert that’ll take a name and make a task.

       let alert = UIAlertController(title: “Make a task”, message: “What do you want to call it?”, preferredStyle: UIAlertControllerStyle.alert)

       alert.addTextField(configurationHandler: nil)

       alert.addAction(UIAlertAction(title: “Cancel”, style: UIAlertActionStyle.cancel, handler: nil))

       alert.addAction(UIAlertAction(title: “Create Task”, style: UIAlertActionStyle.default, handler: { (action) in

           let task = Task()

           task.name = (alert.textFields?[0].text)!

           try! self.realm.write {

               self.realm.add(task)

               self.taskList.tasks.append(task)

           }

           self.tableView.reloadData()

       }))

       self.present(alert, animated: true, completion: nil)

   }

   override func didReceiveMemoryWarning() {

       super.didReceiveMemoryWarning()

   }

   override func numberOfSections(in tableView: UITableView) -> Int {

       return 1

   }

   override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

       return self.taskList.tasks.count

   }

   override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

       let cell = tableView.dequeueReusableCell(withIdentifier: “reuseIdentifier”, for: indexPath)

       cell.textLabel?.text = self.taskList.tasks[indexPath.row].name

       return cell

   }

}

시작하는 데 필요한 전부입니다! Realm의 컬렉션 및 객체 알림을 통해 훨씬 더 똑똑해질 수 있으므로 tableView객체가 추가되거나 삭제 될 때 지능적으로 다시로드 할 수 있지만 현재로서는 오프라인 우선 앱의 기반 인 지속성이 있습니다.

직렬화 및 역 직렬화

오프라인 우선 앱은 온라인 상태가 아니라면 오프라인 우선 앱이 아닙니다. Realm과 데이터를주고받는 것은 약간 까다로울 수 있습니다.

우선, 클라이언트 스키마를 서버의 스키마에 가깝게 일치시키는 것이 중요합니다. 대부분의 백엔드 데이터베이스가 작동하는 방식을 감안할 때 Realm 객체에는 기본적으로 기본 키가 없기 때문에 Realm 클래스에 기본 키 필드를 추가해야합니다.

스키마가 잘 일치되면 서버에서 Realm으로 들어오는 데이터를 역 직렬화하고 데이터를 JSON으로 직렬화하여 서버로 다시 보낼 수있는 방법이 필요합니다. 가장 쉬운 방법은 좋아하는 모델 매핑 라이브러리를 선택하여 무거운 작업을 수행하도록하는 것입니다. Swift에는 Argo, Decodable, ObjectMapper 및 Mapper가 있습니다. 이제 서버에서 응답을 받으면 모델 매퍼가이를 네이티브 RealmObject로 디코딩하도록하면됩니다.

그래도 그렇게 좋은 해결책은 아닙니다. 처음부터 서버에서 JSON을 안전하게 가져 오려면 여전히 많은 네트워킹 코드를 작성해야하며 스키마가 변경 될 때마다 모델 매퍼 코드를 다시 작성하고 디버깅해야합니다. 더 나은 방법이 있어야하며 Realm 모바일 플랫폼이 바로 그것이라고 생각합니다.

Realm 모바일 플랫폼 작업

Realm 모바일 플랫폼 (RMP)은 실시간 동기화를 제공하므로 서버와 앱이 대화를 나누는 것이 아니라 모바일 앱 구축에 집중할 수 있습니다. 위의 Realm 모델을 선택하고 RMP의 사용자 인증을 추가하고 RMP가 서버와 앱 영역 간의 데이터 동기화를 처리하도록합니다. 그런 다음 기본 Swift 객체로 계속 작업합니다.

시작하려면 Realm 모바일 플랫폼 MacOS 번들을 다운로드하여 설치하세요.이 번들을 사용하면 Mac에서 Realm 오브젝트 서버 인스턴스를 매우 빠르게 얻을 수 있습니다. 그런 다음 할일 목록 앱에 몇 가지 항목을 추가하여 Realm 오브젝트 서버에 연결합니다.

위의 설치 지침을 따르고 나면 서버가 실행되고 //127.0.0.1:9080에 관리자가 있어야합니다. 이 자격 증명을 기억하면 Swift 코드로 돌아갑니다.

더 많은 코드를 작성하기 전에 프로젝트에 두 가지 작은 변경을해야합니다. 먼저 Xcode에서 앱의 대상 편집기로 이동하고 기능 탭에서 키 체인 공유 스위치를 활성화해야합니다.

그런 다음 TLS가 아닌 네트워크 요청을 허용해야합니다. 프로젝트의 Info.plist 파일로 이동하여 태그 안에 다음을 추가합니다 .

NSAppTransportSecurity

   NSAllowsArbitraryLoads