启用 iOS 移动应用的脱机同步功能Enable offline syncing with iOS mobile apps

Note

Visual Studio App Center 正在投资于对移动应用开发至关重要的新集成服务。Visual Studio App Center is investing in new and integrated services central to mobile app development. 开发人员可以使用生成测试分发服务来设置持续集成和交付管道。Developers can use Build, Test and Distribute services to set up Continuous Integration and Delivery pipeline. 部署应用后,开发人员可以使用分析诊断服务监视其应用的状态和使用情况,并使用推送服务与用户互动。Once the app is deployed, developers can monitor the status and usage of their app using the Analytics and Diagnostics services, and engage with users using the Push service. 开发人员还可以利用 Auth 对用户进行身份验证,利用数据服务在云中持久保存和同步应用数据。Developers can also leverage Auth to authenticate their users and Data service to persist and sync app data in the cloud. 立即查看 App CenterCheck out App Center today.

概述Overview

本教程介绍 Azure 应用服务 for iOS 的移动应用功能的脱机同步。This tutorial covers offline syncing with the Mobile Apps feature of Azure App Service for iOS. 通过脱机同步功能,最终用户可在无网络连接的情况下与移动应用进行交互,以查看、添加或修改数据。With offline syncing end-users can interact with a mobile app to view, add, or modify data, even when they have no network connection. 更改存储在本地数据库中。Changes are stored in a local database. 设备重新联机后,这些更改会与远程后端同步。After the device is back online, the changes are synced with the remote back end.

对于首次体验“移动应用”的用户,请先完成创建 iOS 应用教程。If this is your first experience with Mobile Apps, you should first complete the tutorial Create an iOS App. 如果不使用下载的快速入门服务器项目,则必须将数据访问扩展包添加到项目。If you do not use the downloaded quick-start server project, you must add the data-access extension packages to your project. 有关服务器扩展包的详细信息,请参阅使用适用于 Azure 移动应用的 .NET 后端服务器 SDKFor more information about server extension packages, see Work with the .NET backend server SDK for Azure Mobile Apps.

若要了解有关脱机同步功能的详细信息,请参阅移动应用中的脱机数据同步To learn more about the offline sync feature, see Offline Data Sync in Mobile Apps.

查看客户端同步代码Review the client sync code

创建 iOS 应用 教程下载的客户端项目已包含使用基于核心数据的本地数据库支持脱机同步的代码。The client project that you downloaded for the Create an iOS App tutorial already contains code that supports offline synchronization using a local Core Data-based database. 本部分汇总了教程代码中已包含的内容。This section summarizes what is already included in the tutorial code. 有关该功能的概念性概述,请参阅移动应用中的脱机数据同步For a conceptual overview of the feature, see Offline Data Sync in Mobile Apps.

使用移动应用的脱机数据同步功能,即使网络不可访问,最终用户也能够与本地数据库交互。Using the offline data-sync feature of Mobile Apps, end-users can interact with a local database even when the network is inaccessible. 要在应用中使用这些功能,可以初始化 MSClient 的同步上下文,并引用本机存储。To use these features in your app, you initialize the sync context of MSClient and reference a local store. 然后通过 MSSyncTable 接口引用表。Then you reference your table through the MSSyncTable interface.

在 QSTodoService.m (Objective-C) 或 ToDoTableViewController.swift (Swift) 中,请注意成员 syncTable 的类型为 MSSyncTable 。In QSTodoService.m (Objective-C) or ToDoTableViewController.swift (Swift), notice that the type of the member syncTable is MSSyncTable. 脱机同步使用此同步表接口而不是 MSTableOffline sync uses this sync table interface instead of MSTable. 使用同步表时,所有操作将转到本地存储,而且只会与具有显式推送和提取操作的远程后端同步。When a sync table is used, all operations go to the local store and are synchronized only with the remote back end with explicit push and pull operations.

若要获取对同步表的引用,请对 MSClient 使用 syncTableWithName 方法。To get a reference to a sync table, use the syncTableWithName method on MSClient. 若要删除脱机同步功能,请改用 tableWithNameTo remove offline sync functionality, use tableWithName instead.

