使用【超微型】的AWS AppSync + Amplify来创建iOS聊天应用
首先
这篇文章是and factory Advent Calendar 2020 的第四篇文章。昨天是@ykkd先生的Swiftlint自动修正代码!?
亚马逊云服务 AppSync / Amplify
我认为在移动应用程序的后端中,使用Firebase作为聊天应用程序的后端很常见,但AWS的托管服务有一个重大优点,就是与其他AWS服务的集成很容易。
虽然我自己是iOS工程师,但我对AWS提供的服务和构建很感兴趣,所以我决定这次尝试使用AppSync和Amplify。
AppSync是亚马逊提供的一项托管服务,旨在支持使用GraphQL进行服务开发。通过使用GraphQL中提供的订阅功能,可以相对轻松地创建聊天功能。
此外,Amplify是为客户端访问AppSync而提供的工具。通过在iOS中使用Amplify SDK,SDK将负责处理与AppSync之间的数据传递。

这次我们将使用AWS AppSync + Amplify来快速创建一个超级简约的聊天应用程序。
放大器的设置
前提条件 tí
-
- AWSアカウントを持っている
-
- Amplifyのセットアップが完了している
Amplifyを実行するコマンドラインツールのセットアップ、Podのインストールを行います。
詳細はこちらを参照ください。
在Amplify的命令行工具中,执行以下操作。
Amplify的初始化
amplify init
之后会被询问项目名称等初始设置,我会进行回答。这次我回答如下。
? Enter a name for the project
-> SampleChatApp
? Enter a name for the environment
-> dev
? Choose your default editor:
-> Visual Studio Code
? Choose the type of app that you're building
-> ios
? Do you want to use an AWS profile?
-> Yes
? Please choose the profile you want to use
-> default
✅ 放大设置成功完成。
API的设置
当你完成amplify的设置后,我们将使用命令行工具来设置API。
在项目根目录下执行以下命令,并根据确认内容逐步回答。
amplify add api
再次被問到。接下來是有關API的初設定。這次案例的答案如下所示。
? Please select from one of the below mentioned services:
-> GraphQL
? Provide API name:
-> samplechatapp
? Choose the default authorization type for the API
-> API key
? Enter a description for the API key:
-> SampleChatApp's API key.
? After how many days from now the API key should expire (1-365):
-> 7
? Do you want to configure advanced settings for the GraphQL API
-> No, I am done.
? Do you have an annotated GraphQL schema?
-> No
? Choose a schema template:
-> Single object with fields (e.g., “Todo” with ID, name, description)
GraphQL schema compiled successfully.
? Do you want to edit the schema now?
-> Yes // Yesとするとエディタが開き、スキーマを編集できる
模型定义
当回答最后一个关于创建API的问题回答为是后,编辑器将打开,以便可以编辑模式。
通过GraphQL,我们根据graphql文件中定义的模式来创建API。
在这里,我们简单地定义了以下消息模型:
它具有文本、创建日期(以毫秒级的纪元时间为基础)和最小限度的UserID属性。
type Message @model {
id: ID!
text: String!
createdAt: String!
user: String!
}
定义完模式之后,将之前创建的模式以及API的定义文件等本地资源推送到远程。
amplify push
一旦成功推送后,通常会有问题被提出。
基于这个回答,将决定用于访问基于该API创建的封装处理的类型和命名。
? Do you want to generate code for your newly created GraphQL API
-> Yes
? Enter the file name pattern of graphql queries, mutations and subscriptions
-> graphql/**/*.graphql
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscripti
ons
-> Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested]
-> 2
? Enter the file name for the generated code
-> API.swift
执行到这一步,您可以使用Amplify的命令行工具创建API,
然后您可以在AWS控制台->服务->AppSync中进行确认。

