SlideShare une entreprise Scribd logo
1  sur  260
Télécharger pour lire hors ligne
Parseの世界
@maruyama097
丸山 不二夫
“I think the biggest mistake that we made,
as a company, is betting too much on HTML5
as opposed to native…”
Mark Zuckerburg 2012年9月
“Facebook does not hate the Web.”
Tom Occhino f8 2015年4月
Agenda
 Part I
モバイル・アプリをめぐる動向
 Part II
Parseの世界
 Part III
Web技術の新しい展開
モバイル・アプリをめぐる動向
このところ、モバイル・アプリの開発スタイルをめぐっ
て、注目すべき動きが連続している。アプリ開発のス
タイルの大きな変革期に差し掛かっていると考えてい
いと思う。
Part I
Part I
モバイルアプリをめぐる動向
 モバイル・アプリをめぐる新しい動き
 新しいモバイル・アプリの諸特徴とその背景
 FacebookとWebテクノロジー
Facebook : “Parse”
https://parse.com/
Amazon : “AWS Mobile SDK”
http://goo.gl/Nc5Bta
Microsoft : “Azure Mobile App
Services” http://goo.gl/dcWlRF
Twitter : "Fabric"
https://goo.gl/dV3Zsb
モバイルアプリをめぐる新しい動き
 Facebook : Parse
https://parse.com/
 Amazon : AWS Mobile SDK
http://goo.gl/Nc5Bta
 Microsoft : Azure Mobile App Services
http://goo.gl/dcWlRF
 Twitter : Fabric
https://goo.gl/dV3Zsb
新しいモバイル・アプリの
諸特徴とその背景
これらの動きは、幾つかの特徴を共有している。ここ
では、これらの動きを典型的に表していると思われる
Facebook Parseを中心に、旧来のWebアプリと
の比較でその特徴を見ることにしよう。あわせて、そ
の背景を見ておこう。
新しいモバイル・アプリの
諸特徴とその背景
 Server/Clientから、Cloud/Mobileへ
 Server-sideから、Mobile-sideへ
 同期型から、非同期型へ
 PULLモデルから、PUSHのサポートへ
Server/Clientから
Cloud/Mobileへ
ネットワーク・プログラミングでのサーバーとクライアン
トの役割は、現在も基本的なものである。ただ、その
役割の主要な担い手は、クラウドとモバイルに、大きく
変わりつつある。現在、もっとも一般的なアプリ開発
の舞台は、クラウドとモバイルであることに注意しよう
。
Parse Old Web Apps
Cloud & Mobile Server & Client
モバイルは、人類史上最大の
プラットフォーム
Mooreの法則で
チップは、こんなにも小さくなった
ウエアラブル用
Cortex A7 UP
500MHz, 0.36mm2
IOT用
Cortex M0
40MHz, 0.05mm2
ハイエンド
ウエアラブル用
Cortex A7 MP2
500MHz, 1.1mm2
モバイル用
Cortex A7 MP2
1.3GHz, 2.2mm2
Qualcomm のフラグシップ・モバイルCPUの
パフォーマンスの変化
指数関数的に、スピードアップしている
PCとモバイルのプロセッサー
歴史的には、モバイルのプロセ
ッサーは、PCに数年遅れていた。
ただし、現在(2014年)では、処
理能力でもメモリーのアクセス・
スピードでも、PCに並び、コア数
では、それを追い越している。
処理速度
コア数 メモリー・アクセス
Server-sideから
Mobile-sideへ
旧来のエンタープライズ・アプリの中心は、サーバー・
サイドのWebアプリであった。ただ、現在進行中の変
化は、モバイル・サイドのアプリ開発が、今後、主流に
なるだろうことを示している。この点では、Thin
Server Architectureという考えが非常に参考にな
る。
Parse Old Web Apps
Mobile-side Programming Server-side
+ Cloud Code Programming
Code
Code
Cloud Code
サーバーサイドのWebアプリのスタイル
の見直しの進行
サーバーサイドの
Webアプリ
+
HTML5の新機能
Android
Native
クライアントのデバイスの処理能力が
飛躍的に向上して、サーバーの生成
するHTMLをブラウザーでレンダリング
する以上の能力を持ち始めた。
サーバーの負担・ネットワー
ク帯域の増大等、いろいろな
困難がうまれていて、
それらの見直しが進行。
サーバー側
クライアント側
アプリケーション開発の多様化
J2EE,Spring
.ASP,.NET
等のサーバー
サイドアプリの
クライアント Application
Cacheを利用
したアプリ Widget
Packaged
Web App
chrome
Tizen
Firefox OS
JavaScript
MVC
Frameworks
Android
Native
現実に進行したのは、
「サーバーサイドWebアプリ
+HTML5」の世界の一方的
拡大ではなかった。
サーバーサードのWebアプリと、
クライアント中心のNativeアプリ
の中間に、様々のアプリケーショ
ンの形態が生まれた。
Webアプリのサーバーとクライアントへの
分岐とPackaged Web Appへの関心
J2EE,Spring
.ASP,.NET
等のサーバー
サイドアプリの
クライアント Application
Cacheを利用
したアプリ Widget
JavaScript
MVC
Frameworks
Android
Native
従来型のWebアプリ
(サーバーサイド)
新しいタイプのWebアプリ
(クライアントサイド)
外部にWebサーバー
を必要としない
外部にWebサーバー
を必要とする
Packaged
Web App
Chrome
Tizen
Firefox OS
サーバーとクライアントの
役割の見直しの一般的な背景
1. ハードの性能が、特にクライアント側で飛躍的に向上した
こと。クラウド・デバイスは、PCより、はるかにリッチなクラ
イアントである。
2. サーバーの負荷の増大
3. ネットワーク・トラフィックの増大
4. プログラムとViewの分離の難しさ
 全てがサーバー側でコントロールされ、特に、両者が
密に絡み合っているようなWebアプリでは、どちらの
変更も、開発の工程に大きなインパクトを与える可能
性がある。
 プログラマはデザインの変更を嫌い、デザイナーは、
プログラムの変更を嫌う。
Thin Server Architectureについて
 Thin Server Architecture というのは、2008
年の初め頃に、Mario Valenteを中心とするグ
ループが考えだしたコンセプト。
 残念ながら、発表当時は、大きな反響を起こすこ
ともなく、忘れられていた。今また、こうした数年前
のコンセプトに照明があたると言うのも、興味深
いことである。
https://sites.google.com/a/thinserverarchit
ecture.com/home/Home
Thin Server Architectureの定義
 Thin Server Architecture(TSA)は、今日の
Webアプリケーション・フレームワークの、慢心と
複雑さに対する、反応である。
 TSAは、全てのプレゼンテーション層のロジックを
、サーバーから、クライアント(Webブラウザー)
上の、JavaScript(あるいは他)のロジックに移し
変えることを提案する。
 これによって、次の三つのポジティブなことが得ら
れる。
Thin Server Architectureの定義
1. サーバー側の開発者は、ビジネス・ロジックに集
中出来る。
2. クライアントが分離して開発されるので、アプリケ
ーションは、より複雑でなくなる。
3. サーバーとクライアントの通信は、データを他方
の、あるいは将来のシステムとの間のデータのイ
ンポート、エクスポート、あるいは表現に利用可
能な、プロトコルを利用する。
Thin Server Architectureの図式
クライアント サーバー
資料:「Package Web Appについて
--- AndroidとChromeの統合」
 2013年 6月24日 マルレク第一回
 概要: http://bit.ly/1tNxHWH
 資料: http://bit.ly/1wdbBfX
 「アプリ開発の新しい動向」
http://bit.ly/1E2hOxD
同期型から
非同期型へ
関数の呼び出し、プロセス間・ノード間の通信のスタイ
ルも大きく変わろうとしている。相手の応答があるまで
ブロックして待機する同期型の処理の見直しと、非同
期型の処理への移行が進んでいる。Parseでは、メ
ソッドの呼び出しは、デフォールトで非同期型である。
Parse Old Web Apps
Async Sync
1ナノ秒を1秒だとした場合の実行時間
val socket = Socket()
val packet = socket.readFromMemory()
// 3日間ブロックする
// 例外が発生しない時には、次の処理へ
val confirmation = socket.sendToEurope(packet)
// 5年間ブロックする
// 例外が発生しない時には、次の処理へ
“Principles of Reactive Programming”
Coursera lecture 2014 by Erik Meijer
https://class.coursera.org/reactive-001/lecture/51
JSConf 2009 http://goo.gl/I4HLUc
John Resig
Reactive Extension (Rx)
by Microsoft
http://msdn.microsoft.com/en-
us/data/gg577609.aspx
interface IObservable<out T>
{
IDisposable Subscribe(IObserver<T> observer);
}
interface IObserver<in T>
{
void OnNext(T value);
void OnError(Exception ex);
void OnCompleted();
}
Rxの最も重要なインターフェース
Observable
Observerが
実装する3つの
タイプのメソッド
Rx 資料
 2010年 11月 DevCamp Keynote
“Rx: Curing your asynchronous
programming blues”
http://channel9.msdn.com/Blogs/codefest/DC2010T010
0-Keynote-Rx-curing-your-asynchronous-programming-
blues
 2012年 6月 TechEd Europe
“Rx: Curing your asynchronous
programming blues”
http://channel9.msdn.com/Events/TechEd/Europe/2012/
DEV413
JavaOne 2012
http://goo.gl/rLr91w
JavaOne 2012
http://goo.gl/rLr91w
Reactive プログラミング 資料
 2014年 1月21日 マルレク第八回
 概要: http://bit.ly/1tDnESC
 資料: http://bit.ly/1kkJelH
PULLモデルから
PUSHのサポートへ
現在主流のWebアプリは、Webページの閲覧と一緒
で、ユーザーがアクセスしないと起動しないPULL型
のモデル。クラウドからモバイルへの、一斉通知の
PUSHのサポートに対するニーズが高まっている。
Parse Old Web Apps
Push Pull
https://goo.gl/4V7vRL
Google GCM
https://goo.gl/lhJ8bl
Apple APNS
Facebook Push Notification
https://goo.gl/CuLNqf
FacebookとWebテクノロジー
FacebookのWebテクノロジーに対するスタンスは、
興味深いものがある。Nativeによるユーザー・エクス
ペリエンスの向上を追求しながら、ポストHTML5の
Webコンポーネント技術を、最大限、取り入れている
ように見える。Polymerと Reactを比較されたい。
FacebookのHTML5からの離脱
TechCrunch誌とのインタビュー 2012年9月11日
I think the
biggest mistake
that we made,
as a company,
is betting too
much on HTML5
as opposed to
native…
because it just
wasn’t there.
会社として、我々が犯した最大の誤りは、ネーティブに対して、
HTML5に、あまりに賭けすぎたことだと思う。
Facebook does not hate the Web.
But we're realists. We just can't use the Web right now
to build the types of user experiences that we want
Tom Occhino f8 2015 React Native
Facebookの新しいWebテクノロジー
 Flux
https://github.com/facebook/flux
 React
https://github.com/facebook/react
 Parse + React
http://blog.parse.com/learn/parse-and-react-shared-
chemistry/
 React Native
https://goo.gl/O9MjKI
 GraphQL + Relay
https://goo.gl/TXKRXQ
React Native
 F8 2015
 React Native & Relay: Bringing
Modern Web Techniques to Mobile
https://goo.gl/FDVQdK
 By Tom Occhino
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Parseの世界
ここでは、新しいモバイル・アプリ開発の代表的なプラ
ットフォームとしてFacebook Parseを取り上げる。
Parse + React, React Nativeについては、Part
III で紹介する。
Part II
Part II
Parseの世界
 Parse Object
 Relational Data の表現
 Queries / Query Samples
 Promises
 PUSH
 Cloud Code / Cloud Code Trigger
