adhoc OTA iOS app distribution

Update: This post is no longer valid since Dropbox discontinued downloads in the manner mentioned in this post.

Credits

  1. http://stackoverflow.com/questions/26042508/over-the-air-ota-ios-ipa-file-distribution-for-public
  2. http://stackoverflow.com/questions/20276907/enterprise-app-deployment-doesnt-work-on-ios-7-1

I am an iOS developer and often I have to distribute apps to our testers. I have been using Hockey for this until recently but with iOS10 beta release, the Hockey app broke and I wasn’t able to distribute my app. Then I remembered the old fashion technique of OTA (over-the-air) distribution that old time iOS developers must be familiar with. OTA distribution was used up until TestFlight and Hockey became the de-facto app distribution channels. This post tries to explain it.

Before we proceed let’s set the context clearly. This is not a post concerning Enterprise distribution. This post is specific to adhoc distribution of iOS app to devices added to your Apple Developer Account. To rephrase; If you had the device ids of your test devices, how do you distribute your app to them in an easy way. You do it using adhoc OTA distribution. Lets look at how this is done in steps.

Creating an adhoc distribution build

  1. Get the device ids of your test devices and add them to your Apple developer account.
  2. Create an adhoc distribution provisioning profile for your app id and add the test devices to it.
  3. Download the adhoc provisioning profile into Xcode and build your app as an archive by codesigning it with this provisioning profile.
  4. Select the archive in Xcode > Organizer window and choose Export.
  5. From the choices, choose “Save for adhoc distribution” option.
  6. A .ipa file will be generated. This is your adhoc distribution build. Pause a second and rejoice at your progress.

Distributing the adhoc build

The distribution process requires you to upload the .ipa file and an additional .plist file to a web server and expose a public html webpage to your testers. This webpage will contain the download link with a particular url-scheme that iOS devices understand. The link points to the .plist file which internally contains the download path to the .ipa file. There are other conditions around your web server like it should expose the .ipa and .plist files via https and the SSL certificate for https should be from a valid signing authority not a self signed one.

Don’t get anxious. It’s easier than it looks as you are about to discover now.

  1. You already have the .ipa file.
  2. For the .plist file you can download this sample that I copied from this StackOverflow question and do the necessary edits as mentioned in Step 5 below.
  3. I know what you are thinking, “How do I upload the .ipa and .plist file to a server? I don’t have any web server. How do I create the html web page with the download link?”. Worry not, let’s simplify all this by using Dropbox. That’s right. We will use Dropbox for hosting everything; the .ipa file, the .plist file and the html web page file. Using Dropbox also takes care of the https requirement as it is already https.
  4. Upload the .ipa file to Dropbox and copy it’s link. Your link should look something like this https://www.dropbox.com/s/lbine4ik0l0jgv3/xyz.ipa?dl=0. Now here is the critical part. Change “www.dropbox.com” to “dl.dropboxusercontent.com” in the copied link and remove the “?dl=0” part. Finally, your .ipa file link should look like https://dl.dropboxusercontent.com/s/lbine4ik0l0jgv3/xyz.ipa.
  5. Edit the .plist file to point to the above .ipa file link. Note that you don’t need to provide valid values for all the keys present in the .plist file. Only certain values should be correctly filled in and looking at it you would know which ones. Don’t remove any keys though. Let them be present with whatever invalid values they have.
  6. Your .plist file should be renamed to be exactly the same as your .ipa file created above. If your app is xyz.ipa, your .plist file should be xyz.plist.
  7. Your .plist is now ready to go on Dropbox. Upload it and copy it’s link. Your link should look something like this https://www.dropbox.com/s/lbine4ik0l0jgv3/xyz.plist?dl=0. Again change “www.dropbox.com” to “dl.dropboxusercontent.com” in the copied link and remove the “?dl=0” part. Finally, your .plist file link should look like https://dl.dropboxusercontent.com/s/lbine4ik0l0jgv3/xyz.plist.
  8. Now create an html file with this content. Note that it contains the special url-scheme that we mentioned earlier and it also has the modified .plist file link we obtained in Step7 above.
  9. Your html file is now ready to go on Dropbox. Upload it and copy it’s link. Your link should look something like this https://www.dropbox.com/s/lbine4ik0l0jgv3/download.html?dl=0. Again change “www.dropbox.com” to “dl.dropboxusercontent.com” in the copied link and remove the “?dl=0” part. Finally, your html file link should look like https://dl.dropboxusercontent.com/s/lbine4ik0l0jgv3/download.html.
  10. 🎪That’s it. Share this html link with your testers via email and ask them to view the email on their test devices.  When they click the link, they will be redirected to Safari which will open your html page. Once they click “Download”, they will be prompted to install the app.

