Stefano Zanetti § DevCamp
Parse.com
iOS App with Parse.com as RESTful Backend
DevCamp
Stefano Zanetti
Apple iOS Developer
Superpartes Innovation Campus & H-Farm
Co-founder di
# Pragma Mark ― www.pragmamark.org
[tt] @Doh__
[in] Stefano Zanetti
[fb] stefano.znt
[email] zanetti.stefano@gmail.com
DevCamp
What is Parse.com?
The perfect cloud for your apps.
Parse allows your team to focus on creating a
great user experience and forget server
maintenance and complex infrastructure.
DevCamp
What does this mean?
• Backend for apps and websites
• Database NoSQL (schemaless: if you need to store
something, store key/value data without prepare any table)
• Store your app’s data in the cloud
• Parse automatically creates RESTful API for you
• Push notification
• Social
• Hosting
• Cloud code
DevCamp
How much?
BasicBasicBasic ProProPro EnterpriseEnterpriseEnterprise
Great for developer to get startedGreat for developer to get startedGreat for developer to get started For Production applicationsFor Production applicationsFor Production applications For advanced featuresFor advanced featuresFor advanced features
FREEFREEFREE $199/month$199/month$199/month contact Parsecontact Parsecontact Parse
Request Pushes Burst limit Request Pushes Burst limit Request Pushes Burst limit
1 milion/
month
1 milion/
month
20/second
15 milion/
month
5 milion/
month
40/second
contact
Parse
DevCamp
Create & Save an Object
PFObject *testObject = [PFObject
objectWithClassName:@"TestObject"];
[testObject setObject:@"bar" forKey:@"foo"];
[testObject save];
5.Copy and paste next code somewhere in the
project
6.Compile and run
DevCamp
Which libraries Parse needs?
• AudioToolbox.framework
• CFNetwork.framework
• CoreGraphics.framework
• CoreLocation.framework
• libz.1.1.3.dylib
• MobileCoreServices.framework
• QuartzCore.framework
• Security.framework
• StoreKit.framework
• SystemConfiguration.framework
• AdSupport.framework (optional if iOS targetting is than then
6.0)
• Social.framework (optional if iOS targetting is less than 6.0)
• Accounts.framework (optional if iOS targetting is less than 6.0)
If you're targeting iOS versions less than 5.0, you'll need to add the "-fobjc-
arc" flag to the "Other Linker Flags" entry in your target build settings.
DevCamp
Saving/Updating Objects
PFObject *post = [PFObject objectWithClassName:@"Post"];
[post setObject:@"New post" forKey:@"title"];
[post setObject:@"This is my first message" forKey:@"message"];
[post setObject:[NSNumber numberWithBool:NO] forKey:@"visible"];
[post save];
• PFObject contains key-value pairs of JSON-
compatible data.
• This data is schemaless
• Interface is similar to NSMutableDictionary
DevCamp
Check Data Browser
• You don't have to configure or set up a new Class
• You don't need to specify a key for the object you are saving
Parse automatically fills 3 field:
• objectId
• createdAt
• updatedAt
objectId: "xWMyZ4YEGZ", title: "New post", message: "This is my
first message", visible: false, createdAt:"2011-06-10T18:33:42Z",
updatedAt:"2011-06-10T18:33:42Z"
DevCamp
Retriving Data
Using PFQuery you can retrive a PFObject
PFQuery *query = [PFQuery queryWithClassName:@"Post"];
PFObject *post = [query getObjectWithId:@"xWMyZ4YEGZ"];
NSString *postTitle = [post objectForKey:@"title"];
BOOL visible = [[post objectForKey:@"visible"] boolValue];
DevCamp
Note!!
The three special values are provided as properties:
NSString *objectId = post.objectId;
NSDate *updatedAt = post.updatedAt;
NSDate *createdAt = post.createdAt;
If you need to refresh an object you already have with the
latest data that is in the Parse Cloud, you can call the
refresh method like so:
[myObject refresh];
DevCamp
Blocks
If you want to run code when operation is completed you
can use blocks (iOS 4.0+) or callbacks methods.
[post saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
// The Post saved successfully.
} else {
// There was an error saving the Post.
}
}];
DevCamp
Callbacks
// First set up a callback.
- (void)saveCallback:(NSNumber *)result error:(NSError *)error {
if (!error) {
// The Post saved successfully.
} else {
// There was an error saving the Post.
}
}
// Then, elsewhere in your code...
[post saveInBackgroundWithTarget:self
selector:@selector(saveCallback:error:)];
DevCamp
Load in background
PFQuery *query = [PFQuery queryWithClassName:@"Post"];
[query getObjectInBackgroundWithId:@"xWMyZ4YEGZ"
block:^(PFObject *post, NSError
*error) {
if (!error) {
// The get request succeeded. Log the score
NSLog(@"The title is: %d", [[post objectForKey:@"title"]
intValue]);
} else {
// Log details of our failure
NSLog(@"Error: %@ %@", error, [error userInfo]);
}
}];
With Blocks
DevCamp
Load in background
// First set up a callback.
- (void)getCallback:(PFObject *)post error:(NSError *)error {
if (!error) {
// The get request succeeded. Log the score
NSLog(@"The title is: %d", [[post objectForKey:@"title"] intValue]);
} else {
// Log details of our failure
NSLog(@"Error: %@ %@", error, [error userInfo]);
}
}
// Then, elsewhere in your code...
PFQuery *query = [PFQuery queryWithClassName:@"Post"];
[query getObjectInBackgroundWithId:@"xWMyZ4YEGZ"
target:self
selector:@selector(getCallback:error:)];
With CallBacks
DevCamp
Saving Objects Offline
Just call saveEventually method and system store the update
on the device until a network connection is available
[post saveEventually];
DevCamp
Saving Counter Objects
The “likes” field is a counter:
[post incrementKey:@"likes"];
[post saveInBackground];
or
[post incrementKey:@"likes" byAmount:3];
[post saveInBackground];
DevCamp
Saving Array Objects
• addObject:forKey: and addObjectsFromArray:forKey: append the given
objects to the end of an array field.
• addUniqueObject:forKey: and addUniqueObjectsFromArray:forKey: add only
the given objects which aren't already contained in an array field to that
field.The position of the insert is not guaranteed.
• removeObject:forKey: and removeObjectsInArray:forKey: remove all
instances of each given object from an array field.
[post addUniqueObjectsFromArray:[NSArray
arrayWithObjects:@"stefano", @"massimo", nil] forKey:@"users"];
[post saveInBackground];
DevCamp
Delete Objects
To delete an object from the cloud:
[myObject deleteInBackground];
To delete e single field from an Object:
// After this, the visilble field will be empty
[myObject removeObjectForKey:@"visible"];
// Saves the field deletion to the Parse Cloud
[myObject saveInBackground];
DevCamp
One-To-Many Relationship
// Create the post
PFObject *myPost = [PFObject objectWithClassName:@"Post"];
[myPost setObject:@"I'm Hungry" forKey:@"title"];
[myPost setObject:@"Where should we go for lunch?" forKey:@"content"];
// Create the comment
PFObject *myComment = [PFObject objectWithClassName:@"Comment"];
[myComment setObject:@"Let's do Sushirrito." forKey:@"content"];
// Add a relation between the Post and Comment
[myComment setObject:myPost forKey:@"parent"];
// This will save both myPost and myComment
[myComment saveInBackground];
You can link objects:
DevCamp
One-To-Many Relationship
You can also link objects using just their objectIds like so:
// Add a relation between the Post with objectId "1zEcyElZ80" and
the comment
[myComment setObject:[PFObject
objectWithoutDataWithClassName:@"Post" objectId:@"1zEcyElZ80"]
forKey:@"parent"];
DevCamp
One-To-Many Relationship
By default, when fetching an object, related
PFObjects are not fetched:
PFObject *post = [fetchedComment objectForKey:@"parent"];
[post fetchIfNeededInBackgroundWithBlock:^(PFObject *object,
NSError *error) {
NSString *title = [post objectForKey:@"title"];
}];
DevCamp
Many-To-Many Relationship
A User may have many Posts that they might like.
PFUser *user = [PFUser currentUser];
PFRelation *relation = [user relationforKey:@"likes"];
[relation addObject:post];
[user saveInBackground];
Add relation:
[relation removeObject:post];
Remove relation:
DevCamp
Many-To-Many Relationship
By default, the list of objects in this relation
are not downloaded
[[relation query] findObjectsInBackgroundWithBlock:^(NSArray
*objects, NSError *error) {
if (error) {
// There was an error
} else {
// objects has all the Posts the current user liked.
}
}];
DevCamp
Many-To-Many Relationship
If you want only a subset of the Posts you can add
extra constraints to the PFQuery returned by query
PFQuery *query = [relation query];
// Add other query constraints.
DevCamp
Basic Queries
PFQuery *query = [PFQuery queryWithClassName:@"Post"];
[query whereKey:@"title" equalTo:@"pragmamark"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError
*error) {
if (!error) {
// The find succeeded.
NSLog(@"Successfully retrieved %d scores.", objects.count);
} else {
// Log details of the failure
NSLog(@"Error: %@ %@", error, [error userInfo]);
}
}];
The general pattern is to create a PFQuery, put conditions on it, and then retrieve a
NSArray of matching PFObjects using either findObjectsInBackgroundWithBlock: or
findObjectsInBackgroundWithTarget:selector:
DevCamp
Basic Queries
// Only use this code if you are already running it in a background
// thread, or for testing purposes!
PFQuery *query = [PFQuery queryWithClassName:@"Post"];
[query whereKey:@"title" equalTo:@"pragmamark"];
NSArray* scoreArray = [query findObjects];
If you are already in a background thread:
DevCamp
NSPredicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"title = 'pragmamark'"];
PFQuery *query = [PFQuery queryWithClassName:@"Post" predicate:predicate];
These features are supported:
• Simple comparisons such as =, !=, <, >, <=, >=, and BETWEEN with a key and a constant.
• Containment predicates, such as x IN {1, 2, 3}.
• Key-existence predicates, such as x IN SELF.
• BEGINSWITH expressions.
• Compound predicates with AND, OR, and NOT.
• Sub-queries with "key IN %@", subquery.
The following types of predicates are not supported:
• Aggregate operations, such as ANY, SOME,ALL, or NONE.
• Regular expressions, such as LIKE, MATCHES, CONTAINS, or ENDSWITH.
• Predicates comparing one key to another.
• Complex predicates with many ORed clauses.
DevCamp
Query Constraints
[query whereKey:@"playerName" notEqualTo:@"Michael Yabuti"];
[query whereKey:@"playerAge" greaterThan:[NSNumber numberWithInt:18]];
query.limit = 10; // limit to at most 10 results
query.skip = 10; // skip the first 10 results
// Sorts the results in ascending order by the score field
[query orderByAscending:@"score"];
// Sorts the results in descending order by the score field
[query orderByDescending:@"score"];
// Restricts to wins < 50
[query whereKey:@"wins" lessThan:[NSNumber numberWithInt:50]];
// Restricts to wins <= 50
[query whereKey:@"wins" lessThanOrEqualTo:[NSNumber numberWithInt:50]];
// Restricts to wins > 50
[query whereKey:@"wins" greaterThan:[NSNumber numberWithInt:50]];
// Restricts to wins >= 50
[query whereKey:@"wins" greaterThanOrEqualTo:[NSNumber numberWithInt:50]];
DevCamp
Constraints on Array
// Finds scores from anyone who is neither Jonathan, Dario, nor
Shawn
NSArray *names = [NSArray arrayWithObjects:@"Jonathan Walsh",
@"Dario Wunsch",
@"Shawn Simon",
nil];
[query whereKey:@"playerName" notContainedIn:names];
or
[query whereKey:@"playerName" containedIn:names];
If you want to retrieve objects matching several different values
DevCamp
Constraints on Array
// Find objects where the array in arrayKey contains 2.
[query whereKey:@"arrayKey" equalTo:[NSNumber numberWithInt:2]];
For keys with an array type, you can find objects where the key's
array value contains 2 by:
// Find objects where the array in arrayKey contains each of the
// elements 2, 3, and 4.
[query whereKey:@"arrayKey" containsAllObjectsInArray:@[@2, @3,
@4]];
You can also find objects where the key's array value contains
each of the values 2, 3, and 4 with the following:
DevCamp
Check particular key
// Finds objects that have the score set
[query whereKeyExists:@"score"];
// Finds objects that don't have the score set
[query whereKeyDoesNotExist:@"score"];
If you want to retrieve objects that have a particular key set or not:
DevCamp
Matches Key
PFQuery *teamQuery = [PFQuery queryWithClassName:@"Team"];
[teamQuery whereKey:@"winPct" greaterThan:[NSNumber withDouble:
0.5]];
PFQuery *userQuery = [PFQuery queryForUser];
[userQuery whereKey:@"hometown" matchesKey:@"city"
inQuery:teamQuery];
[userQuery findObjectsInBackgroundWithBlock:^(NSArray *results,
NSError *error) {
// results will contain users with a hometown team with a
winning record
}];
You can use the whereKey:matchesKey:inQuery: method to get objects
where a key matches the value of a key in a set of objects resulting from
another query. (use whereKey:doesNotMatchKey:inQuery: to get objects
where a key does not match)
DevCamp
Restrict the returned fields
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"];
[query selectKeys:@[@"playerName", @"score"]];
NSArray *results = [query findObjects];
The remaining fields can be fetched later by calling one of the
fetchIfNeeded variants on the returned objects:
PFObject *object = (PFObject*)[results objectAtIndex:0];
[object fetchIfNeededInBackgroundWithBlock:^(PFObject *object,
NSError *error) {
// all fields of the object will now be available here.
}];
DevCamp
Queries on string value
// Finds barbecue sauces that start with "Big Daddy's".
PFQuery *query = [PFQuery queryWithClassName:@"BarbecueSauce"];
[query whereKey:@"name" hasPrefix:@"Big Daddy's"];
DevCamp
Relational queries
// Assume PFObject *myPost was previously created.
PFQuery *query = [PFQuery queryWithClassName:@"Comment"];
[query whereKey:@"post" equalTo:myPost];
[query findObjectsInBackgroundWithBlock:^(NSArray *comments,
NSError *error) {
// comments now contains the comments for myPost
}];
If you want to retrieve objects where a field matches a particular PFObject,
you can use whereKey:equalTo: just like for other data types.
[query whereKey:@"post"
equalTo:[PFObject objectWithoutDataWithClassName:@"Post"
objectId:@"1zEcyElZ80"]];
DevCamp
Matches Query
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
}];
If you want to retrieve objects where a field contains (or not) a PFObject
that match a different query, you can use whereKey:matchesQuery: (or
whereKey:notMatchQuery:)
Note that the default limit of 100 and maximum limit of 1000
apply to the inner query as well.
DevCamp
Include Key
PFQuery *query = [PFQuery queryWithClassName:@"Comment"];
// Retrieve the most recent ones
[query orderByDescending:@"createdAt"];
// Only retrieve the last ten
query.limit = [NSNumber numberWithInt:10];
// Include the post data with each comment
[query includeKey:@"post"];
[query findObjectsInBackgroundWithBlock:^(NSArray *comments, NSError *error) {
// Comments now contains the last ten comments, and the "post" field
// has been populated. For example:
for (PFObject *comment in comments) {
// This does not require a network access.
PFObject *post = [comment objectForKey:@"post"];
NSLog(@"retrieved related post: %@", post);
}
}];
If you want to return multiple types of related objects in one query use
includeKey: method:
DevCamp
Caching Policy
query.cachePolicy = kPFCachePolicyNetworkElseCache;
The default query behavior doesn't use the cache, but you can enable
caching by setting query.cachePolicy.
• kPFCachePolicyIgnoreCache: is the default cache policy.
• kPFCachePolicyCacheOnly :The query only loads from the cache, ignoring the network. If there are
no cached results, that causes a PFError.
• kPFCachePolicyNetworkOnly:The query does not load from the cache, but it will save results to
the cache.
• kPFCachePolicyCacheElseNetwork:The query first tries to load from the cache, but if that fails, it
loads results from the network. If neither cache nor network succeed, there is a PFError.
• kPFCachePolicyNetworkElseCache:The query first tries to load from the network, but if that
fails, it loads results from the cache. If neither network nor cache succeed, there is a PFError.
• kPFCachePolicyCacheThenNetwork:The query first loads from the cache, then loads from the
network. In this case, the callback will actually be called twice - first with the cached results, then with the
network results. Since it returns two results at different times, this cache policy cannot be used
synchronously with findObjects.
DevCamp
Control the cache’s behavior
BOOL isInCache = [query hasCachedResult];
Check to see if there is a cached result for the query with:
[query clearCachedResult];
Remove any cached results for a query with:
[PFQuery clearAllCachedResults];
Remove cached results for queries with:
query.maxCacheAge = 60 * 60 * 24; // One day, in seconds.
Control the maximum age of a cached result with:
DevCamp
Counting Objects
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"];
[query whereKey:@"playername" equalTo:@"Sean Plott"];
[query countObjectsInBackgroundWithBlock:^(int count, NSError
*error) {
if (!error) {
// The count request succeeded. Log the count
NSLog(@"Sean has played %d games", count);
} else {
// The request failed
}
}];
If you just need to count how many objects match a query, but you do not
need to retrieve the objects that match, you can use countObjects instead of
findObjects.
DevCamp
Compound Queries
PFQuery *lotsOfWins = [PFQuery queryWithClassName:@"Player"];
[lotsOfWins whereKey:@"wins" greaterThan:[NSNumber numberWithInt:
150]];
PFQuery *fewWins = [PFQuery queryWithClassName:@"Player"];
[fewWins whereKey:@"wins" lessThan:[NSNumber numberWithInt:5]];
PFQuery *query = [PFQuery orQueryWithSubqueries:[NSArray
arrayWithObjects:fewWins,lotsOfWins,nil]];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError
*error) {
// results contains players with lots of wins or only a few wins.
}];
If you want to find objects that match one of several queries, you can use
orQueryWithSubqueries: method.
DevCamp
Subclassing PFObject
1.Declare a subclass which conforms to the PFSubclassing protocol.
2.Implement the class method parseClassName.This is the string you would
pass to initWithClassName: and makes all future class name references
unnecessary.
3.Import PFObject+Subclass in your .m file.This implements all methods in
PFSubclassing beyond parseClassName.
4.Call [YourClass registerSubclass] in your ApplicationDelegate before
Parse setApplicationId:clientKey:.
DevCamp
Example of Subclassing
// Armor.h
@interface Armor : PFObject<PFSubclassing>
+ (NSString *)parseClassName;
@end
// Armor.m
// Import this header to let Armor know that PFObject privately provides most
// of the methods for PFSubclassing.
#import <Parse/PFObject+Subclass.h>
@implementation Armor
+ (NSString *)parseClassName {
return @"Armor";
}
@end
// AppDelegate.m
#import <Parse/Parse.h>
#import "Armor.h"
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[Armor registerSubclass];
[Parse setApplicationId:parseAppId clientKey:parseClientKey];
}
DevCamp
Why PFFile?
PFFile lets you store application files in the cloud that would otherwise be
too large or cumbersome to fit into a regular PFObject. (up to 10 megabytes)
DevCamp
Saving a file on the cloud
Then you can associate a PFFile onto a PFObject just like any other piece of
data:
NSData *data = [@"Working at Parse is great!"
dataUsingEncoding:NSUTF8StringEncoding];
PFFile *file = [PFFile fileWithName:@"resume.txt" data:data];
[file saveInBackground];
PFObject *jobApplication = [PFObject
objectWithClassName:@"JobApplication"]
[jobApplication setObject:@"Joe Smith" forKey:@"applicantName"];
[jobApplication setObject:file
forKey:@"applicantResumeFile"];
[jobApplication saveInBackground];
DevCamp
Upload / Download
NSData *data = [@"Working at Parse is great!"
dataUsingEncoding:NSUTF8StringEncoding];
PFFile *file = [PFFile fileWithName:@"resume.txt" data:data];
[file saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// Handle success or failure here ...
} progressBlock:^(int percentDone) {
// Update your progress spinner here. percentDone will be between
0 and 100.
}];
Use saveInBackgroundWithBlock:progressBlock: and
getDataInBackgroundWithBlock:progressBlock:
DevCamp
SignUp / SignIn
PFUser *user = [PFUser user];
user.username = @"my name";
user.password = @"my pass";
user.email = @"email@example.com";
// other fields can be set just like with PFObject
[user setObject:@"415-392-0202" forKey:@"phone"];
[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
// Hooray! Let them use the app now.
} else {
NSString *errorString = [[error userInfo] objectForKey:@"error"];
// Show the errorString somewhere and let the user try again.
}
}];
[PFUser logInWithUsernameInBackground:@"myname" password:@"mypass"
block:^(PFUser *user, NSError *error) {
if (user) {
// Do stuff after successful login.
} else {
// The login failed. Check error to see why.
}
}]
DevCamp
Current User
PFUser *currentUser = [PFUser currentUser];
To get the current logged user you use the cached currentUser object:
[PFUser logOut];
You can clear the current user by logging them out:
DevCamp
Security
To limit access to some users you have many possibility:
• ACLWithUser:
• setReadAccess:forUser: or setWriteAccess:forUser:
• setPublicReadAccess: or setPublicWriteAccess:
• setDefaultACL:withAccessForCurrentUser:
DevCamp
ACL methods
PFObject *groupMessage = [PFObject objectWithClassName:@"Message"];
PFACL *groupACL = [PFACL ACL];
// userList is an NSArray with the users we are sending this
message to.
for (PFUser *user in userList) {
[groupACL setReadAccess:YES forUser:user];
[groupACL setWriteAccess:YES forUser:user];
}
groupMessage.ACL = [PFACL ACLWithUser:[PFUser currentUser]];
[groupACL setPublicReadAccess:YES];
groupMessage.ACL = groupACL;
[groupMessage saveInBackground];
DevCamp
Default ACL
To help ensure that your users' data is secure by default, you
can set a default ACL:
// data is visible to the world (readonly)
PFACL *defaultACL = [PFACL ACL];
[defaultACL setPublicReadAccess:YES];
[PFACL setDefaultACL:defaultACL withAccessForCurrentUser:YES];
// data is only accessible by the user itself
[PFACL setDefaultACL:[PFACL ACL] withAccessForCurrentUser:YES];
DevCamp
Grouping users
Roles provide a logical way of grouping users with common access privileges
to your Parse data:
• Administrator
• User
• Guest
• ...
DevCamp
Create Role
// By specifying no write privileges for the ACL, we can ensure the role
cannot be altered.
PFACL *roleACL = [PFACL ACL];
[roleACL setPublicReadAccess:YES];
PFRole *role = [PFRole roleWithName:@"Administrator" acl:roleACL];
[role saveInBackground];
PFRole *role = [PFRole roleWithName:roleName acl:roleACL];
for (PFUser *user in usersToAddToRole) {
[role.users addObject:user];
}
[role saveInBackground];
DevCamp
Hierarchy
PFRole *administrators = /* Your "Administrators" role */;
PFRole *moderators = /* Your "Moderators" role */;
[moderators.roles addObject:administrators];
[moderators saveInBackground];
Any user with Administrator privileges should also be granted the
permissions of any Moderator:
DevCamp
Best practise
[PFUser enableAutomaticUser];
PFACL *defaultACL = [PFACL ACL];
// Optionally enable public read access while disabling public
write access.
// [defaultACL setPublicReadAccess:YES];
[PFACL setDefaultACL:defaultACL withAccessForCurrentUser:YES];
• restrict access to data as much as possible
• specify a default ACL based upon the current user
DevCamp
Push notitification
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData
*)deviceToken
{
// Store the deviceToken in the current Installation and save it to
Parse.
PFInstallation *currentInstallation = [PFInstallation
currentInstallation];
[currentInstallation setDeviceTokenFromData:deviceToken];
[currentInstallation saveInBackground];
}
Every Parse application installed on a device registered for push
notifications has an associated Installation object.The Installation object is
where you store all the data needed to target push notifications.
DevCamp
Installation
While it is possible to modify a PFInstallation just like you would a PFObject, there are several special
fields that help manage and target devices.
• badge:The current value of the icon badge for iOS apps. Changing this value on the PFInstallation
will update the badge value on the app icon. Changes should be saved to the server so that they
will be used for future badge-increment push notifications.
• channels:An array of the channels to which a device is currently subscribed.
• timeZone:The current time zone where the target device is located.This value is synchronized
every time anInstallation object is saved from the device (readonly).
• deviceType:The type of device, "ios", "android", "winrt", "winphone", or "dotnet"(readonly).
• installationId: Unique Id for the device used by Parse (readonly).
• deviceToken:The Apple generated token used for iOS devices (readonly).
• channelUris:The Microsoft-generated push URIs for Windows devices (readonly).
• appName:The display name of the client application to which this installation belongs (readonly).
• appVersion:The version string of the client application to which this installation belongs
(readonly).
• parseVersion:The version of the Parse SDK which this installation uses (readonly).
• appIdentifier:A unique identifier for this installation's client application. In iOS, this is the Bundle
Identifier(readonly).
DevCamp
Channels
// When users indicate they are pragmamark fans, we subscribe them
to that channel.
PFInstallation *currentInstallation = [PFInstallation
currentInstallation];
// When users indicate they are no longer Giants fans, we
unsubscribe them.
// [currentInstallation removeObject:@”pragmamark”
forKey:@”channels”];
[currentInstallation addUniqueObject:@"pragmamark"
forKey:@"channels"];
[currentInstallation saveInBackground];
This allows you to use a publisher-subscriber model for sending pushes.
Devices start by subscribing to one or more channels, and notifications can
later be sent to these subscribers.
DevCamp
Sending Pushes to channel
// Send a notification to all devices subscribed to the "Giants"
channel.
PFPush *push = [[PFPush alloc] init];
[push setChannel:@"pragmamark"];
// [push setChannels:@[@”pragmamark”, @”devcamp”]];
[push setMessage:@"PragmaDevCamp is coming!!"];
[push sendPushInBackground];
DevCamp
Sending Pushes to queries
// Create our Installation query
PFQuery *pushQuery = [PFInstallation query];
[pushQuery whereKey:@"local" equalTo:@"IT"];
// Send push notification to query
PFPush *push = [[PFPush alloc] init];
[push setQuery:pushQuery]; // Set our Installation query
[push setMessage:@"Ancora pochi giorni per registrarsi al
DevCamp"];
[push sendPushInBackground];
DevCamp
Customizing
• alert: the notification's message.
• badge: (iOS only) the value indicated in the top right corner of the app icon.This can be
set to a value or toIncrement in order to increment the current value by 1.
• sound: (iOS only) the name of a sound file in the application bundle.
• content-available: (iOS only) if you are using Newsstand, set this value to 1 to trigger a
background download.
• action: (Android only) the Intent should be fired when the push is received. If not title
or alert values are specified, the Intent will be fired but no notification will appear to
the user.
• title: (Android & Windows 8 only) the value displayed in the Android system tray or
Windows toast notification.
DevCamp
Customizing example
// Create date object for tomorrow
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:2013];
[comps setMonth:5];
[comps setDay:30];
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 expireAfterTimeInterval: 60 * 60 * 24 * 7] - 1 week
[push setQuery:everyoneQuery];
[push setMessage:@"Season tickets on sale until May 30th"];
[push sendPushInBackground];