Parse Object
Parseの最大の特徴は、Parse Objectにある。
それは、モバイルとクラウドの間を自由に行き来する
Remote Objectである。と同時に、それは、データ
の担い手として Data Objectである。
Parse Old Web Apps
Data Object
MVC
HTML/HTTP
get/Fetch Save
Parse.Object
クラスとインスタンスの生成
Parse JavaScript
// Parse.objectのサブクラスを生成する簡単なシンタックス
var GameScore = Parse.Object.extend("GameScore");
// このクラスの新しいインスタンスを生成する。
var gameScore = new GameScore();
// 先の方法の代わりに、典型的なBackboneのシンタックスを利用することもできる
var Achievement = Parse.Object.extend({
className: "Achievement"
});
A complex subclass of
Parse.Object
// Parse.objectのサブクラスを生成する少し複雑ななシンタックス
var Monster = Parse.Object.extend(“Monster”, // 第一引数 クラス名
{ // 第二引数 インスタンスのメソッド達
hasSuperHumanStrength: function () {
return this.get("strength") > 18;
},
// インスタンスのプロパティの初期値は、initialize で定義する
initialize: function (attrs, options) {
this.sound = "Rawr"
}
},
{ // 第三引数 クラスのメソッド達
spawn: function(strength) {
var monster = new Monster();
monster.set("strength", strength);
return monster;
}
});
var monster = Monster.spawn(200);
alert(monster.get(‘strength’)); // spawnでsetされた 200を表示
alert(monster.sound); // インスタンスの soundの初期値 Rawr を表示.
Data Objectとしての Parse.Object
var number = 42;
var string = "the number is " + number;
var date = new Date();
var array = [string, number];
var object = { number: number, string: string };
var BigObject = Parse.Object.extend("BigObject");
var bigObject = new BigObject();
bigObject.set("myNumber", number);
bigObject.set("myString", string);
bigObject.set("myDate", date);
bigObject.set("myArray", array);
bigObject.set("myObject", object);
bigObject.set("myNull", null);
bigObject.save();
Parse.Object
クラスとインスタンスの生成
Parse Android Java
ParseObject gameScore =
new ParseObject("GameScore");
Parse iOS
PFObject *gameScore =
[PFObject objectWithClassName:@"GameScore"];
Parse .NET
ParseObject gameScore =
new ParseObject("GameScore");
Parse iOS Swift
var gameScore =
PFObject(className:"GameScore")
Parse.Object
オブジェクトの保存 save
var GameScore = Parse.Object.extend("GameScore");
var gameScore = new GameScore();
gameScore.set("score", 1337);
gameScore.set("playerName", "Sean Plott");
gameScore.set("cheatMode", false);
gameScore.save( null, {
success: function(gameScore) {
// オブジェクトの保存に成功した後で、実行されるべき処理
alert('New object created with objectId: ' + gameScore.id);
},
error: function(gameScore, error) {
// オブジェクトの保存に失敗した後で、実行されるべき処理
// error は、 Parse.Errorで、 error code と messageを持っている
alert('Failed to create new object, with error code: ' + error.message);
}
});
フィールドを、
saveの第一引数で、直接に設定
var GameScore = Parse.Object.extend("GameScore");
var gameScore = new GameScore();
gameScore. save({
score: 1337,
playerName: "Sean Plott",
cheatMode: false
}, {
success: function(gameScore) {
// The object was saved successfully.
},
error: function(gameScore, error) {
// The save failed.
// error is a Parse.Error with an error code and message.
}
});
Saveされるデータ
objectId: “xWMyZ4YEGZ”,
score: 1337,
playerName: “Sean Plott”,
cheatMode: false,
createdAt: “2011-06-10T18:33:42Z”,
updatedAt: "2011-06-10T18:33:42Z"
SaveされたオブジェクトのユニークID
Saveされた時点で追加・変更されるフィールド
オブジェクトの保存(非同期で行われる)
Parse Android Java
gameScore.put("score", 1337);
gameScore.put("playerName", "Sean Plott");
gameScore.put("cheatMode", false);
gameScore.saveInBackground();
Parse .NET
gameScore["score"] = 1337;
gameScore["playerName"] = "Sean Plott";
gameScore[“cheatMode”] = false;
await gameScore.SaveAsync();
Parse iOS Swift
gameScore["score"] = 1337
gameScore["playerName"] = "Sean Plott”
gameScore["cheatMode"] = false
gameScore.saveInBackgroundWithBlock { … }
Parse.Object
オブジェクトの取得 get
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.get( "xWMyZ4YEGZ",
{
success: function(gameScore) {
// オブジェクトの取得に成功
},
error: function(object, error) {
// オブジェクトの取得に失敗
// error は Parse.Errorで、error codeとmessageを持つ.
}
});
オブジェクトの取得 (Android)
ParseQuery<ParseObject> query = ParseQuery.getQuery(
"GameScore");
query.getInBackground( "xWMyZ4YEGZ",
new GetCallback<ParseObject>() {
public void done(ParseObject object, ParseException e) {
if (e == null) {
// objectは、ゲームのスコア
} else {
// なんか、うまくいかない
}
}
});
オブジェクトの取得
Parse .NET
ParseQuery<ParseObject> query =
ParseObject.GetQuery("GameScore");
ParseObject gameScore =
await query.GetAsync("xWMyZ4YEGZ");
Parse iOS Swift
var query = PFQuery(className:"GameScore")
query.getObjectInBackgroundWithId("xWMyZEGZ") {
(gameScore: PFObject?, error: NSError?) -> Void in
if error == nil && gameScore != nil {
println(gameScore)
} else {
println(error)
}
}
データベースとの同期 Fetch
myObject.fetch({
success: function(myObject) {
// オブジェクトの更新に成功
},
error: function(myObject, error) {
// オブジェクトの更新に失敗
// error は Parse.Errorで、error codeとmessageを持つ.
}
});
myObject.fetchInBackground(
new GetCallback<ParseObject>() {
public void done(ParseObject object, ParseException e) {
if (e == null) {
// Success!
} else {
// Failure!
}
}
});
await myObject.FetchAsync();
オブジェクトの更新
// オブジェクトの生成
var GameScore = Parse.Object.extend("GameScore");
var gameScore = new GameScore();
gameScore.set("score", 1337);
gameScore.set("playerName", "Sean Plott");
gameScore.set("cheatMode", false);
gameScore.set("skills", ["pwnage", "flying"]);
gameScore.save(null, {
success: function(gameScore) {
// 保存に成功したら、新しいデータで更新しよう
// この場合、cheatModeとscoreがクラウドに送られて
// playerNameは、変わらない。
gameScore.set("cheatMode", true);
gameScore.set("score", 1338);
gameScore.save();
}
});
オブジェクトの削除 destroy
myObject. destroy({
success: function(myObject) {
// objectは、Parse Cloudから削除された
},
error: function(myObject, error) {
// 削除失敗。
// error は Parse.Errorで、error codeとmessageを持つ.
}
});
// unsetメソッドで、オブジェクトの一つのフィールドの削除ができる
// このあと、 playerName フィールドは、空になる
myObject.unset("playerName");
// 削除されたフィールドをParse Cloudに保存する
myObject.save();
Relational Data の表現
Parse Objectでは、Relationalなデータの表現が
可能である。あとで見る Parse Queryを使って、デ
ータの検索が可能となる。
一対一、一対多の関係の表現
// 型を宣言する
var Post = Parse.Object.extend("Post");
var Comment = Parse.Object.extend("Comment");
// postを生成する
var myPost = new Post();
myPost.set("title", "I'm Hungry");
myPost.set("content", "Where should we go for lunch?");
// commentを生成する
var myComment = new Comment();
myComment.set("content", "Let's do Sushirrito.");
// postをcommentの parentの値として設定する
myComment.set("parent", myPost);
// myPost とmyCommentの両方が保存される
myComment.save();
MyPost
MyComment
parent
MyPost
MyComment
parent
内部的には、Parseのフレームワークは、整合性を維持するため
に、参照されたオブジェクトを一箇所のみに格納する。オブジェク
トIDを使った場合のみ、次のように、オブジェクトのリンクは可能
である。
var post = new Post();
post.id = "1zEcyElZ80";
myComment.set("parent", post);
myComment.put(“parent”,
ParseObject.createWithoutData("Post", "1zEcyElZ80"));
myComment[“parent”] =
ParseObject.CreateWithoutData("Post", "1zEcyElZ80");
myComment[“parent”] =
PFObject(withoutDataWithClassName:“Post”,objectId:"1zEcyElZ80")
var post = fetchedComment. get("parent");
post.fetch({
success: function(post) {
var title = post.get("title");
}
});
関連付けられたオブジェクトの取得
 オブジェクトを取り出しても、デフォールトでは、
Parse.Objectに関連付けられたオブジェクトは取り出せ
ない。これらのオブジェクトの値は、次のようにしないと取
り出せない。 post
fetchedComment
post
fetchedComment
get(“parent”)
var post = myComment[“parent”] as PFObject
post.fetchIfNeededInBackgroundWithBlock {
(post: PFObject?, error: NSError?) -> Void in
let title = post?[“title”] as? NSString
// do something with your title variable
}
ParseObject post =
fetchedComment.Get<ParseObject>(“parent”);
await post.FetchIfNeededAsync();
fetchedComment.getParseObject(“post”)
.fetchIfNeededInBackground(
new GetCallback<ParseObject>(){
public void done(ParseObject post, ParseException e) {
String title = post.getString(“title”);
// Do something with your new title variable
}
});
多対多の関係の表現 relation
 Many-to-manyの関係は、Parse.Relation を使って
モデル化される。このモデルは、一つのキーに、
Parse.Objectの配列を格納するのに似ている。ただし、全
ての関係のオブジェクトを一回で取り出す必要がないことを
除いては。
 加えて、このことで Parse.Relation は、 Parse.Object
の配列を利用するアプローチと比較して、より多くのオブジ
ェクトにスケールすることが可能になる。
var user = Parse.User.current();
var relation = user.relation("likes");
relation.add(post);
user.save();
1
2
User
Post
likes
1 3
2
ParseUser user = ParseUser.getCurrentUser();
ParseRelation<ParseObject> relation = user.getRelation("likes");
relation.add(post);
user.saveInBackground();
var user = ParseUser.CurrentUser;
var relation = user.GetRelation<ParseObject>(“likes”);
relation.Add(post);
await user.SaveAsync();
var user = PFUser.currentUser()
var relation = user.relationForKey(“likes”)
relation.addObject(post)
user.saveInBackground()
var user = Parse.User.current();
var relation = user.relation("likes");
relation.add(post);
user.save();
関係リストの取得
 デフォールトでは、次のような関係のオブジェクトのリスト
はダウンロードできない。
 この場合には、queryで返される Parse.Query を利用
すれば、ユーザーが「いいね」をつけたポストのリストを、
取得できる。次のようなコードになる。
relation.add( [post1, post2, post3] );
user.save();
relation.query().find({
success: function(list) {
// リストは、ユーザーがいいねをしたpostを含んでいる
});
relation.getQuery().findInBackground(
new FindCallback<ParseObject>() {
void done(List<ParseObject> results, ParseException e) {
if (e != null) {
// There was an error
} else {
// results have all the Posts the current user liked.
}
}});
IEnumerable<ParseObject> relatedObjects =
await relation.Query.FindAsync();
relation.query().findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if let error = error {
// There was an error
} else {
// objects has all the Posts the current user liked.
}}
関係リストの項目の削除 remove
// postをParse.Relationから削除できる
relation.remove(post);
user.save();
// saveを呼ぶ前に、複数回removeを呼べる
relation.remove(post1);
relation.remove(post2);
user.save();
Queryの制限条件
 ポストのサブセットが欲しいならば、次のように制限条件を