表操作之前,必须初始化本地存储区。Before any table operations can be performed, the local store must be initialized. 下面是相关的代码:Here is the relevant code:

  • Objective-CObjective-C. QSTodoService.init 方法中:In the QSTodoService.init method:

    MSCoreDataStore *store = [[MSCoreDataStore alloc] initWithManagedObjectContext:context];
    self.client.syncContext = [[MSSyncContext alloc] initWithDelegate:nil dataSource:store callback:nil];
    
  • SwiftSwift. ToDoTableViewController.viewDidLoad 方法中:In the ToDoTableViewController.viewDidLoad method:

    let client = MSClient(applicationURLString: "http:// ...") // URI of the Mobile App
    let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
    self.store = MSCoreDataStore(managedObjectContext: managedObjectContext)
    client.syncContext = MSSyncContext(delegate: nil, dataSource: self.store, callback: nil)
    

    此方法使用移动应用 SDK 提供的接口 MSCoreDataStore 创建本地存储。This method creates a local store by using the MSCoreDataStore interface, which the Mobile Apps SDK provides. 或者,可以通过实现 MSSyncContextDataSource 协议提供其他本地存储。Alternatively, you can provide a different local store by implementing the MSSyncContextDataSource protocol. 此外, MSSyncContext 的第一个参数用于指定冲突处理程序。Also, the first parameter of MSSyncContext is used to specify a conflict handler. 由于已传递 nil,因此会获取默认的冲突处理程序,但该处理程序在发生任何冲突时会失败。Because we have passed nil, we get the default conflict handler, which fails on any conflict.

现在,让我们执行实际的同步操作,从远程后端获取数据:Now, let's perform the actual sync operation, and get data from the remote back end:

  • Objective-CObjective-C. syncData 先推送新的更改,然后调用 pullData 以便从远程后端获取数据。syncData first pushes new changes and then calls pullData to get data from the remote back end. 接下来, pullData 方法获取符合查询的新数据:In turn, the pullData method gets new data that matches a query:

    -(void)syncData:(QSCompletionBlock)completion
    {
         // Push all changes in the sync context, and then pull new data.
         [self.client.syncContext pushWithCompletion:^(NSError *error) {
             [self logErrorIfNotNil:error];
             [self pullData:completion];
         }];
    }
    
    -(void)pullData:(QSCompletionBlock)completion
    {
         MSQuery *query = [self.syncTable query];
    
         // Pulls data from the remote server into the local table.
         // We're pulling all items and filtering in the view.
         // Query ID is used for incremental sync.
         [self.syncTable pullWithQuery:query queryId:@"allTodoItems" completion:^(NSError *error) {
             [self logErrorIfNotNil:error];
    
             // Lets the caller know that we have finished.
             if (completion != nil) {
                 dispatch_async(dispatch_get_main_queue(), completion);
             }
         }];
    }
    
  • SwiftSwift:

    func onRefresh(sender: UIRefreshControl!) {
        UIApplication.sharedApplication().networkActivityIndicatorVisible = true
    
        self.table!.pullWithQuery(self.table?.query(), queryId: "AllRecords") {
            (error) -> Void in
    
            UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    
            if error != nil {
                // A real application would handle various errors like network conditions,
                // server conflicts, etc. via the MSSyncContextDelegate
                print("Error: \(error!.description)")
    
                // We will discard our changes and keep the server's copy for simplicity
                if let opErrors = error!.userInfo[MSErrorPushResultKey] as? Array<MSTableOperationError> {
                    for opError in opErrors {
                        print("Attempted operation to item \(opError.itemId)")
                        if (opError.operation == .Insert || opError.operation == .Delete) {
                            print("Insert/Delete, failed discarding changes")
                            opError.cancelOperationAndDiscardItemWithCompletion(nil)
                        } else {
                            print("Update failed, reverting to server's copy")
                            opError.cancelOperationAndUpdateItem(opError.serverItem!, completion: nil)
                        }
                    }
                }
            }
            self.refreshControl?.endRefreshing()
        }
    }
    