Seems like an long process but it’s not. Plus it’s a nice alternative to have apart from Hockey, Testflight for easy distribution to testers. Hope this helps someone.

Running an AWS EC2 instance with Ubuntu/Tomcat/MySQL

This is a beginner’s guide to setting up an AWS EC2 instance with Ubuntu/Tomcat7/MySQL.

  1. Create AWS account: Creant an AWS account with your personal and credit card details and login to AWS console. If you are an existing Amazon cusotmer, you can login with that account. AWS provides a free tier which is worth exploring. I have deployed two websites on free tier instances that are functioning smoothly.

    Warning: It is said on forums that free tier instances can mysteriously disappear. So, a periodic back-up of your data and a guide on your set-up process should be maintained.

  2. Configure Security Group: Next you need to set up a security group to which the instance will belong. A security group is where you enable the ports you want your instance to listen on. A default security group already exists which you can modify. Click on Network & Security options in the left pane and edit the default security group to add http, https and ssh ports to the Inbound tab. Without this, your instance won’t respond to any incoming http, https and ssh requests. Other data transfer protocols need to be added here if required.

  3. Create Instance: To launch an AWS instance, select Instances from the left pane and click on launch instance. Give your instance a descriptive name. Create a new public/private key pair and download the key which is a .pem file needed to connect to your server. Store this file in a secure location. Select the configuration for your server and make sure you select the default security group you just edited in the previous step. Security group needs to be configured before launching the instance because you won’t be able to change it afterwards. Once your instance launches, you can see its details by clicking on Instances option from the left pane in the console.

    Warning: Do not restart an instance from the AWS console after it has been launched. Doing so changes the public IP of the instance and you will need to update the DNS record with your domain registrar for your site to function. To overcome this, you can create an elastic IP and assign it to your instance. You will incur charges if you create an elastic IP and leave it unassigned.

  4. SSH Into The Instance: Launch Command Prompt and cd to the directory where your .pem file is stored.
    ssh -i keyname.pem ubuntu@publicIP

    You can get the public IP of your instance from AWS Console > Instances. You may need to modify permissions of the .pem file if required

    chmod 600 keyname.pem

  5. Installations: After sshing into the instance, update Ubuntu package manager and installed packages
    sudo apt-get update
    sudo apt-get dist-upgrade
    

    install MySQL

    sudo apt-get install mysql-server

    install tomcat

    sudo apt-get install tomcat7 tomcat7-webapps
  6. Running Tomcat on Port 80: By default tomcat listens for http requests on port 8080 but by default browser http requests are sent to port 80. In order to fulfill them, you need to configure tomcat to run on port 80. First you need to modify the HTTP connector in conf/server.xml
    <Connector port="80" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

    Then you need to follow a few more steps mentioned here.

Serving static content from linux/tomcat7

Sometimes it makes more sense to serve static content like images to your website from a folder on the disk on your server rather than packaging it into a war file. To serve static content from tomcat7, you don’t need to write any java code or create a war file. You can simply edit the $TOMCAT_HOME/conf/server.xml and add <Context path=”/images” docBase=”/home/ubuntu/images” /> tag to the <Host> tag present in the file. path refers to the url path relative to the host/context and docBase refers to the location on disk. So if you have a url like /images/1.jpg, having the file /home/ubuntu/images/1.jpg will render the image.

