[Parse][iOS] PFObjectの保存・更新・削除の基本
はじめに
前回の記事ではToDo リストアプリを作りました。その中でいくつか新キャラが登場していましたが、今回は PFObject について掘り下げようと思います。
PFObjectとは?
Parse.comのドキュメントによると PFObject は以下の特徴を持っているとのこと。
- JSON互換(キーと値のペア)
- スキーマレス(事前にキーとか定義しなくてもいいよ)
- あんたが勝手に作ったPFObjectをバックエンドに保存するよ
- キーは英数字の文字列で指定してね
- 値はJSONエンコードできるものなら何でもいいよ
- 文字列
- 数値
- ブール値
- 配列
- 辞書
- データを区別するためにクラス名を持つよ
- クラス名はアッパーキャメルケース(例:GameScoreとか)、キー名はローワーキャメルケース(playerName)がいいよ
ドキュメントに従い、以下のようなJSONオブジェクトを例に考えてみます。
{ "score" : 1337, "playerName" : "Sean Plott", "cheatMode" : false }
チートモード?なんだそれ?ってのは置いときまして、これをPFObjectで表すと、
PFObject *gameScore = [PFObject objectWithClassName:@"GameScore"]; gameScore[@"score"] = @1337; gameScore[@"playerName"] = @"Sean Plott"; gameScore[@"cheatMode"] = @NO;
となるらしいです。なんかちょっと制限のあるミュータブルな辞書っぽい感じで書けます。
オブジェクトを保存する
で、上記のように勝手に作ったオブジェクトを保存するには、以下のメソッドを使用します。
- - (BOOL)save
- 同期的に保存処理を行う。
- - (BOOL)save:(NSError **)error
- 同期的に保存処理を行う。エラー付き。
- - (void)saveInBackground
- 非同期で保存処理を行う。
- - (void)saveInBackgroundWithBlock:(PFBooleanResultBlock)block
- 非同期で保存処理を行う。
保存処理が終了したら引数で指定したBlocksが実行される。 - - (void)saveInBackgroundWithTarget:(id)target selector:(SEL)selector
- 非同期で保存処理を行う。
保存処理が終了したらtargetで指定したインスタンスのselectorで指定したメソッドが実行される。 - - (void)saveEventually
- オフラインで保存処理を行う。(注!)
- - (void)saveEventually:(PFBooleanResultBlock)callback
- オフラインで保存処理を行う。(注!)
保存処理が終了したら引数で指定したBlocksが実行される。
たぶん一番多用するのは- (void)saveInBackgroundWithBlock:(PFBooleanResultBlock)blockメソッドな気がします。
PFObject *gameScore = [PFObject objectWithClassName:@"GameScore"]; gameScore[@"score"] = @1337; gameScore[@"playerName"] = @"Sean Plott"; gameScore[@"cheatMode"] = @NO; [todo saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { // HUD消したり・・・ if (!succeeded) { // エラー処理 return; } // 成功時の処理 }];
- saveEventuallyメソッド
オフラインで保存を行う。となっておりますが、オフライン時にsaveEventually系のメソッドを利用して保存処理を実行した場合、実際にサーバに反映されるのはアプリをリセット(メモリから消す)してから起動したときのようです。
なので、ToDo リストアプリのように一覧画面で都度最新のデータ取得するような作りの場合、オフライン時に保存したデータはすぐに反映されないようです。。。
IOS: saveEventually not saving eventually until after App reset! | Parse
saveAll
複数のオブジェクトを一気に保存することも可能です。その場合はsaveAll系のクラスメソッドを使います。
PFObject *gameScore1 = [PFObject objectWithClassName:@"GameScore"]; gameScore1[@"score"] = @1337; gameScore1[@"playerName"] = @"Sean Plott"; gameScore1[@"cheatMode"] = @NO; PFObject *gameScore2 = [PFObject objectWithClassName:@"GameScore"]; gameScore2[@"score"] = @99999; gameScore2[@"playerName"] = @"Yuki Beckham"; gameScore2[@"cheatMode"] = @YES; [PFObject saveAllInBackground:@[gameScore1, gameScore2] block:^(BOOL succeeded, NSError *error) { // HUD消したり・・・ if (!succeeded) { // エラー処理 return; } // 成功時の処理 }];
用意されているsaveAll系のクラスメソッドは以下の通り。
- + (BOOL)saveAll:(NSArray *)objects
- 同期的に保存処理を行う。
- + (BOOL)saveAll:(NSArray *)objects error:(NSError **)error
- 同期的に保存処理を行う。エラー付き。
- + (void)saveAllInBackground:(NSArray *)objects
- 非同期で保存処理を行う。
- + (void)saveAllInBackground:(NSArray *)objects block:(PFBooleanResultBlock)block
- 非同期で保存処理を行う。
保存処理が終了したら引数で指定したBlocksが実行される。 - + (void)saveAllInBackground:(NSArray *)objects target:(id)target selector:(SEL)selector
- 非同期で保存処理を行う。
保存処理が終了したらtargetで指定したインスタンスのselectorで指定したメソッドが実行される。
(saveAllEventuallyとかは無い!)
データタイプ
保存できる値の基本的なタイプは以下のものがあります。(File、Pointer、Relationについては後で)
タイプ | Parseでのタイプ | Objective-Cの型 | Objective-Cの例 |
---|---|---|---|
文字列 | String | NSString | @"もじれつ" |
数値 | Number | NSNumber | @100 |
ブール値 | Boolean | NSNumber | @YES |
日付 | Date | NSDate | [NSDate date] |
バイナリ | Bytes | NSData | [@"foo" dataUsingEncoding:NSUTF8StringEncoding] |
位置情報 | GeoPoint | PFGeoPoint | [PFGeoPoint geoPointWithLatitude:35.697239 longitude:139.774719] |
配列 | Array | NSArray | @[ @"item1", @"item2", @"item3" ] |
オブジェクト | Object | NSDictionary | @{ @"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3" } |
以下のプログラムを実行すると・・・
PFObject *bigObject = [PFObject objectWithClassName:@"BigObject"]; bigObject[@"myNumber"] = @100; bigObject[@"myString"] = @"もじれつ"; bigObject[@"myDate"] = [NSDate date]; bigObject[@"myData"] = [@"foo" dataUsingEncoding:NSUTF8StringEncoding]; bigObject[@"myGeoPoint"] = [PFGeoPoint geoPointWithLatitude:35.697239 longitude:139.774719]; bigObject[@"myArray"] = @[ @"item1", @"item2", @"item3" ]; bigObject[@"myDictionary"] = @{ @"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3" }; bigObject[@"myNull"] = [NSNull null]; [bigObject saveInBackground];
こうなります(myNull無くね?)↓
ちなみに、PFObjectのサイズ上限は128KBを超えちゃダメなので、画像とか文書を上記のバイナリで保存するのではなくPFFileを使用しろとのことです(PFFileは後で)。
PFObjectはスキーマレスだよ
ToDo リストアプリを見て分かるように、今のところダッシュボードではスキーマの定義とかやってません。このことから分かるようにParseではスキーマレスなデータを扱うようです。一般的なRDBMSのテーブル的なものはParse上ではクラスと呼ばれ、このクラスは初回のオブジェクト保存と同時に作成されます。
また、スキーマレスということなので、例えば以下のようなプログラムを書いても問題なく動作します。
// Todoクラスの初回のオブジェクト保存 PFObject *todo1 = [PFObject objectWithClassName:@"Todo"]; todo1[@"name"] = @"世界一の剣豪になる!"; [todo1 saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { ・・・ }]; // 新たにTodoクラスのオブジェクトを作成し、カラムを追加して保存 PFObject *todo2 = [PFObject objectWithClassName:@"Todo"]; todo2[@"name"] = @"実家に帰る。"; todo2[@"priority"] = @100; // <- New column! [todo2 saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { ・・・ }];
でも、こうすると・・・
// Todoクラスの初回のオブジェクト保存 PFObject *todo1 = [PFObject objectWithClassName:@"Todo"]; todo1[@"name"] = @"世界一の剣豪になる!"; [todo1 saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { ・・・ }]; // 新たにTodoクラスのオブジェクトを作成し、カラムを追加して保存 PFObject *todo2 = [PFObject objectWithClassName:@"Todo"]; todo2[@"name"] = @"実家に帰る。"; todo2[@"priority"] = @100; [todo2 saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { ・・・ }]; // NumberであるはずのpriorityにStringを入れる PFObject *todo3 = [PFObject objectWithClassName:@"Todo"]; todo3[@"name"] = @"両親にご飯をごちそうする。"; todo3[@"priority"] = @"High"; // <- 違うタイプのやつ [todo3 saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) { エラーになる!! }];
さすがにエラーになりますw
オブジェクトの更新
オブジェクトの更新にもsave系のメソッドを使います。
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"]; // IDを指定してオブジェクトを取得する [query getObjectInBackgroundWithId:@"xWMyZ4YEGZ" block:^(PFObject *gameScore, NSError *error) { // チートモードとスコアを更新する gameScore[@"cheatMode"] = @YES; gameScore[@"score"] = @1338; [gameScore saveInBackground]; }];
(PFQueryについても後で)
カウンター
Numberタイプのフィールドは- (void)incrementKey:(NSString)keyメソッドでインクリメントできます。また、- (void)incrementKey:(NSString)key byAmount:(NSNumber *)amountメソッドでamountにインクリメントの量を指定できます。
// 1加算(普通のインクリメント) [gameScore incrementKey:@"score"]; // 10加算 [gameScore1 incrementKey:@"score" byAmount:@10]; // 1減算(普通のデクリメント) [gameScore1 incrementKey:@"score" byAmount:@-1];
配列
Arrayタイプのフィールドであれば、以下の便利メソッドが使えます。
- - (void)addObject:(id)object forKey:(NSString *)key
- 指定した配列フィールドに要素(1つ)を追加する。
- - (void)addObjectsFromArray:(NSArray *)objects forKey:(NSString *)key
- 指定した配列フィールドに要素(複数)を追加する。
- - (void)addUniqueObject:(id)object forKey:(NSString *)key
- 指定した配列フィールドに要素(1つ)を追加する。その要素が既に存在する場合は追加しない。
- - (void)addUniqueObjectsFromArray:(NSArray *)objects forKey:(NSString *)key
- 指定した配列フィールドに要素(複数)を追加する。その要素が既に存在する場合は追加しない。
- - (void)removeObject:(id)object forKey:(NSString *)key
- 指定した配列フィールドから要素(1つ)を削除する。
- - (void)removeObjectsInArray:(NSArray *)objects forKey:(NSString *)key
- 指定した配列フィールドから要素(複数)を削除する。
オブジェクトの削除
オブジェクトの削除は以下のメソッドで行います。
- - (BOOL)delete
- 同期的に削除処理を行う。
- - (BOOL)delete:(NSError **)error
- 同期的に削除処理を行う。エラー付き。
- - (void)deleteInBackground
- 非同期で削除処理を行う。
- - (void)deleteInBackgroundWithBlock:(PFBooleanResultBlock)block
- 非同期で削除処理を行う。
削除処理が終了したら引数で指定したBlocksが実行される。 - - (void)deleteInBackgroundWithTarget:(id)target selector:(SEL)selector
- 非同期で削除処理を行う。
削除処理が終了したらtargetで指定したインスタンスのselectorで指定したメソッドが実行される。 - - (void)deleteEventually
- オフラインで削除処理を行う。
save系とほぼ一緒!
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"]; // IDを指定してオブジェクトを取得する [query getObjectInBackgroundWithId:@"xWMyZ4YEGZ" block:^(PFObject *gameScore, NSError *error) { // オブジェクトを削除 [gameScore deleteInBackground]; }];
deleteAll
これまたsave系と同様、複数のオブジェクトを一気に削除することも可能です。その場合はdeleteAll系のクラスメソッドを使います。
- + (BOOL)deleteAll:(NSArray *)objects
- 同期的に削除処理を行う。
- + (BOOL)deleteAll:(NSArray *)objects error:(NSError **)error
- 同期的に削除処理を行う。エラー付き。
- + (void)deleteAllInBackground:(NSArray *)objects
- 非同期で削除処理を行う。
- + (void)deleteAllInBackground:(NSArray *)objects block:(PFBooleanResultBlock)block
- 非同期で削除処理を行う。
削除処理が終了したら引数で指定したBlocksが実行される。 - + (void)deleteAllInBackground:(NSArray *)objects target:(id)target selector:(SEL)selector
- 非同期で削除処理を行う。
削除処理が終了したらtargetで指定したインスタンスのselectorで指定したメソッドが実行される。
(deleteAllEventuallyとかは無い!)
まとめ
以上、PFObjectの保存・更新・削除をざっとまとめてみました。いやーやればやるほど面白いです。オフライン時の機能が充実すれば更に良しってところでしょうか?
次回は認証かPFQueryかPFFileか、はたまたなんかのチュートリアルなのか??(まだ決まってません)乞うご期待!