koogawa blog

iOS、Android、foursquareに関する話題

RealmSwiftで簡単なGPSロガー作ってみたのでメモ(Swift 3編)

以前書いた下記記事のソースコードが古くなっていたので、Swift 3対応版として書き直しました。

RealmSwiftで簡単なGPSロガー作ってみたのでメモ - koogawa blog


以前から気になっていた Realm ですが、先日受講した id:KishikawaKatsumi さんの授業をきっかけに、実際に触ってみたくなりました。 Realm を理解するには何か作ってみるのが一番ってことで、簡単なGPSロガーを作ってみました。

f:id:koogawa:20150802150835p:plain

次のような機能があります。

  • Startボタンを押すと位置情報を記録開始
  • アプリをバックグラウンドに落としても記録し続ける
  • 位置情報が取得されると地図にもピンが立つ
  • 1日経過したデータは起動時に自動削除
  • Stopボタンを押すと位置情報の取得終了

***

以下、実装メモです。

動作環境

  • Xcode 8.3
  • Swift 3.0
  • RealmSwift 2.4.2
  • CocoaPods 1.2.0

RealmSwift インストール

次のような Podfile を用意して pod install します。

use_frameworks!

target 'GPSLogger' do
  pod 'RealmSwift'
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '3.0'
    end
  end
end

RealmSwift インポート

import RealmSwift

モデルクラス作成

位置情報を格納するためのモデルクラスを作ります。緯度、経度、作成日時のみを保持するようにしました。

class Location: Object {
    @objc dynamic var latitude: Double = 0.0
    @objc dynamic var longitude: Double = 0.0
    @objc dynamic var createdAt = Date(timeIntervalSince1970: 1)
}

Realm では次の型が使用できます:Bool, Int8, Int16, Int32, Int64, Double, Float, String, NSDate, NSData

https://realm.io/docs/swift/latest/#supported-types

Startボタンを押したときの処理

位置情報の取得を開始します。

self.locationManager.startUpdatingLocation()

位置情報を永続化

取得できた位置情報(CLLocation)から Location モデルを生成し、Realmに永続化できるようにします。

let location = makeLocation(rawLocation: rowLocation)

// Get the default Realm
let realm = try! Realm()

// Add to the Realm inside a transaction
try! realm.write {
    realm.add(location)
}

テーブルビューを更新

addNotificationBlock メソッドでブロックを登録すると、Realmオブジェクトが更新されたときに通知を受けることができます。

self.token = realm.observe {
    [weak self] notification, realm in
    self?.tableView.reloadData()
}

ここでは通知を受け取るたび(つまり位置情報が追加されるたび)にテーブルビューを更新しています。

Stopボタンを押したときの処理

位置情報の取得を停止します。

self.locationManager.stopUpdatingLocation()

通知の停止

先ほど addNotificationBlock メソッドでブロックを登録した際に保持した NotificationToken オブジェクトの stop メソッドを呼ぶことで、通知の受け取りを停止します。

if let token = self.token {
    token.invalidate()
}

アプリを起動したときの処理

すでに永続化されている位置情報をロードします。

// Get the default Realm
let realm = try! Realm()

// Load recent location objects
return realm.objects(Location.self).sorted(byKeyPath: "createdAt", ascending: false)

createdAt でソートをかけています。

古いデータの削除

古いデータがいつまでも残ってしまうのを防ぐため、1日経過した位置情報ログを削除します。

DispatchQueue.global().async {
    // Get the default Realm
    let realm = try! Realm()

    // Old Locations stored in Realm
    let oldLocations = realm.objects(Location.self).filter(NSPredicate(format:"createdAt < %@", NSDate().addingTimeInterval(-86400)))

    // Delete an object with a transaction
    try! realm.write {
        realm.delete(oldLocations)
    }
}

データ量によっては時間がかかるので、バックグラウンドで実行するようにしています。

所感

CoreData を初めて使った時と比べると、かなりスムーズに導入・実装ができました。ドキュメントが充実しているのも安心ですね。

ソースコード

こちらに全てアップしてあります。Realm を使い慣れている方にとってはツッコミどころ満載だと思いますので、ご指摘など歓迎です:-)

github.com

リンク