If you have a war file and are creating a custom servlet, then you will also need to map the url pattern /images/* to the default servlet in web.xml file.

Additionally, the folder on the disk should have the right permissions to allow read access by tomcat.

A little bit on Cron

What is Cron?

Cron is a service that runs periodic tasks known as cron jobs. Cron has various implementations which handle the execution of cron jobs. Cron jobs are stored in crontab file and can be edited using the crontab command.

//list all cron jobs
static-202:~ Sayeed$ crontab -l

//edit cron jobs. crontab file opens in the default text editor for editing.
static-202:~ Sayeed$ crontab -e

//set vim as the editor
static-202:~ Sayeed$ export visual=vi

//delete the crontab file of the current user. this will delete all the cron jobs.
static-202:~ Sayeed$ crontab -r

To delete a particular cron job, open the crontab file using crontab -e command -> delete the cron job -> save the file.

Syntax

minute(0-59) hour(0-23) day-of-month(1-31) month(1-12) day-of-week(0-6, 0 being sunday) <command>

Example

I wanted to add a cron job on my linux server to take the dump of MYSQL database at 00:00 hrs everyday. Here is how it looks:

0 0 * * * mysqldump -uxyz -pxyz et_prod > /home/ubuntu/et_prod.sql

More Examples

//take database dump at 10a.m. every sunday.
* 10 * * 0 mysqldump -uxyz -pxyz et_prod > /home/ubuntu/et_prod.sql

//take database dump every 2 hours.
* */2 * * * mysqldump -uxyz -pxyz et_prod > /home/ubuntu/et_prod.sql

BSD Signals and Cocoa

What is a signal?

A signal is a notification sent by the kernel to a process when it tries to do something it shouldn’t be doing often resulting in termination of the process. There are many kinds of signals like

  • SIGABRT
  • SIGSEGV
  • SIGBUS
  • SIGILL
  • SIGSTOP
  • SIGKILL

A more complete list can be seen here. Among them, the ones of particular import to a Cocoa developer are SIGABRT, SIGSEGV and SIGBUS.

SIGABRT happens when an NSException goes uncaught. SIGSEGV happens when a process tries to access a memory that’s not mapped to its address space for example when it tries to write into the TEXT segment which is a readonly segment. SIGBUS happens when a process tries to access unaligned memory like reading a long value from a memory address that’s not divisible by 4.

Now that we know what signals mean, let’s see if we can generate the important ones on purpose.

Raising SIGABRT

NSString *str = @"";
[(id)str objectAtIndex:0];

The above code should generate a SIGABRT as we are trying to invoke a method of NSArray class on an NSString object. When we run the above code, we get a crash. Looking at the top section of the crash report, we see

Exception Type:  EXC_CRASH (SIGABRT)

Raising SIGSEGV

int i = *(int *)0x1;

The above code when run on an x86_64 Mac produces a SIGSEGV because our process is trying to load the value at address 0x1 which can never be mapped into its address space. Looking at the top section of the crash report we see

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000001

Another example of a code that raises SIGSEGV on an x86_64 Mac is invoking a nil block.

void (^block)(void) = nil;
block()

Looking at the top section of the crash report we see

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000010

The address 0x0000000000000010 seen raises an interesting question. Why would a nil block invocation try to access the value at 0x0000000000000010?

I got the explanation for that here from Stackoverflow courtesy Matt J Galloway. The gist is that a block gets converted into a struct by the compiler and the block variable becomes a pointer to the compiler generated struct. The generated struct looks like this

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

The 4th member is a function pointer pointing to the body of the block. When we invoke the block, the processor tries to load 0 (nil) + 8 bytes (void *isa) + 4 bytes (int flags) + 4 bytes (int reserved) = 16 i.e. 0x0000000000000010 in hexadecimal and fails because 0x0000000000000010 address does not belong to our process’ address space and is invalid as far as our process is concerned. Note that the address 0x0000000000000010 in itself is valid but its access from our process is not.

Raising SIGBUS

I had read that accessing misaligned memory would lead to SIGBUS. So I tried doing this


typedef struct
{
    unsigned short a1;
    unsigned short b1;
    
} XYZ;

int main(int argc, char *argv[]){
    
    XYZ p =  {255, 2};
    
    char *t = (char *)&p; //deliberate invalid cast
    printf("%p\n", t);
    
    t += 1; //deliberate misaligning by pointer increment
    printf("%p\n", t);

    unsigned short a1 = *(unsigned short *)t; //deliberate invalid dereferencing
    printf("%d\n", a1);
}

I thought this line

unsigned short a1 = *(unsigned short *)t; //invalid dereferencing

would produce a SIGBUS on my x86_64 Mac because I was trying to read a 2 byte unsigned short from an address that I arrived at by deliberate invalid cast and pointer increment. But unfortunately I didn’t get a crash and later learned that Intel processors can guard against misaligned memory access.

