この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
前回の記事では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か、はたまたなんかのチュートリアルなのか??(まだ決まってません)乞うご期待!