
Xcodeからバックエンドを作る!Amplify iOSを試してみた #reinvent
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
Amplify iOS
Amplify iOSおよびAmplify Androidは、AWS re:Invent 2019で発表されたAmplify Frameworkの新しいツール・ライブラリです。現在はプレビュー版として提供されています。
本記事ではGetting Startedに沿ってiOSアプリを作ってみました。
サンプルコードをGitHubで公開しましたので、合わせてご確認いただけますと幸いです。
インストール
Xcodeプロジェクト、およびCocoaPodsはすでに作成・導入済みの前提で進みます。本記事では AmplifySample という名前のXcodeプロジェクトを作成しました。
まずはXcodeプロジェクトのディレクトリを開き Podfile を作成します。
$ cd ./AmplifySample $ pod init
Podfile は以下のようにします。
target 'AmplifySample' do use_frameworks! # Pods for AmplifySample pod 'amplify-tools' pod 'Amplify' pod 'AWSPluginsCore' pod 'AmplifyPlugins/AWSAPIPlugin' end
pod install を実行し、各ライブラリをインストールします。
$ pod install --repo-update
Xcodeワークスペースが作成されます AmplifySample.xcworkspace を開きビルドを行います。ビルドは Command + B で行えます。
ビルドすると下図のように3つの設定ファイルが生成されます。
それぞれ次のような設定が含まれるようになります。
awsconfiguration.jsonとamplifyconfiguration.json: AmplifyとしてAWSの各リソースを作る際の設定ファイルamplifyxc.config: XcodeからAmplify Frameworkの操作を行う際の設定ファイル
モデルの作成
Amplify iOSでは、デフォルトでGraphQL (AppSync)と通信することでデータを取得したりする前提で各設定が行われています。
amplify/backend/api/amplifyDatasource/schema.graphql にファイルが作成されており、確認してみると下記のようなGraphQLスキーマが作られています。
type Task @model {
  id: ID!
  title: String!
  description: String
  status: String
}
type Note @model {
  id: ID!
  content: String!
}
このGraphQL Schemaを元に、モデルクラスを作ってみます。モデルクラスを作る場合は amplifyxc.config の設定を変更し modelgen=true にします。
push=false modelgen=true profile=default envName=amplify
ビルドすると次のファイルが amplify/generated/models に生成されます。
AmplifyModels.swiftNote.swiftNote+Schema.swiftTask.swiftTask+Schema.swift
生成したファイルはXcode ProjectのTarget内に追加します。
最後にビルドしておきます。
APIとDatabaseの追加
push=true modelgen=true profile=default envName=amplify
profile は ~/.aws/config に設定してあるProfileを利用することが可能です。しかしMFAには対応していないようなのでご留意ください。MFAが不要なIAM Userで実行可能です。
ビルドを実行するとAWSアカウント上に各種リソースが作成されます。awsconfiguration.json と amplifyconfiguration.json に設定が増えていることが確認できます。
{
    "UserAgent": "aws-amplify/cli",
    "Version": "0.1.0",
    "IdentityManager": {
        "Default": {}
    },
    "AppSync": {
        "Default": {
            "ApiUrl": "https://<api id>.appsync-api.ap-northeast-1.amazonaws.com/graphql",
            "Region": "ap-northeast-1",
            "AuthMode": "API_KEY",
            "ApiKey": "<api key>",
            "ClientDatabasePrefix": "amplifyDatasource_API_KEY"
        }
    }
}
{
    "api": {
        "plugins": {
            "awsAPIPlugin": {
                "amplifyDatasource": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://<api id>.appsync-api.ap-northeast-1.amazonaws.com/graphql",
                    "region": "ap-northeast-1",
                    "authorizationType": "API_KEY",
                    "apiKey": "<api key>"
                }
            }
        }
    }
}
APIを利用したアプリの実装
ここまででAPIの用意ができたので、アプリにAPIを呼び出す処理を実装します。
AppDelegate での初期化
AppDelegate.swift の application:didFinishLaunchingWithOptions: メソッド内に次の処理を追加します。
import UIKit
import Amplify
import AmplifyPlugins
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let apiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels())
        do {
            try Amplify.add(plugin: apiPlugin)
            try Amplify.configure()
            print("Amplify initialized")
        } catch {
            print("Failed to configure Amplify \(error)")
        }
        return true
    }
}
View Controllerで書き込み/読み込みを試す
次にView Controller内でAPIに対してのデータの書き込み/読み込みを試していきたいと思います。
まずView Controllerでデータの確認ができるよう、下図のようにViewを配置します。
続いてView Controllerの実装です。以下のように実装しました。
import UIKit
import Amplify
class ViewController: UIViewController {
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var tableView: UITableView!
    var list = [Note]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        query()
        subscribe()
    }
    
    @IBAction func postButtonDidTouch(_ sender: Any) {
        let content = textField.text ?? ""
        let note = Note(content: content)
        _ = Amplify.API.mutate(of: note, type: .create) { (event) in
            switch event {
            case .completed(let result):
                switch result {
                case .success(let note):
                    print("API Mutate successful, created note: \(note)")
                    DispatchQueue.main.async {
                        self.textField.text = nil
                    }
                case .failure(let error):
                    print("Completed with error: \(error.errorDescription)")
                }
            case .failed(let error):
                print("Failed with error \(error.errorDescription)")
            default:
                print("Unexpected event")
            }
        }
    }
    
    func query() {
        _ = Amplify.API.query(from: Note.self, where: nil) { (event) in
            switch event {
            case .completed(let result):
                switch result {
                case .success(let notes):
                    print("Successfully retrieved list of notes: \(notes)")
                    self.list = notes
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                case .failure(let error):
                    print("Got failed result with \(error.errorDescription)")
                }
            case .failed(let error):
                print("Got failed event with error \(error)")
            default:
                print("Should never happen")
            }
        }
    }
    
    func subscribe() {
        _ = Amplify.API.subscribe(from: Note.self, type: .onCreate) { (event) in
            switch event {
            case .inProcess(let subscriptionEvent):
                switch subscriptionEvent {
                case .connection(let subscriptionConnectionState):
                    print("Subsription connect state is \(subscriptionConnectionState)")
                case .data(let result):
                    switch result {
                    case .success(let note):
                        print("Successfully got todo from subscription: \(note)")
                        self.list.append(note)
                        DispatchQueue.main.async {
                            self.tableView.reloadData()
                        }
                    case .failure(let error):
                        print("Got failed result with \(error.errorDescription)")
                    }
                }
            case .completed:
                print("Subscription has been closed")
            case .failed(let error):
                print("Got failed result with \(error.errorDescription)")
            default:
                print("Should never happen")
            }
        }
    }
}
extension ViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return list.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
        let note = list[indexPath.row]
        cell.textLabel?.text = note.content
        return cell
    }
    
}
上記の実装コードのポイントは下記の通りです。
- View Controllerが表示された際にSubscribeを開始
 - TextInputに入力し、Buttonで投稿をしたらAPIにリクエスト
 - 新しいデータが作成されたらSubscribeイベントが到着
 - 新しいデータをTableViewに反映
 
以上で完了です。
動かす
それではiOSアプリとして動かしてみます。Command + R でデバッグ起動します。何かしらを書き込んでみます。投稿するとクラウド上にデータが保存され、TableViewにアイテムが追加されます。
バックエンド側を簡単に確認してみます。CloudFormationを見てもらうと分かりますが、Amplify経由でAppSyncやDynamoDB、Cognito User Poolといったリソースが作成されています。
DynamoDBのNoteテーブルを確認してみると、先ほど追加したデータが格納されていることが分かります。
コマンドラインでの操作不要でAmplify Frameworkが使える!
Amplify FrameworkではAmplify CLIをコマンドラインで実行しなければAPIの作成などは行えませんでした。今回のAmplify iOSおよびAmplify AndroidではXcodeなどの統合開発環境(IDE)からAmplify Frameworkの操作が行えます。これはモバイルアプリ開発者にとっては非常に嬉しい限りですね!
他にもPredictionsなどの面白い機能があるので、試していきたいと思います。
