在 Objective-C 版本的 syncData中,首先在同步上下文中调用 pushWithCompletionIn the Objective-C version, in syncData, we first call pushWithCompletion on the sync context. 此方法是 MSSyncContext(而不是同步表本身)的成员,因为它会将更改推送到所有表。This method is a member of MSSyncContext (and not the sync table itself) because it pushes changes across all tables. 只有已在本地以某种方式修改(通过 CUD 操作来完成)的记录才会发送到服务器。Only records that have been modified in some way locally (through CUD operations) are sent to the server. 然后将调用 pullData 帮助程序,后者再调用 MSSyncTable.pullWithQuery 以检索远程数据并将其存储在本地数据库中。Then the helper pullData is called, which calls MSSyncTable.pullWithQuery to retrieve remote data and store it in the local database.

在 Swift 版本中,因为推送操作不是必需的,所以没有调用 pushWithCompletionIn the Swift version, because the push operation was not strictly necessary, there is no call to pushWithCompletion. 如果同步上下文中正在进行推送操作的表存在任何挂起的更改,则提取始终会先发出推送。If there are any changes pending in the sync context for the table that is doing a push operation, pull always issues a push first. 但是,如果有多个同步表,则最好是显式调用推送,以确保所有内容在相关表中保持一致。However, if you have more than one sync table, it is best to explicitly call push to ensure that everything is consistent across related tables.

在 Objective-C 和 Swift 版本中,均可以使用 pullWithQuery 方法指定查询,筛选想要检索的记录。In both the Objective-C and Swift versions, you can use the pullWithQuery method to specify a query to filter the records you want to retrieve. 在本示例中,查询检索远程 TodoItem 表中的所有记录。In this example, the query retrieves all records in the remote TodoItem table.

pullWithQuery 的第二个参数是用于 增量同步的查询 ID。增量同步使用记录的 UpdatedAt 时间戳(在本地存储中称为 updatedAt)仅检索自上次同步以来修改的记录。查询 ID 应对于应用程序中的每个逻辑查询都是唯一的描述性字符串。The second parameter of pullWithQuery is a query ID that is used for incremental sync. Incremental sync retrieves only records that were modified since the last sync, using the record's UpdatedAt time stamp (called updatedAt in the local store.) The query ID should be a descriptive string that is unique for each logical query in your app. 若选择不使用增量同步,请传递 nil 作为查询 ID。To opt out of incremental sync, pass nil as the query ID. 此方法的效率可能较低,因为它检索每个提取操作的所有记录。This approach can be potentially inefficient, because it retrieves all records on each pull operation.

在修改或添加数据、用户执行刷新手势和启动时,Objective-C 应用将进行同步。The Objective-C app syncs when you modify or add data, when a user performs the refresh gesture, and on launch.

当用户执行刷新手势和启动时,Swift 应用将进行同步。The Swift app syncs when the user performs the refresh gesture and on launch.

由于每当修改数据 (Objective-C) 或启动应用(Objective-C 和 Swift)时应用就会同步,因此,应用假设用户已联机。Because the app syncs whenever data is modified (Objective-C) or whenever the app starts (Objective-C and Swift), the app assumes that the user is online. 在后面的章节中,我们更新应用,以便用户即使在脱机时也能进行编辑。In a later section, you will update the app so that users can edit even when they are offline.

查看 Core Data 模型Review the Core Data model

在使用核心数据脱机存储时,必须在数据模型中定义特定的表和字段。When you use the Core Data offline store, you must define particular tables and fields in your data model. 示例应用已包含具有正确格式的数据模型。The sample app already includes a data model with the right format. 在本部分中,我们会逐步介绍这些表以便说明其用法。In this section, we walk through these tables to show how they are used.

