Twitter Updates

Archive for the ‘iPhone development’ Category

Implementing Apple Remote Push Notification using Objective-C and Ruby

Thursday, August 5th, 2010

Recently, I added the remote push capabilities to an iPhone/iPad application at work. It turned out to be a bit more involved process than I expected so I would document key steps of the process.

Overview of Remote Push Notification

The Apple Push Notification works like SMS, where the application providers can send short messages to the users (upto 256 bytes). The message consists of JSON structure as follows:

 {
   "aps" : {  "alert" : "your-message", "sound" : "filename" : "badge" : 5,     },
   // optionally more structures, e.g. {"acme1" : "bar"}
 }
 

The alert key defines the contents of the message, which is displayed to the user. You can play sound by specifying the sound filename (which is already bundled with the app), however you can leave it blank or use ‘default’ for playing default sound. Finally, the badge will allow notification to show a number next to your application icon. Also, you can optionally add action buttons that can take user to a particular screen in your application (though I won’t show it here).

The format for sending notification is as follows:

You can read Apple documentation for more inforamtion on the binary format.

The architecture of Push Notification consists of three key pieces:

  • iOS Application – that enables push notification and registers the device for notification
  • Application Server – is responsible for generating the messages and publishing them to the Apple Push Notification Service (APNS)
  • Apple Push Notification Service (APNS) – is responsible for delivering the messages to the devices where the application is installed

Here is the architecture diagram from Apple documentation on Push Notification:

Enabling Push Notification for the application

I assume you have already created an application with developer certificate, app-id and provisioning profile. However, Apple Push Notification requires another set of certificates for development and production. So, login to the iPhone developer site and select iPhone Provisioning Portal from the upper right side and then choose App IDs link.

Select “Enable for Apple Push Notification service” checkbox, then click on Configure link:

It will guide you through creating certificate, where you will have to create a certificate request by opening “Keychain Access”, then selecting “Certificate Assistant” option from the menu and choosing “Request a Certificate from Certificate Authority”. You can then upload the generated request file to the portal. Once the certificate is generated you should see the option to download the certificate, e.g.

After downloading the certificate, you can double click or drag it to the “Keychain Access” and it will add the certificate to the Keychain.

Publishing Push Notifications on the Application Server

The push notification requires a server that connects to the APNS service and sends notification messages. You will need to use same set of keys and certificates that you created on the Apple development portal. So open “Keychain Access” and select Keys from the left options, then select the private key for the push notification. You can then export the key by right clicking, which will create a file with .12 file extension. Note that it will ask you for a password for encrypting the key (type in anything as we will remove it later). Next, select “My Certificates” from the left options and select the certificate for push notification. Then repeat the process of exporting by right clicking and saving the certificate. You will see two files with .12 extensions, which can be converted into .pem files as follows:

 openssl pkcs12 -clcerts -nokeys -out apns_cert.pem -in 
 openssl pkcs12 -nocerts -out apns_key.pem -in 
 

You can remove password with the the following command:

 openssl rsa -in apns_key.pem -out apns_key_unenc.pem
 

Then merge two files with following command:

 cat apns_cert.pem apns_key_unenc.pem > apns.pem
 