Instead of crashing, I got the following result

0xbfffcbe8
0xbfffcbe9
512

The output 512 calls for an explanation. The data type unsigned short is 2 bytes that can store values from 0 to 2^16 – 1. The initialization of p sets p.a1 = 255 and p1.b1 = 2. Shown in bits, they look like this on a little endian (least significant bytes are stored at lower memory addresses) architecture

p.a1 = 255
{11111111 00000000}

p.b1 = 2
{00000010 00000000}

The address of a struct is the same as the address of its first member and the members are laid out adjacent to each other in memory. So when I say &p, that’s equivalent to &p.a1 and p.b1 is adjacent to p.a1. Incrementing t by 1 in pointer terms increments it by 1 byte since its type is (char *) due to our deliberate invalid cast which lands me at the 2nd byte of p.a1. When I read an unsigned short from there, I actually end up with 2nd byte {00000000} of p.a1 and 1st byte {00000010} of p.b1. Therefore my unsigned short value looks like {00000000 00000010} in bytes which when converted to decimal taking little endianess into account means

0*2^0 + ..... 0*2^7 + 0*2^8 + 1*2^9 + .... + 0*2^15 = 512

NSUserDefaults Mystery On Mac OS X

While working on my Mac app, I often faced the need to delete the NSUserDefaults file manually. The NSUserDefaults of a mac application are stored in a .plist file whose location depends on whether the application is sandboxed or not.

  • For sandboxed application, the .plist file is located at /Users/John/Library/Containers/<bundle-identifier>/Data/Library/Preferences
  • For non-sandboxed application, the .plist file is located at /Users/John/Library/Preferences

When deleting the .plist file, I faced a problem. Even after deleting the file, the application still seemed to be picking up values from it at runtime. A little bit of exploring led me to the fact that a process named cfprefsd retains a link to the .plist file even after it’s moved to trash and hence the deleted values still get served to the app.

I found that emptying the trash alone isn’t enough. The cfprefsd process must also be killed using the terminal command:

killall kill cfprefsd

Executing the above command delivers the behaviour we expect. Values are read from the new .plist file. Hope this comes handy to Mac OS X developers out there. Good Luck And Good Life!!!

 

Errors and Exceptions in Objective C

Undestanding The Difference

There are two types of bad situations in an Objective C program: errors and exceptions. Let us understand the difference between the two.

Error is an unwanted situation that can happen during program execution but you can recover from it. The program memory remains in a consistent state and further execution can continue.

eg1: Attempt to access a network resource timed out or returned 500

eg2: Attempt to write to disk failed due to lack of disk space or permission denied

Handling: You can handle errors any way you want. Show a user message, log it to a file/console or report it to your back-end.

Exception is an unwanted state in your program that should never have occured. Exceptions may corrupt the memory state of the program in a way that prevents further execution.

This can generally occur in two ways:

  1. A logic error that you overlooked is now causing an exception.

    eg: Accessing invalid array elements i.e. index out of bound exception.

    In the above example, you unconsciously made a programming mistake. You couldn’t have handled it because you overlooked it. This will crash the program.

    Handling: Your approach here should be to reproduce and fix it asap.

  2. While execution, your program comes across a situation that your business logic doesn’t permit.

    eg: A database entry that your business logic expects to be always present is missing. For example, let’s say you are writng a program that requires a currency table with currency names, unicode symbols for currencies and country names. The values from this table are to be shown in a drop down in your program UI. You may create a table upon first launch of the program and insert the values. Later you make an sqlite select query but no values are returned.

    This is where confusion may arises because you can recover from such situations by treating them as errors (because the program memory is not corrupted) and displaying a message to the end user. This approach is wrong because it’s an exception at the business logic level.

    Handling: In such cases, you should force crash the program using NSAssert both in development and production phase. This is an aggressive approach geared toward quickly finding and fixing such exceptions rather than disguising them as errors. Besides, treating them as fatal (crash) attributes them the urgency they deserve.

Some Other Tips

  1. Try to avoid using @try/catch in Objective C because experts don’t recommend it.
  2. Formalize an approach to error/exception handling and stick to it. Document your intent in the code. You will be better off finalizing an approach and employing it consistently rather than revisiting this subject again and again.