打开 QSDataModel.xcdatamodeldOpen QSDataModel.xcdatamodeld. 已定义四个表,其中三个由 SDK 使用,还有一个供待办事项本身使用:Four tables are defined--three that are used by the SDK and one that's used for the to-do items themselves:

  • MS_TableOperations:跟踪需要与服务器同步的项。MS_TableOperations: Tracks the items that need to be synchronized with the server.
  • MS_TableOperationErrors:跟踪脱机同步期间发生的任何错误。MS_TableOperationErrors: Tracks any errors that happen during offline synchronization.
  • MS_TableConfig:跟踪所有提取操作最后一次同步操作的上次更新时间。MS_TableConfig: Tracks the last updated time for the last sync operation for all pull operations.
  • TodoItem:存储待办事项。TodoItem: Stores the to-do items. 系统列 createdAt、updatedAt 和 version 都是可选的系统属性 。The system columns createdAt, updatedAt, and version are optional system properties.

Note

移动应用 SDK 会保留以“`` ”开头的列名称。The Mobile Apps SDK reserves column names that begin with "``". 请不要在系统列以外的其他列中使用此前缀。Do not use this prefix with anything other than system columns. 否则,列名称会在使用远程后端时被修改。Otherwise, your column names are modified when you use the remote back end.

当使用脱机同步功能时,请定义三个系统表和一个数据表。When you use the offline sync feature, define the three system tables and the data table.

系统表System tables

MS_TableOperations MS_TableOperations

MS_TableOperations 表属性

属性Attribute 类型Type
idid Integer 64Integer 64
itemIditemId StringString
propertiesproperties 二进制数据Binary Data
table StringString
tableKindtableKind 16 位整数Integer 16

MS_TableOperationErrors MS_TableOperationErrors

MS_TableOperationErrors 表属性

属性Attribute 类型Type
idid StringString
operationIdoperationId 64 位整数Integer 64
propertiesproperties 二进制数据Binary Data
tableKindtableKind 16 位整数Integer 16

MS_TableConfig MS_TableConfig

属性Attribute 类型Type
idid StringString
keykey StringString
keyTypekeyType Integer 64Integer 64
table StringString
valuevalue 字符串String

数据表Data table

TodoItemTodoItem

属性Attribute 类型Type 注意Note
idid 字符串(标记为必需)String, marked required 远程存储中的主键Primary key in remote store
completecomplete 布尔Boolean 待办项字段To-do item field
texttext StringString 待办项字段To-do item field
createdAtcreatedAt DateDate (可选)映射到 createdAt 系统属性 (optional) Maps to createdAt system property
updatedAtupdatedAt DateDate (可选)映射到 updatedAt 系统属性 (optional) Maps to updatedAt system property
版本version StringString (可选)用于检测冲突,映射到版本(optional) Used to detect conflicts, maps to version

更改应用的同步行为Change the sync behavior of the app

本节将修改应用,使其在启动应用或插入和更新项时不进行同步。In this section, you modify the app so that it does not sync on app start or when you insert and update items. 仅在按下刷新手势按钮时进行同步。It syncs only when the refresh gesture button is performed.

Objective-CObjective-C:

  1. 在 QSTodoListViewController.m 中更改 viewDidLoad 方法,删除方法末尾对 [self refresh] 的调用 。In QSTodoListViewController.m, change the viewDidLoad method to remove the call to [self refresh] at the end of the method. 现在,数据将不会在应用启动时与服务器同步。Now the data is not synced with the server on app start. 相反,它将与本地存储的内容同步。Instead, it's synced with the contents of the local store.

  2. 在 QSTodoService.m 中修改 addItem 的定义,使其不会在插入项后同步 。In QSTodoService.m, modify the definition of addItem so that it doesn't sync after the item is inserted. 删除 self syncData 块并将它替换为以下内容:Remove the self syncData block and replace it with the following:

    if (completion != nil) {
        dispatch_async(dispatch_get_main_queue(), completion);
    }
    
  3. 修改前面提到的 completeItem 的定义。Modify the definition of completeItem as mentioned previously. 删除 self syncData 块并将它替换为以下内容:Remove the block for self syncData and replace it with the following:

    if (completion != nil) {
        dispatch_async(dispatch_get_main_queue(), completion);
    }
    

