みなさんこんにちは。salesforce.comのやすのです。Swifting Mobile SDKの第3回目です。前回まではMobile SDKの導入的な内容に関して書かせて頂きました。今回からは少しずつ実践的な内容に入って行きたいと思います。今回は手始めに「クエリ」と「アップデート」を取り上げ、実際にsalesforce組織のデータを更新してみたいと思います。前回に引き続きできるだけシンプルなアプリを利用して説明していきたいと思いますので、みなさんも是非手を動かして作成してみてください。
今回作成するアプリケーション
今回作成するアプリケーションは取引先を検索し、指定した取引先を表示&更新するシナリオです。このようなレコードの検索、表示および更新はよくあるシナリオになりますので、この機会に是非覚えてください。
※このアプリケーションのソースコードはGithubで公開していますので、こちらからダウンロードしてください。
アプリケーションの主要な構成は次のようになっています。
- 取引先検索画面 (AccountSearchViewController.swift)
- 取引先情報表示画面 (AccountDetailViewController.swift)
- 取引先情報更新画面 (AccountEditViewController.swift)
1.のAccountSearchViewController.swiftはユーザが入力した値をもとに取引先に対してクエリを実行し、その結果をテーブルに表示します。2.のAccountDetailViewController.swiftは検索画面で選択されたレコードの項目を表示します。更新画面で編集後に再度レコードの値を取得し画面に表示される取引先を情報をリフレッシュする役割もあります。3.のAccountEditViewController.swiftは選択された取引先レコードの情報を編集し更新をかけます。
ではここからMobile SDKをどのように利用するか詳細を見ていきましょう。
事前知識
今回紹介する内容は「Blocks」を多用しています。「Blocks」を使われた事ない方はiOS Developer Library の Getting Started with Blocksを一読頂く事をおすすめ致します。
SFRestAPI
クエリ、アップデート、レコードの作成等様々な操作をSFRestAPIクラスのインスタンスを介して行う事となります。このSFRestAPIクラスを利用することでメソッドを呼ぶだけで簡単にクエリ、アップデート等を行う事ができます。実際にSFRestAPIクラスがどんなメソッドを持っているかはこちらのリファレンス中のSFRestAPIを確認ください。また、SFRestAPI+xxでクラス拡張が行われています。今回はSFRestAPI+Blocksで拡張されたメソッドを利用していますので、こちらも詳細をリファレンスでご確認ください。
SFRestAPIを利用するとsalesforceのデータを操作できるということはわかりました。では、このSFRestAPIクラスのインスタンスは一体どのように作成すればよいのでしょか?実は自分でインスタンスを作成する事は必要なく、Mobile SDKを利用してOAuth認可をするとSFRestAPIのインスタンスが勝手に作成されます。ログインが完了した後に下記のクラスメソッドを呼ぶと簡単にインスタンスを取得できるようになっています。
SFRestAPI.sharedInstance()
クエリ
ここからクエリを見て行きましょう。下記のコードはAccountSearchViewController.swift中のメソッドです。
func searchAccounts(partOfName: String) {
let soql = "SELECT Id,Name,BillingAddress,BillingCity,BillingCountry,BillingPostalCode,BillingState,BillingStreet FROM Account WHERE Name LIKE '%\(partOfName)%'"
println(soql);
SFRestAPI.sharedInstance().performSOQLQuery(soql,
failBlock: {(error : NSError!) in
var alert = UIAlertController(title: "エラー", message: error.localizedDescription, preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default, handler: {(action : UIAlertAction!) in
alert.dismissViewControllerAnimated(true, completion: nil)
})
alert.addAction(okAction)
self.presentViewController(alert, animated: true, completion: nil)
},
completeBlock: {(responseData : [NSObject:AnyObject]!) in
self.accounts = responseData["records"] as? Array
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
)
}
下記のコードに着目してください。先ほど紹介したSFRestAPI.sharedInstance()でインスタンスを取得してperformSOQLQueryメソッドを利用しています。引数にはSOQL(文字列)と成功および失敗した際に呼び出される関数を渡しています(ブロックを利用)。
SFRestAPI.sharedInstance().performSOQLQuery(soql,
failBlock: {(error : NSError!) in
...
},
completeBlock: {(responseData : [NSObject:AnyObject]!) in
...
}
)
failBlockではエラーハンドリングを行い、completeBlockでは成功時の処理を行います。completeBlock中のresponseData : [NSObject:AnyObject]!の引数はリクエストに対するレスポンスです。SOQLを指定したリクエストでは下記のようにして簡単にクエリの結果を取得する事ができます。
responseData["records"] as? Array
もしresponseDataの中身を確認したければ、ブロック中の適当な箇所でブレークポイントを置いて、以下のコマンドをコンソール中で入力し実行してみてください。
po responseData
このコマンドでresponseDataの中身をコンソール中に表示してくれます。
レコード情報の取得
IDを指定してある特定のレコードの情報を取得したい時があります。こういった場合も簡単にメソッドを呼ぶだけで実行できます。下記のコードを見て行きましょう。これはAccountDetailViewController.swift中のメソッドです。
func refresh() {
if self.account == nil { return }
SFRestAPI.sharedInstance().performRetrieveWithObjectType("Account",
objectId: self.account!["Id"] as! String,
fieldList: ["Id", "Name", "BillingPostalCode", "BillingState", "BillingCity", "BillingStreet"],
failBlock: { ( error : NSError!) in
dispatch_async(dispatch_get_main_queue(), {
let alert = UIAlertController(
title: "エラー",
message: error.localizedDescription,
preferredStyle: .Alert
)
let okAction = UIAlertAction(
title: "OK",
style: .Default,
handler: { ( action : UIAlertAction! ) in
alert.dismissViewControllerAnimated(true, completion: nil)
}
)
self.presentViewController(alert, animated: true, completion: nil)
})
},
completeBlock: {( response : [NSObject:AnyObject]!) in
self.account = response as? [NSString:AnyObject]
dispatch_async(dispatch_get_main_queue(), {
self.loadAccountData()
})
}
)
}
下記の部分に着目してください。オブジェクト名(この場合はAccount)、取得するレコードのID(objectId)、取得対象の項目(filedListにて配列として指定)を指定するだけで簡単に特定のレコードの値を取得する事ができます。
SFRestAPI.sharedInstance().performRetrieveWithObjectType("Account",
objectId: self.account!["Id"] as! String,
fieldList: ["Id", "Name", "BillingPostalCode", "BillingState", "BillingCity", "BillingStreet"],
failBlock: { ( error : NSError!) in
...
},
completeBlock: {( response : [NSObject:AnyObject]!) in
...
}
)
}
アップデード
アップデートもほぼ同じような流れになっています。早速コードを見て行きましょう。こちらはAccountEditViewController.swift中のメソッドです。
private func updateAccount() {
if self.account == nil { return }
let fields = [ "Name": "\(self.name.text)", "BillingPostalCode": "\(self.postalCode.text)", "BillingState": "\(self.billingState.text)", "BillingCity": "\(self.billingCity.text)", "BillingStreet": "\(self.billingStreet.text)"]
SFRestAPI.sharedInstance().performUpdateWithObjectType("Account",
objectId: self.account!["Id"] as! String,
fields: fields,
failBlock: {( error : NSError! ) in
dispatch_async(dispatch_get_main_queue(), {
let alert = UIAlertController(
title: "エラー",
message: error.localizedDescription,
preferredStyle: .Alert
)
let okAction = UIAlertAction(
title: "OK",
style: .Default,
handler: { ( action : UIAlertAction! ) in
alert.dismissViewControllerAnimated(true, completion: nil)
}
)
self.presentViewController(alert, animated: true, completion: nil)
})
},
completeBlock: { ( response : [NSObject:AnyObject]! ) in
dispatch_async(dispatch_get_main_queue(), {
if self.navigationController != nil {
let detailVC = self.navigationController!.viewControllers[self.navigationController!.viewControllers.count - 2] as? AccountDetailViewController
detailVC?.refresh()
self.navigationController?.popViewControllerAnimated(true)
}
})
}
)
}
まずfields変数に着目してください。こちらは更新するべき項目とその値が入ったDictionary型の変数となっています。
let fields = [ "Name": "\(self.name.text)", "BillingPostalCode": "\(self.postalCode.text)", "BillingState": "\(self.billingState.text)", "BillingCity": "\(self.billingCity.text)", "BillingStreet": "\(self.billingStreet.text)"]
上記のコードでは
- Name項目に対して、「name(テキストボックス)」のtextプロパティを
- BillingPostalCode項目に対して、「postalCode(テキストボックス)」のtextプロパティ
- BillingState項目に対して、「billingState(テキストボックス)」のtextプロパティ
- BillingCity項目に対して、「billingCity(テキストボックス)」のtextプロパティ
- BillingStreet項目に対して、「billingStreet(テキストボックス)」のtextプロパティ
をキーバリューの形で設定しています。
変更対象の項目を設定し終えたら、後はメソッドを下記のようにメソッドを呼ぶだけです。
SFRestAPI.sharedInstance().performUpdateWithObjectType("Account",
objectId: self.account!["Id"] as! String,
fields: fields,
failBlock: {( error : NSError! ) in
...
},
completeBlock: { ( response : [NSObject:AnyObject]! ) in
...
}
)
先ほどレコード情報の取得で紹介した内容とほぼ同じで「fields」で指定する内容がArrayからDictionaryへ変わっているだけですね。
おまけ
dispatch_async(dispatch_get_main_queue(), {
...
})
ブロック中によく上記の様なコードが出てきています。これはメインスレッドで実行してくださいと言っているだけで、特に特別なロジックではありません。(確認はしていませんが)レスポンス後に呼び出されるブロックはメインスレッドでは実行されていないように見受けられます。そのため上記のような形でコードを書いています。これを外して書いた場合にはどうなるでしょうか?是非試してみてください。AccountSearchViewController.swiftで試すと大きな違いがわかると思います。
おわりに
今回は少し実践に近づきクエリしたレコードを更新しました。意外と簡単と思われたのではないでしょうか?次回はもう少しレベルアップして、地図との連携を考えています。