Parse.Queryに課す。
var query = relation.query();
query.equalTo("title", "I'm Hungry");
query.find({
success:function(list) {
// list は、現在のユーザーによって「いいね」されたポストで、タイトルが
// "I'm Hungry” であるものを含んでいる。
}
});
Queries
Parseの強力な機能の一つは、クラウド上のデータベ
ースに対する検索機能が備わっていることである。
Parse.Queryが、それを可能にする。
基本的な Query
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.equalTo("playerName", "Dan Stemkoski");
query.find({
success: function(results) {
alert("Successfully retrieved " + results.length + " scores.");
// 返された Parse.Objectの値を処理
for (var i = 0; i < results.length; i++) {
var object = results[i];
alert(object.id + ' - ' + object.get('playerName'));
}
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
基本的な Query
ParseQuery<ParseObject> query =
ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Dan Stemkoski");
query.findInBackground(
new FindCallback<ParseObject>() {
public void done(
List<ParseObject> scoreList, ParseException e) {
if (e == null) {
Log.d("score", "Retrieved " + scoreList.size() + " scores");
} else {
Log.d("score", "Error: " + e.getMessage());
}
}
});
基本的な Query
var query =
from gameScore in ParseObject.GetQuery("GameScore")
where gameScore.Get<string>("playerName") == "Dan Stemkoski"
select gameScore;
IEnumerable<ParseObject> results = await query.FindAsync();
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"];
[query whereKey:@"playerName" equalTo:@"Dan Stemkoski"];
NSArray* scoreArray = [query findObjects];
.NET版 Parseでは、LINQが使える。
await ... async 構文と合わせて、なかなか、格好がいい。
フィールドの選択 select
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.select("score", "playerName");
query.find().then(function(results) {
// resultsは、 “score”, “playerName” のみのフィールドを含む
});
Query の制約条件
 複数の制約条件をqueryに与えることができる。全ての条件に
マッチしたオブジェクトのみが結果に含まれる。別の言い方を
すれば、それは、制約条件のANDに似ている。
query.notEqualTo("playerName", "Michael Yabuti");
query.greaterThan("playerAge", 18);
 limitを設定することで、結果の数を制限できる。デフォールト
では、結果は100個に制限されている。1から1000までの任意
の設定ができる。
query.limit(10); // 最大 10個の値に制限する
Query の制約条件
 一つの結果だけが必要なら first を利用できる。
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.equalTo("playerEmail", "dstemkoski@example.com");
query.first({
success: function(object) {
// オブジェクトの取得に成功
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
 skipを設定して、最初の結果をスキップできる。
query.skip(10); // 最初の10この結果をスキップ
ソートと比較演算
// score フィールドを昇順に整列する
query.ascending("score");
// scoreフィールドを降順に整列する
query.descending("score");
// ソート可能な型に対して、比較演算が可能である
// wins < 50
query.lessThan("wins", 50);
// wins <= 50
query.lessThanOrEqualTo("wins", 50);
// wins > 50
query.greaterThan("wins", 50);
// wins >= 50
query.greaterThanOrEqualTo("wins", 50);
メンバー、存在、非存在のチェック
// Jonathan, Dario, Shawnのスコアを検索する
query.containedIn("playerName",
["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]);
// Jonathan, Dario, Shawn以外のスコアを検索する
query.notContainedIn("playerName",
["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]);
// スコアが設定されているオブジェクトを検索
query.exists("score");
// スコアが設定されていないオブジェクトを検索
query.doesNotExist("score");
// wins < 50という条件で絞る
query = from gameScore in query
where gameScore.Get<int>("wins") < 50
select gameScore;
// wins <= 50という条件で絞る
query = from gameScore in query
where gameScore.Get<int>("wins") <= 50
select gameScore;
// wins > 50という条件で絞る
query = from gameScore in query
where gameScore.Get<int>("wins") > 50
select gameScore;
// wins >= 50という条件で絞る
query = from gameScore in query
where gameScore.Get<int>("wins") >= 50
select gameScore;
.NET ParseでのLINQの利用
var Team = Parse.Object.extend("Team");
var teamQuery = new Parse.Query(Team);
teamQuery.greaterThan("winPct", 0.5);
var userQuery = new Parse.Query(Parse.User);
userQuery.matchesKeyInQuery("hometown", "city", teamQuery);
userQuery.find({
success: function(results) {
// ホームタウンで、勝ち越しているチームの検索結果
}
});
ParseQuery<ParseObject> teamQuery = ParseQuery.getQuery(“Team”);
teamQuery.whereGreaterThan(“winPct”, 0.5);
ParseQuery<ParseUser> userQuery = ParseUser.getQuery();
userQuery.whereMatchesKeyInQuery(“hometown”, “city”, teamQuery);
userQuery.findInBackground(
new FindCallback<ParseUser>() {
void done(List<ParseUser> results, ParseException e) {
// results has the list of users with a hometown team with
// a winning record
}
});
ホームタウンで、勝ち越しているユーザーの検索
var teamQuery =
from team in ParseObject.GetQuery("Team")
where team.Get<double>("winPct") > 0.5
select team;
var userQuery =
from user in ParseUser.Query
join team in teamQuery on user["hometown"] equals team["city"]
select user;
IEnumerable<ParseUser> results = await userQuery.FindAsync();
// results will contain users with a hometown team with a winning record
var teamQuery = PFQuery(className:"Team")
teamQuery.whereKey("winPct", greaterThan:0.5)
var userQuery = PFUser.query()
userQuery!.whereKey("hometown", matchesKey:"city",
inQuery:teamQuery)
userQuery!.findObjectsInBackgroundWithBlock {
(results: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// results will contain users with a hometown team with a winning record
}
}
var losingUserQuery = new Parse.Query(Parse.User);
losingUserQuery.doesNotMatchKeyInQuery(
"hometown", "city", teamQuery);
losingUserQuery.find({
success: function(results) {
// results has the list of users with a hometown team with
// a losing record
}
});
ParseQuery<ParseUser> losingUserQuery = ParseUser.getQuery();
losingUserQuery.whereDoesNotMatchKeyInQuery(
"hometown", "city", teamQuery);
losingUserQuery.findInBackground(
new FindCallback<ParseUser>() {
void done(List<ParseUser> results, ParseException e) {
// results has the list of users with a hometown team with
// a losing record
}
});
ホームタウンで、負け越しているユーザーの検索
Parse Query Sample
以下、いくつかのサンプルを紹介する。Parse Query
が、SQLやLINQと、ほぼ同等の機能を持つことを確
認されたい。
Relationalな検索
// Parse.Object myPost は、すでに作られているとしよう
var query = new Parse.Query(Comment);
query.equalTo("post", myPost);
query.find({
success: function(comments) {
// comments は、myPostに対するコメントである
}
});
ParseQuery<ParseObject> query = ParseQuery.getQuery("Comment");
query.whereEqualTo("post", myPost);
query.findInBackground(
new FindCallback<ParseObject>() {
public void done(List<ParseObject> commentList, ParseException e) {
// commentList now has the comments for myPost
}
});
MyPost
MyComment
“post”
// Assume ParseObject myPost was previously created.
var query = from comment in ParseObject.GetQuery("Comment")
where comment["post"] == myPost
select comment;
var comments = await query.FindAsync();
// comments now contains the comments for myPost
PFQuery *query = [PFQuery queryWithClassName:@"Comment"];
[query whereKey:@"post" equalTo:myPost];
[query findObjectsInBackgroundWithBlock:
^(NSArray *comments, NSError *error) {
// comments now contains the comments for myPost
}];
画像付きのコメントの検索
var Post = Parse.Object.extend("Post");
var Comment = Parse.Object.extend("Comment");
var innerQuery = new Parse.Query(Post);
innerQuery.exists("image");
var query = new Parse.Query(Comment);
query.matchesQuery("post", innerQuery);
query.find({
success: function(comments) {
// postに対するコメントで、画像付きのコメントの検索結果
}
});
ParseQuery<ParseObject> innerQuery = ParseQuery.getQuery("Post");
innerQuery.whereExists("image");
ParseQuery<ParseObject> query = ParseQuery.getQuery("Comment");
query.whereMatchesQuery("post", innerQuery);
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> commentList, ParseException e) {
// comments now contains the comments for posts with images.
}});
var imagePosts = from post in ParseObject.GetQuery("Post")
where post.ContainsKey("image")
select post;
var query = from comment in ParseObject.GetQuery("Comment")
join post in imagePosts on comment["post"] equals post
select comment;
var comments = await query.FindAsync();
// comments now contains the comments for posts with images
PFQuery *innerQuery = [PFQuery queryWithClassName:@"Post"];
[innerQuery whereKeyExists:@"image"];
PFQuery *query = [PFQuery queryWithClassName:@"Comment"];
[query whereKey:@"post" matchesQuery:innerQuery];
[query findObjectsInBackgroundWithBlock:
^(NSArray *comments, NSError *error) {
// comments now contains the comments for posts with images
}];
画像なしのコメントの検索
var Post = Parse.Object.extend("Post");
var Comment = Parse.Object.extend("Comment");
var innerQuery = new Parse.Query(Post);
innerQuery.exists("image");
var query = new Parse.Query(Comment);
query.doesNotMatchQuery("post", innerQuery);
query.find({
success: function(comments) {
// postに対するコメントで、画像なしのコメントの検索結果
}
});
var post = new Post();
post.id = "1zEcyElZ80";
query.equalTo("post", post);
最新のコメント10個の検索
var query = new Parse.Query(Comment);
// Retrieve the most recent ones
query.descending("createdAt");
// Only retrieve the last ten
query.limit(10);
// Include the post data with each comment
query.include("post");
query.find({
success: function(comments) {
// Comments now contains the last ten comments, and the "post" field
// has been populated. For example:
for (var i = 0; i < comments.length; i++) {
// This does not require a network access.
var post = comments[i].get("post");
}
}
});
オブジェクトを数える count
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.equalTo("playerName", "Sean Plott");
query.count({
success: function(count) {
// The count request succeeded. Show the count
alert("Sean has played " + count + " games");
},
error: function(error) {
// The request failed
}
});
Queryの組み合わせ or
var lotsOfWins = new Parse.Query("Player");
lotsOfWins.greaterThan("wins", 150);
var fewWins = new Parse.Query("Player");
fewWins.lessThan("wins", 5);
var mainQuery = Parse.Query.or(lotsOfWins, fewWins);
mainQuery.find({
success: function(results) {
// たくさん勝っているひと、「あるいは」、ほとんど勝ってない人
},
error: function(error) {
// There was an error.
}
});
Queryの組み合わせ
ParseQuery<ParseObject> lotsOfWins = ParseQuery.getQuery("Player");
lotsOfWins.whereGreaterThan(150);
ParseQuery<ParseObject> fewWins = ParseQuery.getQuery("Player");
fewWins.whereLessThan(5);
List<ParseQuery<ParseObject>> queries =
new ArrayList<ParseQuery<ParseObject>>();
queries.add(lotsOfWins);
queries.add(fewWins);
ParseQuery<ParseObject> mainQuery = ParseQuery.or(queries);
mainQuery.findInBackground(
new FindCallback<ParseObject>() {
public void done(List<ParseObject> results, ParseException e) {
// results has the list of players that win a lot or haven't won much.
}
});
Compound Queries
var lotsOfWins = from player in ParseObject.GetQuery("Player")
where player.Get<int>("wins") > 150
select player;
var fewWins = from player in ParseObject.GetQuery("Player")
where player.Get<int>("wins") < 5
select player;
ParseQuery<ParseObject> query = lotsOfWins.Or(fewWins);
var results = await query.FindAsync();
// results contains players with lots of wins or only a few wins.
Compound Queries
var lotsOfWins = PFQuery(className:"Player")
lotsOfWins.whereKey("wins", greaterThan:150)
var fewWins = PFQuery(className:"Player")
fewWins.whereKey("wins", lessThan:5)
var query = PFQuery.orQueryWithSubqueries([lotsOfWins, fewWins])
query.findObjectsInBackgroundWithBlock {
(results: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// results contains players with lots of wins or only a few wins.
}
}
Promises
Promiseの利用は、Parse JavaScript SDKの大き
な特徴の一つである。Parseの全ての非同期メソッド
は、Promiseを返す。Promiseを利用することで、
callbackのネストのない、クリーンなコードを書くこと
ができる。
Promiseを生成する
var successful = new Parse.Promise();
successful.resolve(“The good result.”); // 結果は成功である
var failed = new Parse.Promise();
failed.reject(“An error message.”); // 結果は失敗である
// Promiseの生成時に、その結果が分かっているのなら
// as (resolved) あるいは error (rejected)メソッドを利用できる。
var successful = Parse.Promise.as("The good result.");
var failed = Parse.Promise.error("An error message.");
非同期のメソッドを作る
var delay = function(millis) {
var promise = new Parse.Promise();
setTimeout(function() {
promise.resolve();
}, millis);
return promise;
};
delay(100).then(function() {
// 100m秒後に実行される
});
then メソッド
 全てのPromiseは、then という名前のメソッドを持つ。
 then は、2つのcallback を持つ。
最初の callback は、Promise が成功した時に呼ばれ、
二つ目の callback は、Promise が失敗した時に呼ば
れる。
obj.save().then(function(obj) {
// the object was saved successfully.
}, function(error) {
// the save failed.
});
複数のPromiseを
一つのチェインにつなげる
var query = new Parse.Query("Student");
query.descending("gpa");
query.find().then(function(students) {
students[0].set("valedictorian", true);
return students[0].save();
}).then(function(valedictorian) {
return query.find();
}).then(function(students) {
students[1].set("salutatorian", true);
return students[1].save();
}).then(function(salutatorian) {
// Everything is done!
});
エラー処理
 チェインの中のPromiseのどれか一つでもエラーを返せ
ば、それ以降の全ての success コールバックは、error
コールバックに会うまでスキップされる。
 errorコールバックは、そのエラーを変形して、リジェクトさ
れない新しいPromiseを返して、それをハンドルできる。
 リジェクトされたPromiseは、例外を投げるのと同じように
考えることができる。errorコールバックは、エラーをハン
ドルし、あるいは、再度エラーを返す catch ブロックのよ
うなものである。
Error Handling
var query = new Parse.Query("Student");
query.descending("gpa");
query.find().then(function(students) {
students[0].set("valedictorian", true);
// 強制的にこのコールバックを失敗させる
return Parse.Promise.error("There was an error.");
}).then(function(valedictorian) {
// この処理はスキップされる
return query.find();
}).then(function(students) {
// この処理もスキップされる
students[1].set("salutatorian", true);
return students[1].save();
}, function(error) {
// このエラーハンドラーが呼ばれることになる。
// この中で、新しいPromiseを返すことができる
return Parse.Promise.as("Hello!");
}).then(function(hello) {
// うまくいく
}, function(error) {
// エラーはすでに処理されているので、ここは呼ばれない。
});
シリアルなpromiseの実行
var query = new Parse.Query("Comments");
query.equalTo(“post”, 123); // post 123のcommentを検索する
query.find().then(function(results) {
// promiseを一つ用意する
var promise = Parse.Promise.as();
_.each(results, function(result) {
// それぞれのcommentに、それを削除する関数を適用する
promise = promise.then(function() {
// 削除が終了した時にpromiseを返す。このpromiseは、次の繰り返しで
// thenに渡される。削除は、一個づつ進む。
return result.destroy();
});
});
return promise;
}).then(function() {
// 全てのcommentが削除された.
});
パラレルなpromiseの実行
 whenメソッドを使って、promiseを複数のタスクをパラレ
ルに実行するのにも利用できる。
 複数の操作を一度に開始することができる。
Parse.Promise.whenを使えば、その入力の全ての
promiseが解決された時に、promiseを返すような新し
いpromiseを生成できる。
 この新しいpromiseは、渡されたpromiseのいずれもが
失敗しなかった時に成功する。そうでなければ、最後のエ
ラーで失敗する。
 パラレルに操作を実行するのは、シリアルな実行より高速
であろう。しかし、それは、多くのシステムのリソースと帯
域を消費するかもしれない。
パラレルなpromiseの実行
var query = new Parse.Query("Comments");
query.equalTo("post", 123);
query.find().then(function(results) {
// promiseの配列を用意して、一つの削除に一つのpromiseを割り当てる。
var promises = [];
_.each(results, function(result) {
// 削除を直ちに開始して、promiseを配列に追加する
promises.push(result.destroy());
});
// whenで並列実行。全ての削除が終わったら、新しいpromiseを返す
return Parse.Promise.when(promises);
}).then(function() {
// 全てのcommentが削除された.
});
PUSH
Android, iOSといった異なるプラットフォームに対し
て、細かなターゲットの設定も行いながら、通知を一
斉送信できる ParseのPUSH機能は、とても強力で
ある。
PUSHを送る二つの方法
 push通知を送るには、二つの方法がある。
一つは、Parseの channnelを使うやり方で、
もう一つは、advanced targeting を使うやり方であ
る。
 channelは、pushを送るために単純で容易なモデルを提
供し、advanced targetingは、より強力で柔軟なモデル
を提供する。双方ともに、お互いに完全な互換性がある。
PUSHでのクラウド利用 Cloud Code
 通知の送出には、Parse.comのpushコンソールや
REST API、あるいは、Cloud Codeからの送出がよく
利用される。
 Cloud Codeでは、JavaScript SDKが使われているの
で、Cloud Functionからpushを送りたいと思うのなら、
まず、ここからスタートすることになる。
 しかし、 Cloud Codeの外側のJavaScript SDKや他の
クライアントSDKから通知を送ることに決めるなら、Parse
アプリのpush通知の中で、クライアントのpushを可能に
する必要がある。
クライアントからの送出は、
セキュリティ上の脆弱性を持つ
 しかし、クライアントからのpushは、アプリにセキュリティ
上の脆弱性をもたらしうることを、きちんと理解しておくこと
。推奨できるのは、クライアントからのpushをテスト目的
にのみ利用することである。そして、アプリが製品段階に
入る準備ができた時には、push通知のロジックをCloud
Codeに移すことである。
PUSHの解析情報
 ユーザーは、pushを作成してから30日間まで、過去の
pushを見ることができる。将来に予定されているpushは
、送出が行われていない限りは、pushコンソールから削
除できる。pushの送出の後には、pushコンソールには、
pushの解析情報が表示される。
PUSHで利用される属性 Installations
 badge
 channels
 timeZone
 deviceType
 pushType
 installationId
 deviceToken
 channelUris
 appName
 appVersion
 parseVersion
 appIdentifier
Channelを使う
 通知を送る最も簡単な方法は、channel を使うことであ
る。channelを使えば、pushを送るのに、publisher-
subscriber モデルを利用することが可能になる。
 デバイスは、一つ以上のchannelにサブスクライブするこ
とから始める。その後、通知は、サブスクライブしたデバイ
スに送られる。
 与えられたInstallationによってサブスクライブされた
channelは、Installationオブジェクトのchannel フィー
ルドに格納される。
ChannelにPUSHを送る
Parse.Push.send({
// “Giants”と "Mets" という二つのchannelにPUSHを送る
channels: [ “Giants”, “Mets” ],
// Pushで送られるデータ
data: {
alert: "The Giants won against the Mets 2-3."
}
}, {
success: function() {
// Push は成功した
},
error: function(error) {
// エラー
}
});
Giantsチャンネルと
Metsチャンネルに、
試合結果を配信する
Sending Pushes to Channels
LinkedList<String> channels = new LinkedList<String>();
channels.add("Giants");
channels.add("Mets");
ParsePush push = new ParsePush();
push.setChannels(channels);
// Notice we use setChannels not setChannel
push.setMessage("The Giants won against the Mets 2-3.");
push.sendInBackground();
// Send a notification to all devices subscribed to the "Giants" channel.
var push = new ParsePush();
push.Channels = new List<string> {"Giants"};
push.Alert = "The Giants just scored!";
await push.SendAsync();
let channels = [ "Giants", "Mets" ]
let push = PFPush()
// Be sure to use the plural 'setChannels'.
push.setChannels(channels)
push.setMessage("The Giants won against the Mets 2-3.")
push.sendPushInBackground()
Advanced Targetingを使う
 channelは、多くのアプリでとても役に立つのだが、push
の受け取り手をターゲットする際に、もっと細かく指定する
ことが必要となることがある。
 Parseでは、Query APIを使えば、Installationオブジェ
クトの任意のサブセットに対するqueryを書くことができる
ので、それらにpushを送出できる。
 Installationオブジェクトは、他のオブジェクトと同様に、
Parseに格納されているので、どんなデータでも保存でき
るし、Installationオブジェクトと他のオブジェクトとの関
係も作り出すこともできる。 こうして、ユーザーベースで、
非常にカスタマイズ化されたダイナミックなセグメントに、
pushを送ることができる。
Queryの結果にPushを送る
var query = new Parse.Query(Parse.Installation);
query.equalTo('injuryReports', true); // injuryReportsが真のもの
Parse.Push.send({
where: query, // 送出先
data: {
alert: "Willie Hayes injured by own pop fly."
}
}, {
success: function() {
// Push は成功した
},
error: function(error) {
// Handle error
}
}); 負傷者情報を配信する
QueryをChannelに使う
var query = new Parse.Query(Parse.Installation);
query.equalTo(‘channels’, ‘Giants’); // Set our channel
query.equalTo('scores', true);
Parse.Push.send({
where: query, // channelがGiantsで、scoreが真のものへ
data: {
alert: "Giants scored against the A's! It's now 2-2."
}
}, {
success: function() { // Push was successful },
error: function(error) { // Handle error }
});
Giantsチャンネルに得点を配信する
// Create our Installation query
ParseQuery pushQuery = ParseInstallation.getQuery();
// Set the channel
pushQuery.whereEqualTo("channels", "Giants");
pushQuery.whereEqualTo("scores", true);
// Send push notification to queryParse
Push push = new ParsePush();
push.setQuery(pushQuery);
push.setMessage("Giants scored against the A's! It's now 2-2.");
push.sendInBackground();
var push = new Parse.Push();
push.Query = from installation in ParseInstallation.Query
where installation.Get<bool>("scores") == true
select installation;
push.Channels = new List<string> { "Giants" };
push.Alert = "Giants scored against the A's! It's now 2-2.";
await push.SendAsync();
// Create our Installation query
let pushQuery = PFInstallation.query()
// Set channel
pushQuery.whereKey("channels", equalTo: "Giants")
pushQuery.whereKey("scores", equalTo: true)
// Send push notification to query
let push = PFPush()
// Set our Installation query
push.setQuery(pushQuery)
push.setMessage("Giants scored against the A's! It's now 2-2.")
push.sendPushInBackground()
// ユーザーがその場所に近いか調べる
var userQuery = new Parse.Query(Parse.User);
userQuery.withinMiles("location", stadiumLocation, 1.0);
// そのユーザーのPushの送り先を調べる
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.matchesQuery('user', userQuery);
// Send push notification to query
Parse.Push.send({
where: pushQuery,
data: {
alert: "Free hotdogs at the Parse concession stand!"
}
}, {
success: function() {
// Push was successful
},
error: function(error) {
// Handle error
}
});
スタジアムに近いところにいるユーザーに通知を送る
// Find users near a given location
ParseQuery userQuery = ParseUser.getQuery();
userQuery.whereWithinMiles("location", stadiumLocation, 1.0)
// Find devices associated with these users
ParseQuery pushQuery = ParseInstallation.getQuery();
pushQuery.whereMatchesQuery("user", userQuery);
// Send push notification to query
ParsePush push = new ParsePush();
push.setQuery(pushQuery);
// Set our Installation query
push.setMessage("Free hotdogs at the Parse concession stand!");
push.sendInBackground();
// Find users in the Seattle metro area
var userQuery = ParseUser.Query.WhereWithinDistance(
"location",
marinersStadium,
ParseGeoDistance.FromMiles(1));
var push= new ParsePush();
push.Query = from installation in ParseInstallation.Query
join user in userQuery on installation["user"] equals user
select installation;
push.Alert = "Mariners lost? Free conciliatory hotdogs at the Parse
concession stand!";
await push.SendAsync();
// Find users near a given location
let userQuery = PFUser.query()
userQuery.whereKey("location", nearGeoPoint: stadiumLocation,
withinMiles: 1)
// Find devices associated with these users
let pushQuery = PFInstallation.query()
pushQuery.whereKey("user", matchesQuery: userQuery)
// Send push notification to querylet push = PFPush()
push.setQuery(pushQuery)
// Set our Installation query
push.setMessage("Free hotdogs at the Parse concession stand!")
push.sendPushInBackground()
送出オプション
 Push通知は、単にメッセージを送る以上のことが可能で
ある。
 iOSでは、pushに音や画像を含めることができるし、任意
のカスタムデータを送ることができる。
 Androidでは、通知を受け取った時に起動されるIntent
の指定までできる。通知が、時間に敏感な場合には、エク
スパイアーする日付の設定も可能である。
通知をカスタマイズする
 単なるメッセージ以上のものを送ろうと思ったら、データ辞
書の他のフィールドを設定することができる。特別な意味
を持つ次のようなフィールドが用意されている。
 alert: 通知のメッセージ
 badge: (iOS のみ) アプリのアイコンの上右隅に示さ
れる値。値を指定することも、一つづつその値を増やすこ
ともできる。
 sound: (iOS のみ) アプリケーション内のサウンド・ファ
イルの名前
 content-available: (iOS のみ) Newsstandアプリ
や、iOS7から導入された Remote Notification
Background Mode (“Background Push”とよばれて
いる)を使っている場合には、バックグラウンドでのダウン
ロードを起動するために、この値に1を設定する。
 category: (iOS のみ) このpush通知の
UIUserNotificationCategoryの識別子
 uri: (Android のみ) URIを含む、オプションのフィール
ド。通知が開かれた時、このURIに関連したActivityが起
動される。
 title: (Android のみ) Androidのシステム・トレーの通
知に表示される値。
Parse.Push.send({
channels: [ "Mets" ],
data: {
alert: "The Mets scored! The game is now tied 1-1.",
badge: "Increment",
sound: "cheering.caf",
title: "Mets Score!"
}
}, {
success: function() {
// Push was successful
},
error: function(error) {
// Handle error
}
});
JSONObject data = new JSONObject("{
¥“alert¥”: ¥“The Mets scored!¥”,
¥“badge¥”: ¥“Increment¥”, ¥"sound¥": ¥"cheering.caf¥"}");
ParsePush push = new ParsePush();
push.setChannel("Mets");
push.setData(data);
push.sendPushInBackground();
Metsが得点した時に、
Metsチャンネルに通知
する。音付きで。
let data = [
"alert" : "The Mets scored! The game is now tied 1-1!",
"badge" : "Increment",
"sounds" : "cheering.caf"]
let push = PFPush()
push.setChannels(["Mets"])
push.setData(data)
push.sendPushInBackground()
var push = new ParsePush();
push.Channels = new List<string> {"Mets"};
push.Data = new Dictionary<string, object> {
{"title", "Score Alert"}
{"alert", "The Mets scored! The game is now tied 1-1!"},
};
await push.SendAsync();
var query = new Parse.Query(Parse.Installation);
query.equalTo('channels', 'Indians');
query.equalTo('injuryReports', true);
Parse.Push.send({
where: query,
data: {
action: "com.example.UPDATE_STATUS"
alert: "Ricky Vaughn was injured in last night's game!",
name: "Vaughn",
newsItem: "Man bites dog"
}
}, {
success: function() {
// Push was successful
},
error: function(error) {
// Handle error
}
});
Indians チャンネルに
負傷者情報を流す。
アクション付きで。
JSONObject data = new JSONObject("{
¥“name¥”: ¥“Vaughn¥”,
¥"newsItem¥": ¥"Man bites dog¥"}”
));
ParsePush push = new ParsePush();
push.setQuery(injuryReportsQuery);
push.setChannel("Indians");
push.setData(data);
push.sendPushInBackground();
let data = [
"alert" : "Ricky Vaughn was injured in last night's game!",
"name" : "Vaughn",
"newsItem" : "Man bites dog”
]
let push = PFPush()
push.setQuery(injuryReportsdata)
push.setChannel("Indians")
push.setData(data)
push.sendPushInBackground()
エクスパイアの設定
 デバイスの電源が入っていなかったり、インターネットに接続
されていない場合、push通知は配達されない。もし、遅れて
配達されれば意味のない時間に敏感な通知の場合には、エ
クスパイアーする日付を設定できる。これで、もはや重要では
ない情報で、不必要にユーザーに警告するのを避ける。
 通知のエクスパイアーの時間を指定するために、Parseでは
二つのパラメーターが用意されている。一つは、Parseが通
知を送るのを止めるべき日付を指定する expiration date
である。
 例えば、これから正確に一週間後に、通知をエクスパイアー
させるには、次のようにする。
Parse.Push.send({
where: everyoneQuery,
expiration_time: new Date(2015, 5, 10)
data: {
alert: "Season tickets on sale until May 10, 2015"
}
}, {
success: function() {
// Push was successful
},
error: function(error) {
// Handle error
}
});
ParsePush push = new ParsePush();
push.setExpirationTime(1430762356);
push.setQuery(everyoneQuery);
push.setMessage("Season tickets on sale until May 4th");
push.sendPushInBackground();
指定の日付5/15以降は
通知を受け取れないように
設定する。
var push = new ParsePush();
push.Expiration = new DateTime(2015, 5, 4);
push.Alert = "Season tickets on sale until May 4th";
await push.SendAsync();
// Create date object for tomorrow
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:2015];
[comps setMonth:5];
[comps setDay:4];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [gregorian dateFromComponents:comps];
// Send push notification with expiration date
PFPush *push = [[PFPush alloc] init];
[push expireAtDate:date];
[push setQuery:everyoneQuery];
[push setMessage:@"Season tickets on sale until May 4th"];
[push sendPushInBackground];
プラットフォームでターゲットする
// Notification for Android users
var queryAndroid = new Parse.Query(Parse.Installation);
queryAndroid.equalTo('deviceType', 'android');
Parse.Push.send({
where: queryAndroid,
data: {
alert: "Your suitcase has been filled with tiny robots!"
}
});
// Notification for iOS users
var queryIOS = new Parse.Query(Parse.Installation);
queryIOS.equalTo(‘deviceType’, ‘ios');
Parse.Push.send({
where: queryIOS,
data: {
alert: "Your suitcase has been filled with tiny robots!"
}
});
Android端末のみに
通知を送る
iOS端末のみに
通知を送る
// Notification for Windows 8 users
var queryWindows = new Parse.Query(Parse.Installation);
queryWindows.equalTo('deviceType', 'winrt');
Parse.Push.send({
where: queryWindows,
data: {
alert: "Your suitcase has been filled with tiny glass!"
}
});
// Notification for Windows Phone 8 users
var queryWindowsPhone = new Parse.Query(Parse.Installation);
queryWindowsPhone.equalTo('deviceType', 'winphone');
Parse.Push.send({
where: queryWindowsPhone,
data: {
alert: "Your suitcase is very hip; very metro."
}
});
WinRT端末のみに
通知を送る
WindowsPhone端末
のみに通知を送る
ParseQuery query = ParseInstallation.getQuery();
query.whereEqualTo("channels", "suitcaseOwners");
// Notification for Android users
query.whereEqualTo("deviceType", "android");
ParsePush androidPush = new ParsePush();
androidPush.setMessage("Your suitcase has been filled with tiny robots!");
androidPush.setQuery(query);
androidPush.sendPushInBackground();
// Notification for iOS users
query.whereEqualTo("deviceType", "ios");
ParsePush iOSPush = new ParsePush();
iOSPush.setMessage("Your suitcase has been filled with tiny apples!");
iOSPush.setQuery(query);
iOSPush.sendPushInBackground();
Targeting by Platform
// Notification for Windows 8 users
query.whereEqualTo("deviceType", "winrt");
ParsePush winPush = new ParsePush();
winPush.setMessage("Your suitcase has been filled with tiny glass!");
winPush.setQuery(query);
winPush.sendPushInBackground();
// Notification for Windows Phone 8
usersquery.whereEqualTo("deviceType", "winphone");
ParsePush wpPush = new ParsePush();
wpPush.setMessage("Your suitcase is very hip; very metro.");
wpPush.setQuery(query);
wpPush.sendPushInBackground();
// Notification for Android users
var androidPush = new ParsePush();
androidPush.Alert = "Your suitcase has been filled with tiny robots!";
androidPush.Query =
from installation in ParseInstallation.Query
where installation.Channels.Contains("suitcaseOwners")
where installation.DeviceType == "android"
select installation;
await androidPush.SendAsync();
// Notification for iOS usersvar iOSPush = new ParsePush();
iosPush.Alert = "Your suitcase has been filled with tiny apples!";
iosPush.Query =
from installation in ParseInstallation.Query
where installation.Channels.Contains("suitcaseOwners")
where installation.DeviceType == "ios"
select installation;
await iosPush.SendAsync();
// …….
PFQuery *query = [PFInstallation query];
[query whereKey:@"channels" equalTo:@"suitcaseOwners"];
// Notification for Android users
[query whereKey:@"deviceType" equalTo:@"android"];
PFPush *androidPush = [[PFPush alloc] init];
[androidPush setMessage:@"Your suitcase has been filled with tiny
robots!"];
[androidPush setQuery:query];
[androidPush sendPushInBackground];
// Notification for iOS users
[query whereKey:@"deviceType" equalTo:@"ios"];
PFPush *iOSPush = [[PFPush alloc] init];[iOSPush
setMessage:@"Your suitcase has been filled with tiny apples!"];
[iOSPush setChannel:@"suitcaseOwners"];
[iOSPush setQuery:query];
[iOSPush sendPushInBackground];
// ……
Pushのスケジューリング
var query = new Parse.Query(Parse.Installation);
query.equalTo('user_id', 'user_123');
Parse.Push.send({
where: query,
data: {
alert: "You previously created a reminder for the game today"
},
push_time: new Date(2015, 5, 10)
}, {
success: function() {
// Push was successful
},
error: function(error) {
// Handle error
}
});
2015年5月10日に、
通知を送るように
スケジュールする
Cloud Code
PUSHは、クラウドからの送出が推奨されている。
Parseでは、デフォールトで利用可能なParse
Objectを通じた、Parse Cloudのデータベース利用
以外にも、明示的にクラウド側のコードを設定できる。
Cloud Codeのセットアップ
$ parse new MyCloudCode
Email: ninja@gmail.com
Password:
1:MyApp
Select an App: 1
$ cd MyCloudCode
-config/
global.json
-cloud/
main.js
-public/
index.html
Cloud Codeのdeploy
Parse.Cloud.define("hello",
function( request, response ) {
response.success("Hello world!");
}
);
$ parse deploy
curl -X POST 
-H "X-Parse-Application-Id: …. " 
-H "X-Parse-REST-API-Key: …." 
-H "Content-Type: application/json" 
-d '{}' 
https://api.parse.com/1/functions/hello
Cloud上の関数に渡される
二つの引数
 request
 params
 user
 response
 success
 error
Cloud Codeの実行 run
Parse.Cloud.run('hello',
{}, {
success: function(result) {
// result is 'Hello world!'
},
error: function(error) {
}
});
ParseCloud.callFunctionInBackground("hello",
new HashMap<String, Object>(),
new FunctionCallback<String>() {
void done(String result, ParseException e) {
if (e == null) {
// result is "Hello world!”
}
}
});
var result =
await
ParseCloud.CallFunctionAsync<IDictionary<string, object>>(
"hello", new Dictionary<string, object>()
);
// result is "Hello world!"
Cloud Codeの実行
[PFCloud
callFunctionInBackground:@"hello"
withParameters:@{}
block:^(NSString *result, NSError *error) {
if (!error) {
// result is @"Hello world!"
}
}];
Cloud Codeサンプル
Parse.Cloud.define("averageStars",
function( request, response ) {
var query = new Parse.Query("Review");
query.equalTo("movie", request.params.movie);
query.find({
success: function(results) {
var sum = 0;
for (var i = 0; i < results.length; ++i) {
sum += results[i].get("stars");
}
response.success(sum / results.length);
},
error: function() {
response.error("movie lookup failed");
}
});
});
映画のレビューの
平均値を計算する
sendPushToUser
Parse.Cloud.define(“sendPushToUser”,
function(request, response) {
var senderUser = request.user;
var recipientUserId = request.params.recipientId;
var message = request.params.message;
// メッセージを送っていいのかのチェックをする (友達にのみ送る)
// ユーザーは、友達の情報を持っている
if (senderUser.get("friendIds").indexOf(recipientUserId) === -1) {
response.error(
"The recipient is not the sender's friend, cannot send push.”
);
}
// メッセージが140文字以内かのチェックをする
if (message.length > 140) {
// 長すぎたら切り詰めて後ろに“...”を置く
message = message.substring(0, 137) + "...";
}
友人にショート
メッセージを送る
// 受け取り手の送り先を調べる
var recipientUser = new Parse.User();
recipientUser.id = recipientUserId;
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.equalTo("user", recipientUser);
// Push通知を行う
Parse.Push.send({
where: pushQuery,
data: {
alert: message
}
}).then(function() {
response.success("Push was sent successfully.")
}, function(error) {
response.error("Push failed to send with error: " + error.message);
});
});
友人にショート
メッセージを送る
ショート・メッセージの送信
[PFCloud callFunctionInBackground:@"sendPushToUser"
withParameters:@{@"recipientId": userObject.id, @"message": message}
block:^(NSString *success, NSError *error) {
if (!error) { // Push sent successfully }
}];
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("recipientId", userObject.getObjectId());
params.put("message", message);
ParseCloud.callFunctionInBackground("sendPushToUser", params,
new FunctionCallback<String>() {
void done(String success, ParseException e) {
if (e == null) {
// Push sent successfully
}
}
});
Cloud Code Trigger
Cloud Codeは、Cloud Code Trigger として、デ
ータの妥当性チェックや、データの再フォーマット、例
外の処理等に利用できる。AOP的な使い方。
beforeSave Triggers
Parse.Cloud.beforeSave("Review",
function(request, response) {
if (request.object.get("stars") < 1) {
response.error("you cannot give less than one star");
} else if (request.object.get("stars") > 5) {
response.error("you cannot give more than five stars");
} else {
response.success();
}
});
Parse.Cloud.beforeSave(Parse.User,
function(request, response) {
if (!request.object.get("email")) {
response.error("email is required for signup");
} else {
response.success();
}
});
妥当性チェック:
星の数は、1から5まで
妥当性チェック:サインアップ
には、e-mailが必要
保存時にデータを変更する
Parse.Cloud.beforeSave("Review",
function(request, response) {
var comment = request.object.get("comment");
if (comment.length > 140) {
// Truncate and add a ...
request.object.set("comment", comment.substring(0, 137) + "...");
}
response.success();
});
コメントの長さを、140文字以内
に切り詰めてから保存する
afterSave Triggers
Parse.Cloud.afterSave("Comment",
function(request) {
query = new Parse.Query("Post");
query.get(request.object.get("post").id, {
success: function(post) {
post.increment("comments");
post.save();
},
error: function(error) {
console.error("Got an error " + error.code + " : " + error.message);
}
});
});
保存後に、コメント数を、
1つ増やす。
beforeDelete Triggers
Parse.Cloud.beforeDelete("Album",
function(request, response) {
query = new Parse.Query("Photo");
query.equalTo("album", request.object.id);
query.count({
success: function(count) {
if (count > 0) {
response.error("Can't delete album if it still has photos.");
} else {
response.success();
}
},
error: function(error) {
response.error("Error " + error.code + " : " + error.message
+ " when getting photo count.");
}
});
});
写真が含まれている
アルバムは削除しない
afterDelete Triggers
Parse.Cloud.afterDelete("Post",
function(request) {
query = new Parse.Query("Comment");
query.equalTo("post", request.object.id);
query.find({
success: function(comments) {
Parse.Object.destroyAll(comments, {
success: function() {},
error: function(error) {
console.error("Error deleting related comments " +
error.code + ": " + error.message);
}
});
},
error: function(error) {
console.error("Error finding related comments " + error.code
+ ": " + error.message);
} });});
全コメント削除
後の、処理
Web技術の新しい展開
Facebookのモバイル・アプリ開発技術は、ダイナミッ
クに進化している。Parse + React, さらには、
ReactのReact Nativeへの進化にも目が離せない
Part III
Part III
Web技術の新しい展開
 MVCとFlux
 Reactとは何か?
 React を、サンプルから学ぶ
 Parse + React
 Mutating Parse Data
 React Native
 Relay and GraphQL
MVCとFlux
FluxでのMVCモデルの見直しは、Reactの前身と言
っていいもの。データの流れは、双方向ではなく、一
方向のものになる。
https://facebook.github.io/flux/docs/overview.html
Facebook Parseの世界
Facebook Parseの世界
Flux: Structure and Data Flow
Flux: Structure and Data Flow
Flux
Reactとは何か?
Reactは、ユーザー・インターフェースを構築するため
のJavaScriptライブラリーである。
Reactは、UIのみにフォーカス
 多くの人は、ReactをMVCのVとして利用している。
Reactは、Viewのみにフォーカスしていて、その他のテ
クノロジーについては、いかなる前提も行っていないため
、既存のプロジェクトに容易に取り入れることができる。
Reactは、DOMを必要としない
 Reactは、DOMという考え方を必要としていない。そのこ
とで、プログラミング・モデルは単純になり、 パフォーマン
スも向上する。
 Reactは、Nodeサーバー上でのレンダリングも可能であ
る。また、React Nativeを使ってネイティブ・アプリにパ
ワーを与えることができる。
Reactでは、データは一方向に流れる
 React は、一方向のreactiveなデータの流れを実装し
ている。これによって、定型的なコードの繰り返しを少なく
し、伝統的なデータのバインディングをわかりやすくする。
React を、サンプルから学ぶ
Reactでは、render メソッドの返り値に、直接、
HTMLライクなタグが書ける。わざわざ、Templateを
書く必要もない。それは、とても分かりやすい。
Reactの Hello World! サンプル
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});
React.render( <CommentBox />,
document.getElementById(‘content’) );
// renderの第二引数は、この描画が行われるマウントポイントである。
renderメソッドとJSX記法
 React コンポーネントは、入力データをとり、表示されるも
のを返す render() メソッドを実装する。次の例では、
XMLに似た、JSXと呼ばれるシンタックスを利用している。
コンポーネントに渡される入力データは、renderから、
this.props.を通じてアクセスできる・
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name} </div>;
}
});
React.render(
<HelloMessage name="John" /> , mountNode);
var HelloMessage = React.createClass({
displayName: "HelloMessage",
render: function() {
return React.createElement(
“div”, null, “Hello ”,this.props.name);
}
});
React.render(React.createElement(
HelloMessage, {name: "John"}), mountNode);
JSXはオプション
 JSXはオプションで、Reactを使う上では必須ではない。
次のようにかける。
コンポーネントを組み合わせる
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
Hello, world! I am a CommentList.
</div>
);
}
});
var CommentForm = React.createClass({
render: function() {
return (
<div className="commentForm">
Hello, world! I am a CommentForm.
</div>
);
}
});
コンポーネントを組み合わせる
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
Hello, world! I am a CommentList.
</div>
);
}
});
var CommentForm = React.createClass({
render: function() {
return (
<div className="commentForm">
Hello, world! I am a CommentForm.
</div>
);
}
});
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList />
<CommentForm />
</div>
);
}
});
このCommentBoxコンポーネントは、
内部で、CommentListとCommentForm
というコンポーネントを利用している。
クラス名は、JSXのタグの名前として利用
できる。
コンポーネントのプロパティ
this.propsを利用する
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});
JSX内部の、{...}に囲まれた部分は、
JavaScriptの値が展開される。
this.propを利用すると、コンポーネント
のプロパティの値が利用できる。
コンポーネントのプロパティを利用する
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
<Comment author="Pete Hunt">
This is one comment
</Comment>
<Comment author="Jordan Walke">
This is *another* comment
</Comment>
</div>
);
}
});
二つのCommentコンポネントが利用
されている。最初のコンポーネントでは、
this.prop.authorは、“Pete Hunt”
で二つ目のコンポーネントでは、その値
は、“Jordan Walker”になる。
データ・モデルと結びつける
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(
function (comment) { return (
<Comment author= {comment.author}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
ここでは、renderの内部で、もう一つの
関数 commentNodesが定義されている
この関数では、mapで、複数個のエントリ
を持つリスト・データが処理される。
データ・モデルと結びつける
var data = [
{author: "Pete Hunt", text: "This is one comment"},
{author: "Jordan Walke", text: "This is *another* comment"}
];
var CommentBox = React.createClass({
render: function() { return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data= {this.props.data} />
<CommentForm />
</div>
); } });
React.render( <CommentBox data= {data} />,
document.getElementById('content') );
状態を持つコンポーネント
var CommentBox = React.createClass({
getInitialState: function() { return {data: []}; },
render: function() { return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data= {this.state.data} />
<CommentForm />
</div>
);
}
});
Reactでは、propsとstateは、
区別されている。stateの変更は、
renderの再描画を引き起こす。
状態を持つコンポーネント
var Timer = React.createClass({
getInitialState: function() { // 状態の初期値の設定
return {secondsElapsed: 0};
},
tick: function() { // 状態が変わるとrenderが呼ばれる
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return ( // 状態は、this.state でアクセスできる
<div>Seconds Elapsed: {this.state.secondsElapsed}</div> );
}
});
React.render(<Timer />, mountNode);
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText, index) {
return <li key={index + itemText}>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>; }
});
var TodoApp = React.createClass({
getInitialState: function() { return {items: [], text: ''};
},
onChange: function(e) { this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
Reactサンプル ToDo アプリ
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.render(<TodoApp />, mountNode);
Parse + React
Parse + React は、React からParse APIへの簡
単なアクセスを提供する Parse JS SDK上のインタ
ーフェース層である。
https://github.com/ParsePlatform/ParseReact
Parse + React とは?
 Parse + React は、 Reactのコンポーネントを Parse
の queryにsubscribe させて、データの変化を、Flux-
styleで伝えることを可能にする。
 オブジェクトの生成や変更の際に、これらのコンポーネン
トが、バックグラウンドで、自動的に更新されるように、こ
れらのsubscriptionは、管理される。
 こうしてユーザーインターフェースは、機敏で反応良いも
のになることができる。
Parse DataへのSubscribe
 Parse dataをReactアプリケーションに持ち込む一番い
いやり方は、ReactのコンポーネントをParseのqueryに
subscribeすることである。
 Parse Queryへのsubscribeには、observeメソッドが
利用される。
 コンポーネントがマウントすると、queryは変わったり、明
示的にリフレッシュされる。その度に、新しいデータが
Parseから取り出されて、コンポーネントに渡され、
renderによるコンポーネントの再描画 が引きおこされる
。
queryの更新
 コンポーネントがマウントした時に、それぞれのqueryが
取り込まれる。それらの結果が受け取られ、コンポーナン
トに付け加えられた時にはいつも、コンポーネントは更新
され、再描画される。
 もし、propsやstateが変わっていれば、subscriptionは
全て再計算される。結果として変化したqueryは、すべて
、再取り込みされる。
 queryは、いつでも、this.refreshQueries()を呼ぶこと
で、明示的にリフレッシュされる。
observe メソッド
 ParseReact.Mixinオブジェクトを mixinのコンポーネ
ントのリストに追加することで、observe() ライフサイクル
関数のサポートが得られるようになる。
 新しく提案された React のlifecycleメソッドである
observe() によって、コンポーネントは queryに、
subscribe される。
 この observeメソッドは、コンポーネントが更新するたび
に、描画の直前に走る。
 直近の propsとstateをパラメーターとして受け取って、
observeは、Parse.Queryオブジェクトを構成して、
queryに対する文字列のmapを返す。この文字列のキー
は、それぞれのqueryを同定する名前として利用される。
observe メソッド
 observe()は、コンポーネントが更新される時、描画の前
に呼ばれる。
 マウント時には、初期値のpropsとstateが関数のパラメ
ーターとして渡される。その後は、最新のpropsとstateが
パラメーターとして渡される。それは、このメソッドが、
componentWillUpdateから呼ばれているからである。
 このメソッドは、key/valueのペアからなるオブジェクトを
返す。それぞれのkeyは、subscriptionの名前を表し、
そのvalueは、Parse.QueryかLocal Subscriptionで
ある。
subscribe sample
 この例では、コンポーネントを、50票以上の投票を集めたコ
メントを、生成日の順に並べた query に subscribe して
いる。それは、comments という名前に関連付けられてい
るので、この結果の集合は、 this.data.comments と
して利用可能になる。.
observe: function( props, state) {
return {
comments: (new Parse.Query('Comment'))
.greaterThan('votes', 50)
.ascending('createdAt')
};
}
Local Subscription
 コンポーネントを、もっとローカルなコンセプトに subscribe
することも可能である。コンポーネントは、
ParseReact.currentUser を通じて、ログインしログアウト
する現在のユーザーに subscribe することができる。次の
例では、this.data.user は、現在のユーザーのコピーに等
しくなるだろう。コンポーネントは、ユーザーが変更された時に
更新される。
observe: function() {
return {
user: ParseReact.currentUser
};
}
observe sample
var CommentBlock = React.createClass({
mixins: [ParseReact.Mixin], // queryへの subscriptionを可能にする
observe: function() {
// 生成順に並べられた全てのCommentオブジェクトにsubscribeする
// その結果は、this.data.comments で利用出来る
return {
comments: (new Parse.Query('Comment')).ascending('createdAt')
}; },
render: function() {
// それぞれのcommentをリストのアイテムとして描画する
return (
<ul>
{this.data.comments.map(function(c) {
return <li>{c.text}</li>;
})}
</ul>
);
} });
 このコンポーネントがマウントされた時にはいつも、
queryが発行されて、その結果が
this.data.comments に加えられる。
 queryが再発行されるか、queryにマッチしたオブジェク
トがローカルに変更されるたびに、それは、こうした変化を
反映するように、自分自身を更新する。
Mutating Parse Data
Parse + React では、Parseと共有されるデータは、
Mutation を通じて変更される。Mutationは、
dispatchされると Parse APIとクライアントのアプリ
の両方に、なんらかの方法でデータを更新せよと伝え
るメッセージである。 https://goo.gl/EPhLPy
mutation.dispatch([options])
 mutation.dispatchは、生成・削除・変更といったParse
オブジェクトの変化を、Parse APIにリクエストを行うこと
で実行する。
 サーバーからのレスポンスを待つことが、明示的に要求さ
れない場合、オブジェクトはローカルに楽観的に更新され
る。オブジェクトが更新された場合、新しいバージョンが、
subscribeされた全てのコンポーネントにプッシュされる。
 dispatch は、Parse.Promiseを返す。
このpromiseは、サーバーのリクエストが完了した時に、
成功裡に解決される。 エラーが起きた場合には、この
promiseは、リジェクトされる。
Data mutation
 データの変化 mutationはFluxのActionのやり方で
dispatch される。それで、多くの異なるコンポーネント同
士が話しかけるviewを要求することなしに、更新を同期
することが可能になる。標準的なParseのデータの変化は
、全てサポートされている。
// Create a new Comment object with some initial data
ParseReact.Mutation.Create('Comment', {
text: 'Parse <3 React’
}).dispatch();
 Mutationを生成するためには、最初に適当なコンストラクタを
呼ばなければならない。これらのメソッドは、Mutations API
で見ることができる。いったん Mutation が生成されれば、それ
は、dispatch()を呼ぶことで実行される。
// Create a new Pizza object
var creator = ParseReact.Mutation.Create('Pizza’, {
toppings: [ 'sausage', 'peppers' ],
crust: 'deep dish'
});
// ...and execute it
creator. dispatch();
多くのMutationでは、変更されるべき特定のオブジェクトを自由
に選べる。それは、Query Subscriptio から受け取るオブジェ
クトであるべきである。次の例は、queryから受け取ったオブ
ジェクトをどのように更新すべきかを示している。
React.createClass({
mixin: [ParseReact.Mixin],
observe: function() {
return {
counters: new Parse.Query('Counter');
};
},
// ...
// _myClickHandler は、コンポーネントがクリックされた時に呼び出されると
// としよう。この時、すべてのCounterオブジェクトの値を増やす。
_myClickHandler: function() {
this.data.counters.map(function(counter) {
ParseReact.Mutation.Increment(counter, 'value').dispatch();
});
}
})
// objectIdが ‘c123’であるCounterを削除したい時は、
// 次のように、情報をMutationに渡せば、削除ができる
var target = { className: 'Counter', objectId: 'c123' };
ParseReact.Mutation.Destroy(target).dispatch();
 queryのsubscriptionから受け取ったものでも、親オブ
ジェクトから受け取ったpropでも、コンポーネントが受け
取ったオブジェクトを変更するのは容易である。
 直接は見ることのできないオブジェクトでも、それを変更す
ることは可能である。そのオブジェクトのclassNameと
objectIdを知っていれば、これらのフィールドを
mutationに渡せば、それを修正することが出来る。
var creator = ParseReact.Mutation.Create('Pizza', {
toppings: [ 'sausage', 'peppers' ],
crust: 'deep dish'
});
// 同じトッピングで、三つの新しいピザを作る
creator. dispatch();
creator. dispatch();
creator. dispatch();
Mutationを、わざわざdispatchする一つの理由は、Mutationを独立
の操作として、後の実行のために保存したり、複数回の実行を可能にす
るためである。
React Native
https://github.com/facebook/react-native
React Native
 React Native は、JavaScriptとReactベースの一貫し
た開発者の経験を利用して、ネイティブなプラットフォーム
上で、世界クラスのアプリを構築することを可能とする。
 React Natieのフォーカスは、開発者が関心を持つ全て
のプラットフォームに渡って、「一度学べば、どこでも書け
る」という、開発者の効率性に置かれている。
 Facebookは、React Native を複数の製品版のアプリ
に利用しており、これからも、React Nativeに投資を続
けていくだろう。
Native iOS Components
 React Nativeで、iOS上での UITabBar や
UINavigationControllerといった標準的なプラットフォ
ームのコンポーネントを利用できる。
 このことで、アプリケーションは、プラットフォームのその他
の部分とのルック・アンド・フィールは整合的になり、アプリ
の品質の水準は、高いものに維持できる。
 これらのコンポーネントは、それらのReactコンポーネント
での対応物である TabBarIOSやNavigatorIOSを利用
することで、容易にアプリと一体化する。
TabBarIOS, NavigatorIOSの利用
var React = require('react-native');
var { TabBarIOS, NavigatorIOS } = React;
var App = React.createClass({
render: function() {
return (
<TabBarIOS>
<TabBarIOS.Item title="React Native" selected={true}>
<NavigatorIOS initialRoute={{ title: 'React Native' }} />
</TabBarIOS.Item>
</TabBarIOS>
);
},
});
非同期実行
 JavaScriptアプリ・コードとナイティブなプラットフォーム
の間の操作は、全て、非同期で実行される。ネイティブな
モジュールは、追加のスレッドも利用できる。このことは、
メインスレッドとは別のスレッドで画像のデコードをしたり、
バックグラウンドでディスクに保存したり、UIを組上げなく
ても、テキストの評価やレイアウトの計算等々が可能とな
ることを意味している。その結果、React Nativeアプリは
、自然になめらかに動き、反応も良いものになる。
 コミュニケーションも完全にシリアライザブルなので、
JavaScriptのデバッグを、完全なアプリを、シミュレータ
ーでも実機でも実際に動かしながら、 Chrome
Developer Toolsで行うことが可能になる。
Facebook Parseの世界
Touch Handling
 iOSは、Web上では一般的な対応物がないような、複雑
なviewの階層でのタッチとやりとりする、非常に強力な
Responder Chain というシステムを持っている。React
Nativeは、同様のresponderシステムを実装し、一切設
定を追加することなく、スクロール・ビューやその他のエレ
メントと統合される、TouchableHighlightといった高レ
ベルのコンポーネントを提供している。
var React = require('react-native');
var { ScrollView, TouchableHighlight, Text } = React;
var TouchDemo = React.createClass({
render: function() {
return (
<ScrollView>
<TouchableHighlight onPress={() => console.log('pressed')}>
<Text>Proper Touch Handling</Text>
</TouchableHighlight>
</ScrollView>
);
},
});
Flexbox and Styling
 viewのレイアウトは、容易なものでなければならない。そ
れが、我々がWebからReact Native に、flexbox レイ
アウトを導入した理由である。flexlayoutは、マージンや
パディングを持った積み重ねられ入れ子にされたboxの
ような、もっとも一般的なUIのレイアウトを構築するのを簡
単にする。
 React Native は、fontWeightのような、Webと共通の
styleをサポートしている。StyleSheetという抽象は、ス
タイルやレイアウトを、それを用いるコンポーネントと一緒
に宣言し、また、内部でそれを適用するのに最適化された
メカニズムを提供している。
var React = require('react-native');
var { Image, StyleSheet, Text, View } = React;
var ReactNative = React.createClass({
render: function() {
return (
<View style={styles.row}>
<Image
source={{uri: 'http://facebook.github.io/react/img/logo_og.png'}}
style={styles.image}
/>
<View style={styles.text}>
<Text style={styles.title}> React Native </Text>
<Text style={styles.subtitle}>
Build high quality mobile apps using React </Text>
</View>
</View>
);
},
});
var styles = StyleSheet.create({
row: { flexDirection: 'row', margin: 40 },
image: { width: 40, height: 40, marginRight: 10 },
text: { flex: 1, justifyContent: 'center'},
title: { fontSize: 11, fontWeight: 'bold' }, subtitle: { fontSize: 10 },});
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界
Facebook Parseの世界

Contenu connexe

Similaire à Facebook Parseの世界

ハンズオン勉強会 はじめてのJavaScriptとSPARQL
ハンズオン勉強会 はじめてのJavaScriptとSPARQLハンズオン勉強会 はじめてのJavaScriptとSPARQL
ハンズオン勉強会 はじめてのJavaScriptとSPARQLTaisuke Fukuno
 
Data apiで実現 進化するwebの世界
Data apiで実現 進化するwebの世界Data apiで実現 進化するwebの世界
Data apiで実現 進化するwebの世界Yuji Takayama
 
Realmの暗号化とAndroid System
Realmの暗号化とAndroid SystemRealmの暗号化とAndroid System
Realmの暗号化とAndroid SystemKeiji Ariyama
 
Building React, Flutter and Blazor development and debugging environment with...
Building React, Flutter and Blazor development and debugging environment with...Building React, Flutter and Blazor development and debugging environment with...
Building React, Flutter and Blazor development and debugging environment with...Shotaro Suzuki
 
Titanium Mobile
Titanium MobileTitanium Mobile
Titanium MobileNaoya Ito
 
JSON Value into Power Automate
JSON Value into Power AutomateJSON Value into Power Automate
JSON Value into Power AutomateTomoyuki Obi
 
.NET の過去、現在、そして未来
.NET の過去、現在、そして未来.NET の過去、現在、そして未来
.NET の過去、現在、そして未来Akira Inoue
 
Active Directoryデータの "大きい整数"
Active Directoryデータの "大きい整数"Active Directoryデータの "大きい整数"
Active Directoryデータの "大きい整数"Michio Koyama
 
20171108 Tech Summit 2017 最新! Windows 10 Fall Creators Update 新機能とアプリケーション開発
20171108 Tech Summit 2017 最新! Windows 10 Fall Creators Update新機能とアプリケーション開発20171108 Tech Summit 2017 最新! Windows 10 Fall Creators Update新機能とアプリケーション開発
20171108 Tech Summit 2017 最新! Windows 10 Fall Creators Update 新機能とアプリケーション開発shinobu takahashi
 
初めての Data API CMS どうでしょう - 仙台編 -
初めての Data API   CMS どうでしょう - 仙台編 -初めての Data API   CMS どうでしょう - 仙台編 -
初めての Data API CMS どうでしょう - 仙台編 -Yuji Takayama
 
Interop2016-openstack-user-group-mizuno
Interop2016-openstack-user-group-mizunoInterop2016-openstack-user-group-mizuno
Interop2016-openstack-user-group-mizunoshintaro mizuno
 
Using Ext Direct with SenchaTouch2
Using Ext Direct with SenchaTouch2Using Ext Direct with SenchaTouch2
Using Ext Direct with SenchaTouch2久司 中村
 
Apache Torqueについて
Apache TorqueについてApache Torqueについて
Apache Torqueについてtako pons
 
初めての Data api cms どうでしょう - 大阪夏の陣
初めての Data api   cms どうでしょう - 大阪夏の陣初めての Data api   cms どうでしょう - 大阪夏の陣
初めての Data api cms どうでしょう - 大阪夏の陣Yuji Takayama
 
Java/Androidセキュアコーディング
Java/AndroidセキュアコーディングJava/Androidセキュアコーディング
Java/AndroidセキュアコーディングMasaki Kubo
 
リクルート式 自然言語処理技術の適応事例紹介
リクルート式 自然言語処理技術の適応事例紹介リクルート式 自然言語処理技術の適応事例紹介
リクルート式 自然言語処理技術の適応事例紹介Recruit Technologies
 
Develop Web Application with Node.js + Express
Develop Web Application with Node.js + ExpressDevelop Web Application with Node.js + Express
Develop Web Application with Node.js + ExpressAkinari Tsugo
 

Similaire à Facebook Parseの世界 (20)

Parse触ってみた
Parse触ってみたParse触ってみた
Parse触ってみた
 
ハンズオン勉強会 はじめてのJavaScriptとSPARQL
ハンズオン勉強会 はじめてのJavaScriptとSPARQLハンズオン勉強会 はじめてのJavaScriptとSPARQL
ハンズオン勉強会 はじめてのJavaScriptとSPARQL
 
Data apiで実現 進化するwebの世界
Data apiで実現 進化するwebの世界Data apiで実現 進化するwebの世界
Data apiで実現 進化するwebの世界
 
Realmの暗号化とAndroid System
Realmの暗号化とAndroid SystemRealmの暗号化とAndroid System
Realmの暗号化とAndroid System
 
Building React, Flutter and Blazor development and debugging environment with...
Building React, Flutter and Blazor development and debugging environment with...Building React, Flutter and Blazor development and debugging environment with...
Building React, Flutter and Blazor development and debugging environment with...
 
Titanium Mobile
Titanium MobileTitanium Mobile
Titanium Mobile
 
JSON Value into Power Automate
JSON Value into Power AutomateJSON Value into Power Automate
JSON Value into Power Automate
 
.NET の過去、現在、そして未来
.NET の過去、現在、そして未来.NET の過去、現在、そして未来
.NET の過去、現在、そして未来
 
RとWeb API
RとWeb APIRとWeb API
RとWeb API
 
Active Directoryデータの "大きい整数"
Active Directoryデータの "大きい整数"Active Directoryデータの "大きい整数"
Active Directoryデータの "大きい整数"
 
20171108 Tech Summit 2017 最新! Windows 10 Fall Creators Update 新機能とアプリケーション開発
20171108 Tech Summit 2017 最新! Windows 10 Fall Creators Update新機能とアプリケーション開発20171108 Tech Summit 2017 最新! Windows 10 Fall Creators Update新機能とアプリケーション開発
20171108 Tech Summit 2017 最新! Windows 10 Fall Creators Update 新機能とアプリケーション開発
 
初めての Data API CMS どうでしょう - 仙台編 -
初めての Data API   CMS どうでしょう - 仙台編 -初めての Data API   CMS どうでしょう - 仙台編 -
初めての Data API CMS どうでしょう - 仙台編 -
 
Interop2016-openstack-user-group-mizuno
Interop2016-openstack-user-group-mizunoInterop2016-openstack-user-group-mizuno
Interop2016-openstack-user-group-mizuno
 
Using Ext Direct with SenchaTouch2
Using Ext Direct with SenchaTouch2Using Ext Direct with SenchaTouch2
Using Ext Direct with SenchaTouch2
 
Apache Torqueについて
Apache TorqueについてApache Torqueについて
Apache Torqueについて
 
Java EE8 Report
Java EE8 ReportJava EE8 Report
Java EE8 Report
 
初めての Data api cms どうでしょう - 大阪夏の陣
初めての Data api   cms どうでしょう - 大阪夏の陣初めての Data api   cms どうでしょう - 大阪夏の陣
初めての Data api cms どうでしょう - 大阪夏の陣
 
Java/Androidセキュアコーディング
Java/AndroidセキュアコーディングJava/Androidセキュアコーディング
Java/Androidセキュアコーディング
 
リクルート式 自然言語処理技術の適応事例紹介
リクルート式 自然言語処理技術の適応事例紹介リクルート式 自然言語処理技術の適応事例紹介
リクルート式 自然言語処理技術の適応事例紹介
 
Develop Web Application with Node.js + Express
Develop Web Application with Node.js + ExpressDevelop Web Application with Node.js + Express
Develop Web Application with Node.js + Express
 

Plus de maruyama097

Convolutionl Neural Network 入門
Convolutionl Neural Network 入門Convolutionl Neural Network 入門
Convolutionl Neural Network 入門maruyama097
 
ContainerとName Space Isolation
ContainerとName Space IsolationContainerとName Space Isolation
ContainerとName Space Isolationmaruyama097
 
ニューラル・ネットワークと技術革新の展望
ニューラル・ネットワークと技術革新の展望ニューラル・ネットワークと技術革新の展望
ニューラル・ネットワークと技術革新の展望maruyama097
 
TensorFlowとCNTK
TensorFlowとCNTKTensorFlowとCNTK
TensorFlowとCNTKmaruyama097
 
Neural Network + Tensorflow 入門講座
Neural Network + Tensorflow 入門講座Neural Network + Tensorflow 入門講座
Neural Network + Tensorflow 入門講座maruyama097
 
機械学習技術の現在+TensolFlow White Paper
機械学習技術の現在+TensolFlow White Paper機械学習技術の現在+TensolFlow White Paper
機械学習技術の現在+TensolFlow White Papermaruyama097
 
Cloud OSの進化を考える
Cloud OSの進化を考えるCloud OSの進化を考える
Cloud OSの進化を考えるmaruyama097
 
機械学習技術の現在
機械学習技術の現在機械学習技術の現在
機械学習技術の現在maruyama097
 
大規模分散システムの現在 -- Twitter
大規模分散システムの現在 -- Twitter大規模分散システムの現在 -- Twitter
大規模分散システムの現在 -- Twittermaruyama097
 
Project Araとものづくりの未来
Project Araとものづくりの未来Project Araとものづくりの未来
Project Araとものづくりの未来maruyama097
 
ハードウェア技術の動向 2015/02/02
ハードウェア技術の動向 2015/02/02ハードウェア技術の動向 2015/02/02
ハードウェア技術の動向 2015/02/02maruyama097
 
Project Araと新しいものづくりのエコシステム
  Project Araと新しいものづくりのエコシステム  Project Araと新しいものづくりのエコシステム
Project Araと新しいものづくりのエコシステムmaruyama097
 
エンタープライズと機械学習技術
エンタープライズと機械学習技術エンタープライズと機械学習技術
エンタープライズと機械学習技術maruyama097
 
人間に出来ること --- 人間 vs 機械 Part I 進化と自然認識
人間に出来ること --- 人間 vs 機械 Part I 進化と自然認識人間に出来ること --- 人間 vs 機械 Part I 進化と自然認識
人間に出来ること --- 人間 vs 機械 Part I 進化と自然認識maruyama097
 
Cyber-Physical Systems とは何か?
Cyber-Physical Systems とは何か?Cyber-Physical Systems とは何か?
Cyber-Physical Systems とは何か?maruyama097
 
Project Araと新しいものづくりのエコシステム
Project Araと新しいものづくりのエコシステムProject Araと新しいものづくりのエコシステム
Project Araと新しいものづくりのエコシステムmaruyama097
 
人間の思考、機械の思考
人間の思考、機械の思考人間の思考、機械の思考
人間の思考、機械の思考maruyama097
 
グローバル・ネットワークの成立とネットワーク・マーケット
グローバル・ネットワークの成立とネットワーク・マーケットグローバル・ネットワークの成立とネットワーク・マーケット
グローバル・ネットワークの成立とネットワーク・マーケットmaruyama097
 

Plus de maruyama097 (20)

Convolutionl Neural Network 入門
Convolutionl Neural Network 入門Convolutionl Neural Network 入門
Convolutionl Neural Network 入門
 
ContainerとName Space Isolation
ContainerとName Space IsolationContainerとName Space Isolation
ContainerとName Space Isolation
 
ニューラル・ネットワークと技術革新の展望
ニューラル・ネットワークと技術革新の展望ニューラル・ネットワークと技術革新の展望
ニューラル・ネットワークと技術革新の展望
 
TensorFlowとCNTK
TensorFlowとCNTKTensorFlowとCNTK
TensorFlowとCNTK
 
Neural Network + Tensorflow 入門講座
Neural Network + Tensorflow 入門講座Neural Network + Tensorflow 入門講座
Neural Network + Tensorflow 入門講座
 
機械学習技術の現在+TensolFlow White Paper
機械学習技術の現在+TensolFlow White Paper機械学習技術の現在+TensolFlow White Paper
機械学習技術の現在+TensolFlow White Paper
 
Cloud OSの進化を考える
Cloud OSの進化を考えるCloud OSの進化を考える
Cloud OSの進化を考える
 
機械学習技術の現在
機械学習技術の現在機械学習技術の現在
機械学習技術の現在
 
大規模分散システムの現在 -- Twitter
大規模分散システムの現在 -- Twitter大規模分散システムの現在 -- Twitter
大規模分散システムの現在 -- Twitter
 
Aurora
AuroraAurora
Aurora
 
Project Araとものづくりの未来
Project Araとものづくりの未来Project Araとものづくりの未来
Project Araとものづくりの未来
 
ハードウェア技術の動向 2015/02/02
ハードウェア技術の動向 2015/02/02ハードウェア技術の動向 2015/02/02
ハードウェア技術の動向 2015/02/02
 
Project Araと新しいものづくりのエコシステム
  Project Araと新しいものづくりのエコシステム  Project Araと新しいものづくりのエコシステム
Project Araと新しいものづくりのエコシステム
 
エンタープライズと機械学習技術
エンタープライズと機械学習技術エンタープライズと機械学習技術
エンタープライズと機械学習技術
 
人間に出来ること --- 人間 vs 機械 Part I 進化と自然認識
人間に出来ること --- 人間 vs 機械 Part I 進化と自然認識人間に出来ること --- 人間 vs 機械 Part I 進化と自然認識
人間に出来ること --- 人間 vs 機械 Part I 進化と自然認識
 
Cyber-Physical Systems とは何か?
Cyber-Physical Systems とは何か?Cyber-Physical Systems とは何か?
Cyber-Physical Systems とは何か?
 
Project Araと新しいものづくりのエコシステム
Project Araと新しいものづくりのエコシステムProject Araと新しいものづくりのエコシステム
Project Araと新しいものづくりのエコシステム
 
人間の思考、機械の思考
人間の思考、機械の思考人間の思考、機械の思考
人間の思考、機械の思考
 
グローバル・ネットワークの成立とネットワーク・マーケット
グローバル・ネットワークの成立とネットワーク・マーケットグローバル・ネットワークの成立とネットワーク・マーケット
グローバル・ネットワークの成立とネットワーク・マーケット
 
Google Dremel
Google DremelGoogle Dremel
Google Dremel
 

Facebook Parseの世界

Notes de l'éditeur

  1. Speaker tips: Summary slide to drive concept home: Use comparison with enumerable interfaces here! Instead of begging for an enumerator, you now give the source an observer… Data flow is reversed: A value of type T is given to you… …reflected as input and hence contravariance in C# 4.0 Implication: Getting stuck in enumerators means getting too little… …which is dual to getting too much or getting flooded for observers! Can compare to subscribing to a news paper When calling subscribe: You give the address of your letter box (the observer) You get a way to undo the subscription in the future (the IDisposable is like a letter to cancel the subscription) Asynchronous nature! You’re not blocked till the next news paper arrives. The observer is the letter box: OnNext happens when the postman drops the newsletter in it OnError happens when someone bombs your letter box or the postman drops dead OnCompleted happens when the newspaper publisher goes bankrupt