This page covers a range of topics to serve as a primer for building data-driven iOS applications with Parse. Some of the topics covered here are:
Parse is a service that allows app developers to effortlessly add a backend to their apps without worrying about server setup and maintenance. With some basic setup, one is able to get a database and RESTful API up and running. Additionally, it is easy to further add push notification and social integration capabilities in your app.
In covering Parse SDK functionality, we will be using example of building an Instagram like application from ground up (i.e. with your own backend). In each of the following section we will following use cases to build a basic version of the application:
The final application should look something like following:
Below is a quick overview of the app architecture:
PFQuery
(More details)PFObject
and it's methods. (More details)An important aspect of the any data driven application is to be able to design the data schema. This helps in data organization so that related data is stored in to a single table or class (in Parse
terminology) and non-related data is stored into different tables or classes.
For this app, we will be using just 1 additional data class (represented by PFObject
) apart from _User
class that Parse SDK created when first user is registered. _User
is specifically reserved for PFUser
and we can not change the class name for PFUser
. However, we do get to name other classes that store the data. This class is will be called Post
and will have following fields:
1. objectId: unique id for the user post (default field)
2. author: image author
3. image: image that user posts
4. caption: image caption by author
5. commentsCount: number of comments that has been posted to an image
6. likesCount: number of likes for the post
7. createdAt: date when post is created (default field)
8. updatedAt: date when post is last updated (default field)
9. ACL: object permissions (default field) (out of the scope for this tutorial)
*default field - created automatically by parse SDK, no need to explicitly specify in PFObject
for the class
Here is how data model would look like: (field_name
: field_type
)
Post
and _User
data classes should be enough to store data based on use cases mentioned above.
Parse used to provide a hosted version of their backend service with free registration. But as of February 2016, Parse has stopped new account registration as they have announced that the service will be shutdown on January 28th, 2017. They have open sourced their server code which you can host by yourself and use it as the backend to your application.
Please refer to this guide for instructions on how to host and configure your own Parse server.
See this section on the client SDK integration.
PFUser
)Parse provides a specialized user class called PFUser
that automatically handles much of the functionality required for user account management. With this class, you'll be able to add user account functionality in your app.
PFUser
has several properties but following are the most important ones:
One of the basic use cases for user management is to have them sign up. Once you have setup a view (in storyboard) and created a corresponding view controller (as shown in below image):
The following code snippet shows how to sign up user:
func registerUser() {
// initialize a user object
let newUser = PFUser()
// set user properties
newUser.username = usernameField.text
newUser.email = emailField.text
newUser.password = passwordField.text
// call sign up function on the object
newUser.signUpInBackground { (success: Bool, error: Error?) in
if let error = error {
print(error.localizedDescription)
} else {
print("User Registered successfully")
// manually segue to logged in view
}
}
}
- (void)registerUser {
// initialize a user object
PFUser *newUser = [PFUser user];
// set user properties
newUser.username = self.usernameField.text;
newUser.email = self.emailField.text;
newUser.password = self.passwordField.text;
// call sign up function on the object
[newUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError * error) {
if (error != nil) {
NSLog(@"Error: %@", error.localizedDescription);
} else {
NSLog(@"User registered successfully");
// manually segue to logged in view
}
}];
}
signUpInBackgroundWithBlock
signs up the user asynchronously. This will also enforce that the username isn't already taken. (Warning: Make sure that password and username are set before calling this method.)Parameters:
block
- The block to execute.
Parse User Sign-Up documentation.
Once the user has signed up, next step is to have them log in to your app. The image below shows the Xcode setup to do the same, followed by code snippet for user login (note that the username you enter is case sensitive).
func loginUser() {
let username = usernameLabel.text ?? ""
let password = passwordLabel.text ?? ""
PFUser.logInWithUsername(inBackground: username, password: password) { (user: PFUser?, error: Error?) in
if let error = error {
print("User log in failed: \(error.localizedDescription)")
} else {
print("User logged in successfully")
// display view controller that needs to shown after successful login
}
}
}
- (void)loginUser {
NSString *username = self.usernameField.text;
NSString *password = self.passwordField.text;
[PFUser logInWithUsernameInBackground:username password:password block:^(PFUser * user, NSError * error) {
if (error != nil) {
NSLog(@"User log in failed: %@", error.localizedDescription);
} else {
NSLog(@"User logged in successfully");
// display view controller that needs to shown after successful login
}
}];
}
??
used in above code snippet is a nil coalescing operator
logInWithUsernameInBackground
- Makes an asynchronous request to log in a user with specified credentials.
Returns an instance of the successfully logged in PFUser
. This also caches the user locally so that calls to PFUser.current()
(in Objective-C, [PFUser current]
) will use the latest logged in user.Parse User Login documentation.
Once a user successfully logs into your application, Parse caches the logged in user object for convenient access throughout your application. PFUser.current()
can be used to get the current user. You can use this functionality in AppDelegate
to check if there is a current user in Parse cache or not. If there is a cached user already present then you can directly load the Home view controller (without asking the user to login again).
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Code to initialize Parse
// (See above section 'Parse `initializeWithConfiguration` vs `setApplicationId`', if you have not already set it up)
// check if user is logged in.
if PFUser.current() != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// view controller currently being set in Storyboard as default will be overridden
window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "AuthenticatedViewController")
}
return true
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Code to initialize Parse
// (See above section 'Parse `initializeWithConfiguration` vs `setApplicationId`', if you have not already set it up)
if (PFUser.currentUser) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
self.window.rootViewController = [storyboard instantiateViewControllerWithIdentifier:@"AuthenticatedViewController"];
}
return YES;
}
🚨 If you get an error about -[AppDelegate window]: unrecognized selector sent to instance
, you will need to move the above code to your SceneDelegate file instead, as shown below...
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Code to initialize Parse
// (See above section 'Parse `initializeWithConfiguration` vs `setApplicationId`', if you have not already set it up)
// check if user is logged in.
if PFUser.current() != nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// view controller currently being set in Storyboard as default will be overridden
window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "AuthenticatedViewController")
}
return true
}
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
// Code to initialize Parse
// (See above section 'Parse `initializeWithConfiguration` vs `setApplicationId`', if you have not already set it up)
if (PFUser.currentUser) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
self.window.rootViewController = [storyboard instantiateViewControllerWithIdentifier:@"AuthenticatedViewController"];
}
return YES;
}
You can clear the current user by calling following class function on PFUser
:
PFUser.logOutInBackgroundWithBlock { (error: NSError?) in
// PFUser.current() will now be nil
}
[PFUser logOutInBackgroundWithBlock:^(NSError * _Nullable error) {
// PFUser.current() will now be nil
}];
The above code should be added to the action associated with the logout button (or any other event needs to log out current user)
PFObject
) & Parse Files (PFFileObject
)PFObject
Storing data on Parse is built around the ParseObject
. Each ParseObject
contains key-value pairs of JSON-compatible data. This data is schemaless, which means that you don't need to specify ahead of time what keys exist on each ParseObject
. You simply set whatever key-value pairs you want, and Parse backend will store it.
Each ParseObject
has a class name that you can use to distinguish different sorts of data. For example, in case of our application, we might call ParseObject
to store uploaded images with name Post
:
import Parse
var shield = PFObject(className: "Armor")
shield["displayName"] = "Wooden Shield"
shield["fireProof"] = false
shield["rupees"] = 50
PFObject *post = [[PFObject alloc] initWithClassName:@"Post"];
post[@"postID"] = @"PostID";
post[@"userID"] = @"userID";
PFFileObject *imageFile = [PFFileObject fileObjectWithName:@"photo.png" data:UIImagePNGRepresentation(editedImage)]; //editedImage is UIImage *
post[@"image"] = imageFile;
You can also declare models that subclass PFObject to make it more convenient to set and get properties. In order to subclass PFObject, create a new CocoaTouch class with a subclass of PFObject in the wizard, and manually declare that the class implements the PFSubclassing protocol. More details in the official documentation on subclassing here. See the snippet below for an example:
// needs to be imported
import Parse
// subclassing PFObject will automatically register with Parse SDK now
// See https://github.com/parse-community/Parse-SDK-iOS-OSX/pull/967
class Armor: PFObject, PFSubclassing {
// properties/fields must be declared here
// @NSManaged to tell compiler these are dynamic properties
// See https://stackoverflow.com/questions/31357564/what-does-nsmanaged-do
@NSManaged var displayName: String?
// Objective C has no concept of optionals
@NSManaged var fireProof: Bool
@NSManaged var rupees: Int
// returns the Parse name that should be used
class func parseClassName() -> String {
return "Armor"
}
}
//Post.h
#import "Parse/Parse.h"
@interface Post : PFObject<PFSubclassing>
@property (nonatomic, strong) NSString *postID;
@property (nonatomic, strong) NSString *userID;
@property (nonatomic, strong) NSString *caption;
@property (nonatomic, strong) PFFileObject *image;
@end
//Post.m
#import "Post.h"
@implementation Post
@dynamic postID;
@dynamic userID;
@dynamic caption;
@dynamic image;
+ (nonnull NSString *)parseClassName {
return @"Post";
}
@end
Instantiating an Armor class then looks like:
let shield = Armor()
shield.displayName = "Wooden Shield"
shield.fireProof = false
shield.rupees = 50
Post *post = [Post new];
post.postID = @"PostID";
post.userID = @"userID";
We can save simply by calling saveInBackground()
:
shield.saveInBackground()
[post saveInBackground];
We can also pass a completion block handler to check the success/failure of the save operation:
shield.saveInBackground(block: { (success, error) in
if (success) {
// The object has been saved.
} else {
// There was a problem, check error.description
}
})
[post saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// The object has been saved.
}
else {
NSLog(@"%@", error.localizedDescription);
}
}];
Live queries allows your client to be informed about events when changes to a given Parse query changes. There are currently 5 type of events supported: creation, update, delete, enter (an existing object now fulfills the conditions of the Parse query), leave (when an existing object no longer fulfills the condition). See this documentation for more context of the Parse LiveQuery spec.
To setup, we need to add the ParseLiveQuery to our Podfile
:
pod 'ParseLiveQuery'
Next, we need to instantiate a websocket client using the ParseLiveQuery.Client
. We also need to create a subscription to events that may be triggered by the backend. Both the client and subscription should be declared outside lifecycle methods so they are not auto released. Otherwise, no subscription events will be triggered.
// make sure to import module at the top
import ParseLiveQuery
class ViewController: UIViewController {
// make sure to declare both client and subscriptions variables outside lifecycle methods
// otherwise, websocket delegate methods won't fire
var client : ParseLiveQuery.Client!
var subscription : Subscription<Armor>!
override func viewDidLoad() {
var armorQuery: PFQuery<Armor> {
return (Armor.query()!
.whereKeyExists("displayName")
.order(byAscending: "createdAt")) as! PFQuery<Armor>
}
client = ParseLiveQuery.Client()
subscription = client.subscribe(armorQuery)
// handle creation events, we can also listen for update, leave, enter events
.handle(Event.created) { _, armor in
print("\(armor.displayName)")
}
}
Note that the Armor
class must be designated by the Parse back-end to support live queries. See this section on how to do so.
When you are pushed results in your subscription handler, the callback function will not be running on the main thread. If you want to do any UI operations (including something like reloading the table view), you must instruct the runtime to run the contents of your callback on the main thread. The inside of the callback might look like this:
// ...
subscription = client.subscribe(armorQuery)
.handle(Event.created) { _, armor in
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
PFFileObject
PFFileObject
lets you store application files in the cloud that would otherwise be too large or cumbersome to fit into a regular PFObject
. The most common use case is storing images but you can also use it for documents, videos, music, and any other binary data (up to 10 megabytes).
Parse Documentation on handling Images using PFFileObject
In this example, we will create and save an object to Parse for an image that the user wants to upload along with some other details. Let's create a model
class for Post
object. If you haven't already, first make sure to review the section above on subclassing PFObject.
class Post: PFObject, PFSubclassing {
@NSManaged var media : PFFileObject
@NSManaged var author: PFUser
@NSManaged var caption: String
@NSManaged var likesCount: Int
@NSManaged var commentsCount: Int
/* Needed to implement PFSubclassing interface */
class func parseClassName() -> String {
return "Post"
}
/**
* Other methods
*/
/**
Method to add a user post to Parse (uploading image file)
- parameter image: Image that the user wants upload to parse
- parameter caption: Caption text input by the user
- parameter completion: Block to be executed after save operation is complete
*/
class func postUserImage(image: UIImage?, withCaption caption: String?, withCompletion completion: PFBooleanResultBlock?) {
// use subclass approach
let post = Post()
// Add relevant fields to the object
post.media = getPFFileFromImage(image) // PFFile column type
post.author = PFUser.current() // Pointer column type that points to PFUser
post.caption = caption
post.likesCount = 0
post.commentsCount = 0
// Save object (following function will save the object in Parse asynchronously)
post.saveInBackgroundWithBlock(completion)
}
/**
Method to convert UIImage to PFFile
- parameter image: Image that the user wants to upload to parse
- returns: PFFile for the the data in the image
*/
class func getPFFileFromImage(image: UIImage?) -> PFFile? {
// check if image is not nil
if let image = image {
// get image data and check if that is not nil
if let imageData = UIImagePNGRepresentation(image) {
return PFFile(name: "image.png", data: imageData)
}
}
return nil
}
}
// Post.h
#import <Foundation/Foundation.h>
#import "Parse/Parse.h"
@interface Post : PFObject<PFSubclassing>
@property (nonatomic, strong) NSString *postID;
@property (nonatomic, strong) NSString *userID;
@property (nonatomic, strong) PFUser *author;
@property (nonatomic, strong) NSString *caption;
@property (nonatomic, strong) PFFileObject *image;
@property (nonatomic, strong) NSNumber *likeCount;
@property (nonatomic, strong) NSNumber *commentCount;
+ (void) postUserImage: ( UIImage * _Nullable )image withCaption: ( NSString * _Nullable )caption withCompletion: (PFBooleanResultBlock _Nullable)completion;
@end
// Post.m
#import "Post.h"
@implementation Post
@dynamic postID;
@dynamic userID;
@dynamic author;
@dynamic caption;
@dynamic image;
@dynamic likeCount;
@dynamic commentCount;
+ (nonnull NSString *)parseClassName {
return @"Post";
}
+ (void) postUserImage: ( UIImage * _Nullable )image withCaption: ( NSString * _Nullable )caption withCompletion: (PFBooleanResultBlock _Nullable)completion {
Post *newPost = [Post new];
newPost.image = [self getPFFileFromImage:image];
newPost.author = [PFUser currentUser];
newPost.caption = caption;
newPost.likeCount = @(0);
newPost.commentCount = @(0);
[newPost saveInBackgroundWithBlock: completion];
}
+ (PFFileObject *)getPFFileFromImage: (UIImage * _Nullable)image {
// check if image is not nil
if (!image) {
return nil;
}
NSData *imageData = UIImagePNGRepresentation(image);
// get image data and check if that is not nil
if (!imageData) {
return nil;
}
return [PFFile fileWithName:@"image.png" data:imageData];
}
@end
postUserImage
method from the view controller by passing all the required arguments into it (Please see method's comments for more details on arguments).PFQuery
)PFQuery
is used to retrieve data that is stored in Parse. Adding conditions to PFQuery
can be done by using methods provided by PFQuery
or by specifying an NSPredicate
.
PFQuery Methods
There are several other methods that PFQuery
provides to support SQL-like querying of objects. For example, you can get 20 instagram posts that have more than 100 likes on the post with following code:
// construct query
let query = Post.query()
query.whereKey("likesCount", greaterThan: 100)
query.limit = 20
// fetch data asynchronously
query.findObjectsInBackground { (posts: [Post]?, error: Error?) in
if let posts = posts {
// do something with the array of object returned by the call
} else {
print(error?.localizedDescription)
}
}
// construct query
PFQuery *query = [PFQuery queryWithClassName:@"Post"];
[query whereKey:@"likesCount" greaterThan:@100];
query.limit = 20;
// fetch data asynchronously
[query findObjectsInBackgroundWithBlock:^(NSArray *posts, NSError *error) {
if (posts != nil) {
// do something with the array of object returned by the call
} else {
NSLog(@"%@", error.localizedDescription);
}
}];
For more examples and list of other methods supported by PFQuery
for specifying constraints can be found here.
NSPredicate
A NSPredicate
can be passed to PFQuery
constructor to specify query constraints. Below example shows how to construct a query to fetch all instagram posts with more than 100 likes on them.
// construct query
let predicate = NSPredicate(format: "likesCount > 100")
var query = Post.query(with: predicate)
// fetch data asynchronously
query.findObjectsInBackground { (posts: [Post]?, error: Error?) in
if let posts = posts {
// do something with the array of object returned by the call
for post in posts {
// access the object as a dictionary and cast type
let likeCount = post.likesCount
}
} else {
print(error?.localizedDescription)
}
}
// construct query
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"likesCount > 100"];
PFQuery *query = [PFQuery queryWithClassName:@"Post" predicate:predicate];
// fetch data asynchronously
[query findObjectsInBackgroundWithBlock:^(NSArray *posts, NSError *error) {
if (posts != nil) {
// do something with the array of object returned by the call
} else {
NSLog(@"%@", error.localizedDescription);
}
}];
A complete list of features supported by Parse for NSPredicate
can be found here.
Pointer
type fields and fetching their value (getting value of the object)If one of the keys in your PFObject
refers to another PFObject
(note that PFUser
is a sub-class of PFObject
) then that field is of Pointer
type. For example, in Post
object which represents an Instagram post, one field that you would want to store is the author of the post. You can do this by assigning the current user to the author
key when saving the post.
let post = Post()
// get the current user and assign it to "author" field. "author" field is now of Pointer type
post.author = PFUser.current()
Post *newPost = [Post new];
// get the current user and assign it to "author" field. "author" field is now of Pointer type
newPost.author = [PFUser currentUser];
By default, when you fetch a Post
object it won't have the author information. In order to get the information for the "author" you will have to use PFQuery
method includeKey
.
query.includeKey("author")
[postQuery includeKey:@"author"];
Please see below example for more context.
Based on above discussion, we can easily construct a PFQuery
to fetch most recent posts from Parse as following:
// construct PFQuery
let query = Post.query()
query.orderByDescending("createdAt")
query.includeKey("author")
query.limit = 20
// fetch data asynchronously
query.findObjectsInBackgroundWithBlock { (posts: [Post]?, error: NSError?) -> Void in
if let posts = posts {
// do something with the data fetched
} else {
// handle error
}
}
// construct PFQuery
PFQuery *postQuery = [Post query];
[postQuery orderByDescending:@"createdAt"];
[postQuery includeKey:@"author"];
postQuery.limit = 20;
// fetch data asynchronously
[postQuery findObjectsInBackgroundWithBlock:^(NSArray<Post *> * _Nullable posts, NSError * _Nullable error) {
if (posts) {
// do something with the data fetched
}
else {
// handle error
}
}];
Parse/UI
Parse/UI
is a collection of a handy user interface components to be used with Parse iOS SDK, which streamline and simplify logging in/signing up PFUsers and displaying a list of PFObjects.
Parse/UI
cocoapodAdd pod 'Parse/UI'
under target in your Podfile and run pod install
. For more detailed instructions see this link.
PFImageView
Many apps need to display images stored in the Parse Cloud as PFFile
s. However, to load remote images with the built-in UIImageView
involves writing many lines of boilerplate code. PFImageView
simplifies this task by abstracting away these parts.
Following code snippet show how to use a PFImageView
to display images stored as PFFile
import UIKit
import Parse
class InstagramPostTableViewCell: UITableViewCell {
@IBOutlet weak var photoView: PFImageView!
var instagramPost: PFObject! {
didSet {
self.photoView.file = instagramPost["image"] as? PFFile
self.photoView.loadInBackground()
}
}
}
// InstagramPostTableViewCell.h
#import <UIKit/UIKit.h>
#import "Post.h"
@import Parse;
@interface InstagramPostTableViewCell : UITableViewCell
@property (strong, nonatomic) IBOutlet PFImageView *photoImageView;
@property (strong, nonatomic) Post *post;
@end
// InstagramPostTableViewCell.m
#import "InstagramPostTableViewCell.h"
@implementation InstagramPostTableViewCell
- (void)setPost:(Post *)post {
_post = post;
self.photoImageView.file = post[@"image"];
[self.photoImageView loadInBackground];
}
@end
UIImageView
class to PFImageView
in Interface Builder
Push notifications let your application notify a user of new messages or events even when the user is not actively using your application.
This tutorial at raywenderlich.com provides a good introduction on how push notification works.
Note: This section is only applicable if you have deployed your own Parse server.
Please refer to this section in Configuring a Parse Server
guide for detailed steps on enabling push notifications on your server.
If you need to troubleshoot whether network calls to the Parse server, you can monitor the requests and responses. First, add a few custom notification types:
extension Notification.Name {
static let ParseWillSendURLRequestNotification = Notification.Name(rawValue: "PFNetworkWillSendURLRequestNotification")
static let ParseDidReceiveURLResponseNotification = Notification.Name(rawValue: "PFNetworkDidReceiveURLResponseNotification")
}
// Constant.h
#import <UIKit/UIKit.h>
extern NSNotificationName const PFNetworkWillSendURLRequestNotification;
Enable Parse debugging logs and add notification observers in your application delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// set init log level
Parse.setLogLevel(PFLogLevel.debug)
NotificationCenter.default.addObserver(self, selector: #selector(receiveWillSendURLRequestNotification), name: Notification.Name.ParseWillSendURLRequestNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(receiveDidReceiveURLResponseNotification), name: Notification.Name.ParseDidReceiveURLResponseNotification, object: nil)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// set init log level
[Parse setLogLevel:PFLogLevelDebug];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(receiveWillSendURLRequestNotification:) name:PFNetworkWillSendURLRequestNotification object:nil];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(receiveDidReceiveURLResponseNotification:) name:PFNetworkDidReceiveURLResponseNotification object:nil];
Add these functions to listen to notifications:
@objc func receiveWillSendURLRequestNotification(notification: Notification) {
let request = notification.userInfo?[PFNetworkNotificationURLRequestUserInfoKey];
if let nsRequest = request as? NSMutableURLRequest {
print("URL: \(nsRequest.url?.absoluteString)!")
}
}
@objc func receiveDidReceiveURLResponseNotification(notification: Notification) {
let response = notification.userInfo?[PFNetworkNotificationURLResponseUserInfoKey];
let responseBody = notification.userInfo?[PFNetworkNotificationURLResponseBodyUserInfoKey]
if let response = response as? HTTPURLResponse {
print ("Status Code: \(response.statusCode)")
} else {
return
}
if let responseBody = responseBody as? String {
print ("Response Body: \(responseBody)")
}
}
- (void)receiveWillSendURLRequestNotification:(NSNotification *) notification {
id request = notification.userInfo[PFNetworkNotificationURLRequestUserInfoKey];
if ([request isKindOfClass:[NSMutableURLRequest class]]) {
NSMutableURLRequest *urlRequest = (NSMutableURLRequest *)request;
NSLog(@"URL: %@", urlRequest.URL.absoluteString);
}
}
- (void)receiveDidReceiveURLResponseNotification:(NSNotification *) notification {
id response = notification.userInfo[PFNetworkNotificationURLResponseUserInfoKey];
id responseBody = notification.userInfo[PFNetworkNotificationURLResponseBodyUserInfoKey];
if ( [response isKindOfClass:[NSHTTPURLResponse class]] ) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSLog(@"Status Code: %ld", (long)httpResponse.statusCode);
if ( [responseBody isKindOfClass:[NSString class]] ) {
NSLog(@"Response Body: %@", (NSString *)responseBody);
}
}
}
1) Is there a way to put some key into an app (binary which is delivered from AppStore) and be completely secure?
Please refer to this comprehensive explanation on Stack Overflow