Mobile Development 34 min read

Designing Mobile Persistence Architecture with Virtual Record and DataCenter

This article explains how to choose and design persistence solutions for iOS apps, covering NSUserDefaults, Keychain, file storage, databases, isolation between persistence and business layers, multithreading, and the Virtual Record pattern implemented via DataCenter and CTPersistance.

Architect
Architect
Architect
Designing Mobile Persistence Architecture with Virtual Record and DataCenter

Preface

Persistence solutions, whether on the server or client, are a hot topic because they can significantly affect performance; on mobile, maintainability and extensibility are usually more important than raw speed. This article focuses on the design of a mobile persistence layer, embedding performance tuning within each section.

The persistence layer often becomes the main source of coupling in an app architecture, so I continue the practice of de‑modeling by introducing a Virtual Record design, which is described in detail later.

Choosing a Persistence Scheme Based on Requirements

When persistence is needed, many options exist: NSUserDefaults, Keychain, file storage, and various database‑based solutions.

NSUserDefaults

Suitable for small, weakly related data. Storing large or strongly related data in NSUserDefaults is a bad practice (example: the Taobao app mixes everything into NSUserDefaults, which is a maintenance nightmare).

Keychain

Provides reversible encryption, survives app uninstall, and can be synced via iCloud. Ideal for sensitive small data such as user identifiers.

File Storage

Includes Plist, archive, and stream methods. Structured data is often persisted as Plist; large or rarely used data can be archived; streams are used for images and other frequently accessed but not huge files.

Database Storage

iOS offers Core Data and many third‑party alternatives (FMDB, etc.). When data has a clear state and category that are strongly business‑related, a database is the preferred solution. Large binary assets (images, articles) are usually stored as file names in the database, with the actual files kept on disk.

In short, use NSUserDefaults, Keychain, or simple file storage for trivial data; for anything that requires strong business semantics, adopt a database‑based approach.

Isolation Considerations When Implementing the Persistence Layer

The following aspects of isolation should be addressed:

Isolation between persistence and business layers

Database read/write isolation

Multithreading‑induced isolation

Isolation of storage methods and schemes

Isolation of data expression and data operations

1. Persistence‑Business Layer Isolation

About Model

Earlier I introduced the concepts of fat Model and skinny Model , and advocated a de‑modeling approach in the network layer. The same ideas apply to the persistence layer: the persistence side can use a fat Model while the network side uses de‑modeling , and they complement each other.

Confusion often arises when architects mix the terms Model and Model Layer . Clarifying the distinction helps avoid architectural decay.

Data Model

A Data Model describes how business data is represented in code (byte streams, JSON, objects, etc.). The choice of representation follows the business requirements.

Model Layer

The Model Layer is responsible for CRUD operations. A skinny Model typically stops at CRUD, while a fat Model also provides caching, synchronization, and weak‑business handling.

My Preference

I prefer de‑modeling in the network layer (implemented by a reformer ) and Virtual Record in the persistence layer. Reducing the influence of concrete models weakens coupling and makes migration easier.

Database Read/Write Isolation

On the web, read/write splitting improves response speed; on iOS, it improves code maintainability. The goal is not to separate read and write operations per se, but to define a boundary beyond which data objects are immutable for the business layer.

Core Data does not enforce such isolation; any change to an NSManagedObject is persisted on save . In my experience, Core Data is over‑engineered for many mobile use‑cases, leading to hacks and higher learning curves.

SQLite supports three threading modes: Single Thread (not thread‑safe), Multi Thread (connections cannot be shared), and Serialized (default, uses a serial queue). For most mobile apps, Serialized mode offers a good balance of safety and performance.

Multithreading‑Induced Isolation

Core Data

In multithreaded scenarios, Core Data requires a private‑queue context with its parentContext on the main thread. Objects should be passed by NSManagedObjectID , not by direct pointer.

SQLite

SQLite’s Multi Thread mode locks the whole database with a read/write lock; Serialized mode uses a single queue, which is usually sufficient for mobile apps.

Isolation of Data Expression and Data Operations

Active Record mixes data expression with data operations, leading to tight coupling. Separating Table (weak business) from Record (strong business) improves extensibility and migration.

Interaction Between Persistence and Business Layers

The diagram below illustrates how DataCenter (business‑friendly API) interacts with Virtual Record and CTPersistance tables.

-------------------------------------------
                 |                                         |
                 |  LogicA     LogicB            LogicC    |    ------------------------------->    View Layer
                 |     \         /                 |       |
                 -------\-------/------------------|--------
                         \     /                   |
                          \   / Virtual            | Virtual
                           \ /  Record             | Record
                            |                      |
                 -----------|----------------------|--------
                 |          |                      |       |
  Strong Logics  |     DataCenterA            DataCenterB  |
                 |        /   \                    |       |
-----------------|-------/-----\-------------------|-------|    Data Logic Layer   ---
                 |      /       \                  |       |                         |
   Weak Logics   | Table1       Table2           Table     |                         |
                 |      \       /                  |       |                         |
                 --------\-----/-------------------|--------                         |
                          \   /                    |                                 |--> Data Persistance Layer
                           \ / Query Command       | Query Command                   |
                            |                      |                                 |
                 -----------|----------------------|--------                         |
                 |          |                      |       |                         |
                 |          |                      |       |                         |
                 |      DatabaseA              DatabaseB   |  Data Operation Layer ---
                 |                                         |
                 |             Database Pool               |
                 -------------------------------------------

DataCenter provides business‑friendly interfaces (strong logic) and delegates weak logic to CTPersistanceTable . Each Table generates SQL via a QueryCommand and talks to the underlying database.

Interaction Scenarios

One‑to‑One Interaction

Typical case where a business object fetches a Virtual Record and directly displays it. The code is straightforward and omitted here.

Many‑to‑One Interaction

Multiple view objects contribute parts of a record (e.g., nickname, avatar, token) that together form a complete Virtual Record . The mergeRecord:shouldOverride: method combines them.

/* RecordViewA, RecordViewB, RecordViewC implement mergeRecord:shouldOverride: */
RecordViewA *a;
RecordViewB *b;
RecordViewC *c;
/* Collect values … */
[[a mergeRecord:b shouldOverride:YES] mergeRecord:c shouldOverride:YES];
[self.dataCenter saveRecord:a];

One‑to‑Many Interaction

One object may need data from several tables (vertical slicing) or display data from different tables (horizontal slicing). The same mergeRecord approach is used for both storing and retrieving.

/* Store */
[self.CasaTable insertRecord:a error:NULL];
[self.TaloyumTable insertRecord:a error:NULL];
[self.CasatwyTable insertRecord:a error:NULL];

Many‑to‑Many Interaction

Combines the previous patterns; implementation is identical, so it is omitted.

Summary of Interaction Schemes

Separate strong and weak business by distinguishing Data Model into Table (weak) and Record (strong). DataCenter implements strong logic, while Table implements weak logic. Benefits include reduced coupling, easier migration, read/write isolation, and a solid foundation for the Virtual Record pattern.

Using Virtual Record moves data adaptation logic into concrete record implementations, resulting in cleaner, more maintainable code.

Finally, a brief note on database slicing (vertical and horizontal) as performance‑optimisation techniques for mobile apps.

mobile developmentarchitectureiosPersistenceDataCenterVirtual Record
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.