I used Rails on the server side, so I defined a few models and controllers to store devices and messages, where the device model had an attribute ‘token’ and the message model had three attributes: alert, badge and sound. Each device also has an attribute deactivated_at, which is set to the date when the device is disabled. Next, I created a short Ruby library for sending notifications. The library provides two methods send_message for sending notifications and get_feedbacks for checking disabled devices so that you can remove them from your database. The send_message method accepts a message model as an argument and then opens a connection to the APNS service. Apple recommends using a single connection for publishing notifications to all devices, so it adds all devices within a single a connection.

 require 'socket'
 require 'openssl'

 class ApnsPublisher
   def self.send_message(apns_message)
     json_payload =  {"aps" => {"alert" => apns_message.alert, "sound" => apns_message.sound, "badge" => apns_message.badge}}.to_json.to_s

     open_connection(APNS_SERVER, 2195) do |conn, sock|
       ApnsDevice.find_in_batches(:batch_size => 500 ) do |devices|
         devices.each do |device|
           next unless device.deactivated_at.nil?
           unless ApnsDevicesMessage.find_by_apns_device_id_and_apns_message_id(device.id, apns_message.id)
             token = device.token.gsub(/\s+/,'')
             byte_token = [token].pack("H*")
             message = "\0\0 #{byte_token}\0#{json_payload.length.chr}#{json_payload}"
             raise "message #{message} is too big" if message.size.to_i > 256
             conn.write(message)
             f.write(message)
             ApnsDevicesMessage.create(:apns_device_id => device.id, :apns_message_id => apns_message.id, :delivered_at => Time.new.utc)
           end
         end
       end
     end
   end 

   def self.get_feedbacks()
     open_connection(APNS_FEEDBACK_SERVER, 2196) do |conn, sock|
       while line = sock.gets
         line.strip!
         feedback = line.unpack('N1n1H140')
         token = feedback[2].scan(/.{0,8}/).join('').strip
         device = ApnsDevice.find_by_token(token)
         if device
           device.update_attribute(deactivated_at, Time.at(feedback[0])
         end
       end
     end
   end
 private
   def self.open_connection(host, port, passphrase='')
     cert = File.read(APNS_CERT_FILE)
     ctx = OpenSSL::SSL::SSLContext.new
     ctx.key = OpenSSL::PKey::RSA.new(cert, passphrase)
     ctx.cert = OpenSSL::X509::Certificate.new(cert)

     sock = TCPSocket.new(host, port)
     ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
     ssl.sync = true
     ssl.connect
     yield ssl, sock 

     ssl.close
     sock.close
   end
 end
 

You can use delayed_job plugin to run the get_feedbacks method periodically. Also, I added a service for registering and adding the devices, which is called from the iPhone devices (but not shown here).

Configuration

You will need to define following properties for development:

 APNS_SERVER = "gateway.sandbox.push.apple.com"
 APNS_FEEDBACK_SERVER = "feedback.sandbox.push.apple.com"
 APNS_CERT_FILE = File.join(RAILS_ROOT, 'config', 'apns_dev.pem')
 

and following properties for production:

 APNS_SERVER = "gateway.push.apple.com"
 APNS_FEEDBACK_SERVER = "feedback.push.apple.com"
 APNS_CERT_FILE = File.join(RAILS_ROOT, 'config', 'apns_prod.pem')
 

Registering devices for notification

You can register the device by adding following method in your didFinishLaunchingWithOptions of the primary delegate class:


         [[UIApplication sharedApplication]
                 registerForRemoteNotificationTypes:( UIRemoteNotificationTypeAlert |UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];

and then registering with following callback methods:

 #pragma mark push notifications
 - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
         NSString *token = [[devToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<> "]];
         token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
         DeviceRegisterer *registrar = [[DeviceRegisterer alloc] init];
         [registrar registerDeviceWithToken:token];
 }

 - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
         NSLog(@"failed to regiser %@", err);
 }

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
         NSLog(@"notification options %@", userInfo);
 }

I used ASIHTTPRequest library for invoking REST service I wrote for registering devices (on Rails side), e.g.

DeviceRegisterer.h

 @class ASIFormDataRequest;                                                                                                                                                                                                                                                                                                 

 @interface DeviceRegisterer : NSObject {
         ASIFormDataRequest *request;

 }

 @property (retain, nonatomic) ASIFormDataRequest *request;

 - (void)registerDeviceWithToken:(NSString *)token;

 @end
 

DeviceRegisterer.m


 #import "DeviceRegisterer.h"

 #import "ASIFormDataRequest.h"

 @implementation DeviceRegisterer
 @synthesize request;

 - (void)registerDeviceWithToken:(NSString *)token {
         if (self.request == nil) {
                 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/admin/apns_devices.json", API_BASE_DOMAIN]];
                 [self setRequest:[ASIFormDataRequest requestWithURL:url]];
                 [request setPostValue:token forKey:@"token"];

                 [request setTimeOutSeconds:30];
                 [request setDelegate:self];
                 [request setDidFailSelector:@selector(registerFailed:)];
                 [request setDidFinishSelector:@selector(registerFinished:)];

                 [request startAsynchronous];
         }
 }

 - (void)registerFailed:(ASIHTTPRequest *)theRequest {
         NSLog(@"registerFailed %@", [theRequest error]);
 }

 - (void)registerFinished:(ASIHTTPRequest *)theRequest {
         NSLog(@"registerFinished %d",[theRequest postLength]);
 }

 - (void)dealloc {
         [request cancel];
         [request release];
         [super dealloc];
 }

 @end
 

Summary

In nutshell, push notification is powerful feature that can help your users engage with your application, though it must be used with caution so that users are not annoyed and in turn remove your application or disable it. It also requires a lot of moving parts for sending notification, registering, getting feedback on the devices. I found that Apple does not provide a great debugging options when testing the push notification. For example, first problem I encoutered with testing was that my profile on XCode was old and wasn’t updated after I enabled push notification. I had to delete my old profile and then refresh it from the Organizer. Also, when you send notifications to the APNS, you don’t get any response code or errors. This caused some frustration when I wasn’t getting messages due to slightly wrong JSON format. Fortunately, when your device is connected you can select the device from the XCode Organizer and view the Console tab for debugging information. I was able to view the error (which a bit vague) and then got everything working after fixing the JSON structure.



Validating receipts from Apple iPhone store in Ruby

Monday, May 24th, 2010

Recently, I had to write a service on Ruby-on-Rails project to validate an in-store purchase for iPhone application. Though, it was fairly straight forward, but I am posting the code in case someone else needs it:

  1 require 'net/http'                                                                                                                                                                                                                                                            

  2 require 'net/https'
  3 require 'uri'
  4
  5 class AppleReceiptVerifier 

  6   #
  7   ### Verifies Apple receipt submitted by iPhone 
  8   ### See http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Overview%20of%20the%20Store%20Kit%20API/OverviewoftheStoreKitAPI.html#//apple_ref/doc/uid/TP40008267-CH100-SW14

  9   #
 10   def self.verify(b64_receipt)
 11     url = URI.parse(APPLE_RECEIPT_VERIFY_URL)

 12     http = Net::HTTP.new(url.host, url.port)
 13     http.use_ssl = true
 14     http.verify_mode = OpenSSL::SSL::VERIFY_NONE

 15     valid = false
 16     json_request = {'receipt-data' => b64_receipt}.to_json
 17     resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'})

 18     if resp.code == '200'
 19       json_resp = JSON.parse(resp_body)
 20       if json_resp['status'] == 0

 21         valid = true
 22       end
 23     end
 24     valid
 25   end 

 26 end
 27
 28
 

Note that the receipt returned by iPhone APIs is not base 64 encoded so you will need to encode it before calling the service. Also, for testing, APPLE_RECEIPT_VERIFY_URL will point to the sandbox environment, i.e., https://sandbox.itunes.apple.com/verifyReceipt and for real purchase it will point to https://buy.itunes.apple.com/verifyReceipt. Finally, you can learn more from the Apple documentation on purchase model and on validating receipts.


ActiveObject based O/R Mapping library for iPhone development

Saturday, June 13th, 2009

I have been learning iPhone development lately and needed to add some persistence capability to my application. There are varied options available for persistence such as using “User Defaults” for small user specific settings, serialization similar to serialization or pickle features of other languages and builtin support of embeded Sqlite. I found Sqlite more performant, memory efficient and flexible than other options so I chose it. Using Sqlite with iPhone is fairly straight forward and there are tons of examples such Creating Todo list using Sqlite. However, when I looked for some O/R mapping framework for iPhone, I found that iPhone SDK unlike Mac development didn’t have any support and neither I could find any other solution elsewhere. So, I started writing a simple O/R mapping library based on Active Object pattern. This is similar to how Rails and Django implement O/R mapping. Based on convention over configuration, it simply maps object properties to the database table fields. At this time, this is very basic O/R mapping library and does not support relations, validation, database integrity support, etc. Nevertheless, it met my simple needs and I have released it as open source project under OCActiveObjects on GitHub.

The OCActiveObjects library is fairly small and consists of following classes:

ActiveObject

This is the base class that you extend in order to add automatic O/R support. You have to override following Class methods to specify name of the database and table:

     + (NSString *) getTableName;
   

Above method defines name of table where instances of the object will be stored.

     + (NSString *) getDatabaseName;
   

Above method defines name of the database to be used. You will then be able to call following methods to interact with the Sqlite database:

   + (void) openDatabase;
   

Above method must be called once before any other methods, usually at the start of your application.

   + (void) closeDatabase;
   

Above method must be called once before you shutodnw your application.

   - (void) save;
 

Above method saves a new object or updates an existing object. Each subclass of ActiveObject is automatically assigned a unique database id with a property
named “objectId”. This is another example of convention where all tables will use a numeric surrogate key to identify each row.
If that property is nil then it assumes this is new object and inserts a new row in the database, otherwise it updates an existing row in the
database. It assumes that name of database fields are same as property names, though you can override that behavior by overriding _getPropertyNamesAndTypes Class method.

   + (ActiveObject *) findByPrimaryKey:(NSNumber *)objId;
 

Above method queries an object in the database matching objectId property, which identifies each object in the database.

   + (NSArray *) findWithCriteria:(NSDictionary *)criteria;
 

Above method returns an array of objects that match criteria. The criteria at this time is simple dictionary, i.e., pair of name and values that are joined by
“AND” clause. There is a immediate need to extend this to support more flexible queries.

   + (NSArray *) findAll;
 

Above method returns all objects, which may not be good for iPhone application due to limited amount of memory. This is another area that needs immediate attention.

   + (int) removeAll;
 

Above method removes all rows in the table so be careful with this.

   + (int) removeWithCriteria:(NSDictionary *)criteria;
 

Above method removes only methods matching criteria. Again criteria consists of name/value pairs.

   + (int) countWithCriteria:(NSDictionary *)criteria;
 

Above method counts the number of rows in the database matching criteria.

   + (int) countAll;
 

Above method returns count of all rows in the table.

Exension Methods in ActiveObject

There are number of extension methods to customize SQLs or behavior of the object such as

 - (void) _insert;
 

Above method inserts an object into the database.

 - (void) _update;
 

Above method updates an existing object into the database.

 + (NSDictionary *) _getPropertyNamesAndTypes;
 

You can override above method to change properties that needs to be persisted.

 + (NSString *) _getCreateSQL;
 

Above method generates an SQL for creating table.

 + (NSMutableString *) _getInsertSQL;
 

Above method generates an SQL for inserting a row in the table.

 + (NSMutableString *) _getUpdateSQL;
 

Above method generates an SQL for updating a row in the table.

 + (NSMutableString *) _getSelectSQL;
 

Above method generates an SQL for selecting fields from the database.

 + (void) _createTable;
 

Above method creates database table.

IntrospectHelper

The OCActiveObjects library uses some Objective C magical runtime support to query for properties and this class encapsulates those methods.

SqliteHelper

This class some helper methods for Sqlite3.

How to use

In order to test it, let’s define a simple Person class that extends ActiveObject, e.g.

 #import <Foundation/Foundation.h>
 #import "ActiveObject.h"

 @interface Person : ActiveObject {
         NSString *name;
         short age;
         int rank;
         long votes;
         char sex;
         double income;
         BOOL active;
         NSInteger flags;
         NSNumber *rating;
         NSDate *birthdate;
 }

 @property (nonatomic, retain) NSString *name;
 @property (nonatomic, assign) short age;
 @property (nonatomic, assign) int rank;
 @property (nonatomic, assign) long votes;
 @property (nonatomic, assign) char sex;
 @property (nonatomic, assign) double income;
 @property (nonatomic, assign) BOOL active;
 @property (nonatomic, assign) NSInteger flags;
 @property (nonatomic, retain) NSNumber *rating;
 @property (nonatomic, retain) NSDate *birthdate;

 - (BOOL)isEqualToPerson:(Person *)aPerson;

 @end
 

Implemention of Person.m looks like:

 #import "Person.h"

 @implementation Person
 @synthesize name;
 @synthesize age;
 @synthesize rank;
 @synthesize votes;
 @synthesize sex;
 @synthesize income;
 @synthesize active;
 @synthesize flags;
 @synthesize rating;
 @synthesize birthdate;

 - (BOOL)isEqual:(id)other {
     if (other == self)
         return YES;
     if (!other || ![other isKindOfClass:[self class]])
         return NO;
     return [self isEqualToPerson:other];
 }

 - (BOOL)isEqualToPerson:(Person *)aPerson {
         if (self == aPerson)
         return YES;
     if (![(id)[self name] isEqual:[aPerson name]])
         return NO;
     return YES;
 }

 - (NSUInteger)hash {
         NSUInteger hash = 0;
         hash += [[self name] hash];
         return hash;
 }

 - (NSString *)description {
         return [NSString stringWithFormat:@"id %@, name %@", self.objectId, self.name);
 }

 - (void) dealloc {
         [name release];
         [birthdate release];
         [super dealloc];
 }

 + (NSString *) getTableName {
         return @"persons";
 }

 + (NSString *) getDatabaseName {
         return @"personsdb";
 }

 @end
 

Then you can first open the database, e.g.

         [Person openDatabase];
 

Then create a new person object, e.g.

         Person *person = [[[Person alloc] init] autorelease];
         person.birthdate = [[NSDate alloc]init];
         int random = [person.birthdate timeIntervalSince1970];
         person.age =  random % 30;
         person.rank = random % 20;
         person.votes = random % 10;
         person.sex = 'M';
         person.name = [NSString stringWithFormat:@"Joe #%d", random % 1000];
         person.income = random % 3000;
         person.active = YES;
         person.flags = random % 30 + 0.5;
         person.rating = [NSNumber numberWithInt:20.5];
         return person;
 

You will then be able to save the person object as

         [person save];
 

You can see how many rows are in the database by

         int count = [Person countAll];
 

And then retrieve the object that we saved as

         Person *person2 = (Person *) [Person findByPrimaryKey:person.objectId];
 

When you are done, you can then close the database:

         [Person closeDatabase];
 

One of the frustrating aspect of iPhone development has been lack of good unit testing support. Though, XCode comes with OCUnit, but it is hard to install and use with iPhone. I kept getting weird errors like:

 exited abnormally with code 139
 
 failed tests for architecture 'i386'
 

Though, there are some basic tutorials like Test Driving Your Code with OCUnit or OCUnit: Integrated Unit Testing In Xcode, but they didn’t help. I also tried adding google-toolbox-for-mac but macro errors are extremely frustrating. Besides better testing, OCActiveObjects needs a lot of help to add better support of criteria, paging, relational mapping and validation. I also had hard time figuring out how to create a static library until I found Building static libraries with the iPhone SDK though I still need help in adding framework level support. Hopefuly, other people can contribute to the open source project. You can send me your suggestions and comments as well via email “bhatti AT plexobject DOT com” or tweet me at bhatti_shahzad.