SwiftSwift:

在 ToDoTableViewController.swift 中的 viewDidLoad 内,注释掉以下两行,以停止在应用启动时同步。In viewDidLoad, in ToDoTableViewController.swift, comment out the two lines shown here, to stop syncing on app start. 在编写本文时,如果有人添加或完成某个项,Swift Todo 应用不会更新服务。At the time of this writing, the Swift Todo app does not update the service when someone adds or completes an item. 仅在应用启动时更新服务。It updates the service only on app start.

self.refreshControl?.beginRefreshing()
self.onRefresh(self.refreshControl)

测试应用程序Test the app

本节会连接到无效的 URL,以模拟脱机场景。In this section, you connect to an invalid URL to simulate an offline scenario. 添加数据项时,数据项保存在本地核心数据存储中,而不与移动应用后端进行同步。When you add data items, they're held in the local Core Data store, but they're not synced with the mobile-app back end.

  1. QSTodoService.m 中的移动应用 URL 更改为无效 URL,再次运行该应用:Change the mobile-app URL in QSTodoService.m to an invalid URL, and run the app again:

    Objective-CObjective-C. 在 QSTodoService.m 中:In QSTodoService.m:

    self.client = [MSClient clientWithApplicationURLString:@"https://sitename.chinacloudsites.cn.fail"];
    

    SwiftSwift. 在 ToDoTableViewController.swift 中:In ToDoTableViewController.swift:

    let client = MSClient(applicationURLString: "https://sitename.chinacloudsites.cn.fail")
    
  2. 添加一些待办事项。Add some to-do items. 退出模拟器(或强行关闭应用),然后重启。Quit the simulator (or forcibly close the app), and then restart it. 验证更改是否已保存。Verify that your changes persist.

  3. 查看远程 TodoItem 表的内容:View the contents of the remote TodoItem table:

    • 对于 Node.js 后端,请转到 Azure 门户,在移动应用后端中单击“简易表” > “TodoItem” 。For a Node.js back end, go to the Azure portal and, in your mobile-app back end, click Easy Tables > TodoItem.
    • 对于 .NET 后端,请使用 SQL 工具(如 SQL Server Management Studio)或 REST 客户端(如 Fiddler 或 Postman)。For a .NET back end, use either a SQL tool, such as SQL Server Management Studio, or a REST client, such as Fiddler or Postman.
  4. 验证新项是否 同步到服务器。Verify that the new items have not been synced with the server.

  5. QSTodoService.m中的 URL 更改回正确的 URL,并重新运行应用。Change the URL back to the correct one in QSTodoService.m, and rerun the app.

  6. 通过下拉项列表来执行刷新手势。Perform the refresh gesture by pulling down the list of items.
    此时显示进度盘。A progress spinner is displayed.

  7. 再次查看 TodoItem 数据。View the TodoItem data again. 现在应显示新项和已更改的待办事项。The new and changed to-do items should now be displayed.

摘要Summary

为了支持脱机同步功能,我们使用了 MSSyncTable 接口,并使用本地存储初始化了 MSClient.syncContextTo support the offline sync feature, we used the MSSyncTable interface and initialized MSClient.syncContext with a local store. 在这种情况下,本地存储是基于核心数据的数据库。In this case, the local store was a Core Data-based database.

使用核心数据本地存储时,必须使用 正确的系统属性定义多个表。When you use a Core Data local store, you must define several tables with the correct system properties.

移动应用的常规创建、读取、更新和删除 (CRUD) 操作执行起来就像此应用仍处于连接状态一样,但所有操作都针对本地存储进行。The normal create, read, update, and delete (CRUD) operations for mobile apps work as if the app is still connected, but all the operations occur against the local store.

将本地存储与服务器进行同步时,使用 MSSyncTable.pullWithQuery 方法。When we synchronized the local store with the server, we used the MSSyncTable.pullWithQuery method.

其他资源Additional resources