Couples: powered by CoreStore (english ver)

(日本語の記事はこちら

Hi guys! I’m John, one of the iOS developers in the Couples team.
I’m also the author of CoreStore, a Swift Core Data library which we use heavily in our very-popular Couples app.
CouplesIcon
I started writing CoreStore a year ago when Swift was first released, and has since then grown to be the stable powerhouse it is today (and still improving!) thanks to the heavy demands of our app.

Today I’d like to share CoreStore’s features that provides foundation to the Couples iOS app.

Couples × CoreStore

Type Safety

With the wide array of data stored and displayed in the Couples app, our Core Data model contains more than 40 data entities including Messages, Q&As, Calendars, Links, Albums, Photos, Articles, Stamps, etc.
CoreStore allows no type-casts by exposing type-safe generic classes and utilities:

extension ArticlesViewController: ListObjectObserver {

    func listMonitor(monitor: ListMonitor<Article>, didUpdateObject object: Article, atIndexPath indexPath: NSIndexPath) {
        
        let article: Article = monitor[indexPath]
        // ...
    }
}

Transactions

The Couples app keeps a record of a couple’s photos, messages, anniversaries, etc. that gets updated in the background. Multiple reads and writes on a single database is usually unsafe as it introduces race conditions that may break data in a way that the user does not expect.

CoreStore prevents this problem by making reads execute on the main thread, while executing background updates within transactions:

CoreStore.beginAsynchronous { (transaction) -> Void in

    let event: Event = transaction.create(Into(Event))
    event.eventDate = eventDate
    event.startDate = startDate
    event.endDate = endDate
    // ...
    transaction.commit { result in
        // ...
    }
}

Internally, CoreStore executes these transactions in a serial queue.
1 SQLite file = 1 Transaction Queue.
transaction
You might be worried then, wouldn’t this setup cause bottlenecks?

Data Separation

The answer is that CoreStore heavily supports multiple Data Stacks. The Couples app separates the data stack for unrelated data so transactions within them can run independently:
transactions

Data Import

When the app receives updates from our webserver, CoreStore efficiently imports lists of data by using an efficient batch update-insert algorithm. In our code, all we have to do is implement the ImportableUniqueObject protocol and its methods:

class Article: NSManagedObject, ImportableUniqueObject {
    // ...
    func updateFromImportSource(source: JSON, inTransaction transaction: BaseDataTransaction) throws {

        self.linkURL = source["url"] as? String
        self.siteTitle = source["title"] as? String
        self.siteDescription = source["desc"] as? String
        // ...
    }

Now anytime we receive a JSON response from our server, we just have to start a transaction and import our JSON array

CoreStore.beginAsynchronous { (transaction) -> Void in

    do {

        let articles: [Article] = try transaction.importUniqueObjects(
            Into(Article),
            sourceArray: json["articles"]
        )
    }
    // ...
    transaction.commit { result in
        // ...
    }
}

To prevent duplicates, this importing utility also manages uniquing when a unique ID is provided.

Real-time Updates

To provide users their latest chat messages, photos, and other notifications from their loved one, Couples need to display data to the user as soon as possible. CoreStore provides a ListMonitor and an ObjectMonitor class that replaces NSFetchedResultsController and KVO.

let anniversaryListMonitor: ListMonitor = CoreStore.monitorList(
    From(Event),
    Where("isAnniversary == true")
        && Where("startDate >= %@", dateToday)
        && Where("startDate <= %@", dateTomorrow),
    OrderBy(.Ascending("startDate")
)

These classes can send notifications to multiple observers, which means a single list can be shared by multiple view controllers.

Shared.anniversaryListMonitor.addObserver(anniversaryViewController)
// ...
Shared.anniversaryListMonitor.addObserver(calendarViewController)

This saves a lot of memory!

Migration

One of the headaches when releasing app updates is Core Data migrations. Normally you need to provide mapping models from all previous versions to the latest version:
migrationBefore
This is hard to maintain! CoreStore saved us from all the maintenance burden by allowing incremental migration.
migrationAfter
This means we only need to add a new version and CoreStore takes care of the rest!

CoreStore.defaultStack = DataStack(
    modelName: "CouplesData",
    migrationChain: ["CouplesDataV1", "CouplesDataV2", "CouplesDataV3", "CouplesDataV4"]
)

Closing

For apps that need heavy data management, such as social networking apps and other media content apps, I definitely recommend CoreStore. I update the Github repository regularly and I’d be happy to answer any questions. Just send an issue or a pull request anytime!

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

EvernoteのトップメニューのようなスクロールアニメーションをするCollectionViewの作り方

regexpとの付き合い方 〜 Go言語標準の正規表現ライブラリのパフォーマンスとアルゴリズム〜