客户端实施
安装
将在amplify init时创建的两个json文件,即amplifyconfiguration.json和awsconfiguration.json,转移到Xcode项目中。
此外,在应用启动时,作为Amplify的设置过程,AppDelegate将执行以下操作。
import UIKit
import Amplify
import AmplifyPlugins
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Setup Amplify
do {
try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))
try Amplify.configure()
} catch {
print("An error occurred setting up Amplify: \(error)")
}
// 略
return true
}
}
实施聊天功能
这是聊天功能的实施。
在viewDidLoad中获取保存在数据源中的消息
使用GraphQL的REST API的Get请求由query负责。利用Amplify SDK,我们可以执行Amplify.API.query(request:)来获取Message的数组。此外,通过结合指定limit和包含下一个值的nextToken,还可以执行分页操作。
@IBOutlet private weak var tableView: UITableView!
var messages: [Message] = []
override func viewDidLoad() {
super.viewDidLoad()
self.fetchMessage()
}
func fetchMessage() {
// Amplify SDK経由でqueryオペレーションを実行しMessageの配列を取得
Amplify.API.query(request: .list(Message.self, where: nil)) { event in
switch event {
case .success(let result):
// GraphQLの場合、Query失敗時のerrorもレスポンスに含まれる
switch result {
case .success(let messages):
self.messages = messages
DispatchQueue.main.async {
// tableViewを更新
self.tableView.reloadData()
}
case .failure(let error):
// サーバーから返されるエラーはこっち
}
case .failure(let error):
// 通信エラー等の場合はこっち
}
}
}
通过点击发送按钮来提交数据。
在GraphQL中,创建、修改等数据写入操作由mutate执行。
@IBAction func tappedSendButton() {
// キーボード閉じる
self.textField.resignFirstResponder()
// メッセージ内容
guard let text = self.textField.text, !text.isEmpty else {
return
}
// 送信時間を取得
let createdAt = String(Date().timeIntervalSince1970)
// 別管理しているUserID
let user = UserIdRepositoryProvider.provide().getUserId()
let message = Message(text: text, ts: ts, user: user!)
// mutateで新規メッセージを作成
Amplify.API.mutate(request: .create(message)) { event in
switch event {
case .success(let result):
switch result {
case .success(let message):
print("Successfully created the message: \(message)")
case .failure(let graphQLError):
// サーバーからのエラーの場合はこっち
print("Failed to create graphql \(graphQLError)")
}
case .failure(let apiError):
// 通信まわりなどのErrorになった場合はこっち
print("Failed to create a message", apiError)
}
}
// 初期化しておく
self.textField.text = ""
}
订阅数据源
最后,我们将实现实时结果的反映。
通过使用GraphQL的订阅功能,可以实现双向的套接字通信。
在Amplify中,通过执行Amplify.API.subscribe(),可以使数据源的更改能够响应式地反映出来。
override func viewDidLoad() {
super.viewDidLoad()
self.fetchMessage()
self.subscribeMessage()
}
func subscribeMessage() {
// 新たなメッセージの作成を購読する
subscription = Amplify.API.subscribe(request: .subscription(of: Message.self, type: .onCreate), valueListener: { (subscriptionEvent) in
// 購読したイベント内容をチェック
switch subscriptionEvent {
// サブスクリプションの接続状態の変更を検知
case .connection(let subscriptionConnectionState):
print("Subscription connect state is \(subscriptionConnectionState)")
// データの更新を検知
case .data(let result):
switch result {
case .success(let createdMessage):
self.messages.append(createdMessage)
DispatchQueue.main.async {
// テーブル更新
self.tableView.reloadData()
// 最新のメッセージまでスクロール
let indexPath = IndexPath(row: self.messages.count - 1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
case .failure(let error):
print("Got failed result with \(error.errorDescription)")
}
}
}) { result in
switch result {
case .success:
print("Subscription has been closed successfully")
case .failure(let apiError):
print("Subscription has terminated with \(apiError)")
}
}
}
暂时完成
好像做得很不错啊~

做不完的事情
虽然给人的感觉似乎有一些进展,但实际上这次排序并未成功。
(通过订阅可以按时间顺序获取内容,所以可以应付,但通过查询获取的内容会按照Key中的Message ID排序,导致顺序混乱)

如果是在聊天中,自然希望按照时间顺序排列,但由于超简约的原因,请谅解。
(虽然可以通过修改schema.graphql并将createdAt指定为排序键,但对于通过Amplify SDK间接调用该语句,我不清楚应该如何调用它。如果有人有相关经验,请告诉我。)
最后
本文中完全没有提到,但是关于聊天功能
在本文中没有提到的是,关于聊天功能
-
- 認証情報との紐付け
-
- 送信開始時点で送信中というステータスがユーザーに伝わるようにする
-
- 送信に失敗したときにユーザーに通知して再送信を促す
-
- 送信中にアプリを落としても送信が行われるようにする
-
- 画像や動画などコンテンツの拡充
- データの永続化をしてユーザービリティを高める
考虑到这些事情,越来越多的审查项目浮现出来,
我觉得要将多少责任交给SDK真是一个困难的问题。
虽然如此,关于Amplify iOS SDK,目前仍然处于调查阶段,
我还希望能够通过进一步的调试来加深我的理解。
非常感谢您一直坚持观看直到最后?♂️