Friday, March 4, 2011

How To Download a File Only If It Has Been Updated




Your Ad Here


ou should always avoid downloading data over a wireless network whenever possible. If there is a file on a server that your app needs and it’s infrequently updated, then a good approach would be to cache that file in your app and only download it when an update is detected. (You could also package a version of the file inside the app bundle to provide a quick startup time the first time the app is launched.)
There are several ways to solve this problem. The code below does a HTTP HEAD request, checks the returned "Last-Modified" header, and compares it with updated timestamp of the local file. (A slightly more efficient approach would be to send a "If-Modified-Since" header with your request. But there seems to be some issues with that, so proceed carefully with that approach.)

High-level steps:

  1. Create a HTTP HEAD request.
  2. Read the "Last-Modified" header and convert the string to a NSDate.
  3. Read the last modification timestamp of the local file.
  4. Compare the two timstamps.
  5. Download the file from the server if it has been updated.
  6. Save the downloaded file.
  7. Set the last modification timestamp of the file to match the "Last-Modified" header on the server.
Please note that the code uses synchronous requests to make the code shorter and better illustrate the point. Obviously you should not call this method from the main thread. Depending on your requirements, you could rewrite the code to use asynchronous requests instead.

- (void)downloadFileIfUpdated {
    NSString *urlString = ... your URL ...
    DLog(@"Downloading HTTP header from: %@", urlString);
    NSURL *url = [NSURL URLWithString:urlString];
   
    NSString *cachedPath = ... path to your cached file ...
    NSFileManager *fileManager = [NSFileManager defaultManager];
   
    BOOL downloadFromServer = NO;
    NSString *lastModifiedString = nil;
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"HEAD"];
    NSHTTPURLResponse *response;
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error: nil];
    if ([response respondsToSelector:@selector(allHeaderFields)]) {
        lastModifiedString = [[response allHeaderFields] objectForKey:@"Last-Modified"];
    }
   
    NSDate *lastModifiedServer = nil;
    @try {
        NSDateFormatter *df = [[NSDateFormatter alloc] init];
        df.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
        df.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
        df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
        lastModifiedServer = [df dateFromString:lastModifiedString];
    }
    @catch (NSException * e) {
        NSLog(@"Error parsing last modified date: %@ - %@", lastModifiedString, [e description]);
    }
    DLog(@"lastModifiedServer: %@", lastModifiedServer);
   
    NSDate *lastModifiedLocal = nil;
    if ([fileManager fileExistsAtPath:cachedPath]) {
        NSError *error = nil;
        NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:cachedPath error:&error];
        if (error) {
            NSLog(@"Error reading file attributes for: %@ - %@", cachedPath, [error localizedDescription]);
        }
        lastModifiedLocal = [fileAttributes fileModificationDate];
        DLog(@"lastModifiedLocal : %@", lastModifiedLocal);
    }
   
    // Download file from server if we don't have a local file
    if (!lastModifiedLocal) {
        downloadFromServer = YES;
    }
    // Download file from server if the server modified timestamp is later than the local modified timestamp
    if ([lastModifiedLocal laterDate:lastModifiedServer] == lastModifiedServer) {
        downloadFromServer = YES;
    }
   
    if (downloadFromServer) {
        DLog(@"Downloading new file from server");
        NSData *data = [NSData dataWithContentsOfURL:url];
        if (data) {
            // Save the data
            if ([data writeToFile:cachedPath atomically:YES]) {
                DLog(@"Downloaded file saved to: %@", cachedPath);
            }
           
            // Set the file modification date to the timestamp from the server
            if (lastModifiedServer) {
                NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:lastModifiedServer forKey:NSFileModificationDate];
                NSError *error = nil;
                if ([fileManager setAttributes:fileAttributes ofItemAtPath:cachedPath error:&error]) {
                    DLog(@"File modification date updated");
                }
                if (error) {
                    NSLog(@"Error setting file attributes for: %@ - %@", cachedPath, [error localizedDescription]);
                }
            }
        }
    }
}

14. It’s always good practice to check if the returned object responds to a selector before you send a message to it.

22. The "Last-Modified" timestamp on the server includes the name of the month as text. Set the locale of the date formatter to English (assuming that your server responds in English) otherwise the code will fail when run on devices in non-English countries.

Keep in mind that wireless networks have very high latency. That means it’s very expensive to create a network connection to a server. If the size of the file you want to download is very small, then you will not gain much with the approach above since it will take almost as long time to get the header information as downloading the entire file.


List of features for Apple iOS 4.3




Your Ad Here




Along with the the iPad 2, Apple is also going to release iOS 4.3 on March 11.  Here are some of the features you can expect:

A Configurable Slide Switch for IPad
With iOS 4.3 you will be able to decide whether the hardware switch above the volume buttons on the iPad will control mute or rotation lock in Settings.

Safari Performance Boost
Thanks to the Nitro JavaScript engine, an advanced bytecode JavaScript engine, will make web browsing even faster.

iTunes Home Sharing
Lose the wires and stream all your content directly over WiFI.

Improved Airplay Support
There are several enhancements coming to Airplay, one of the most sought after is the ability to allow apps and even websites to play video using AirPlay Video.

Personal Hotspot
Now Verizon iPhone owners won’t be the only ones enjoying the use of a personal hotspot on their iPhone.
Really nothing groundbreaking or ultra-exciting in 4.3 but there are several nice enhancements, which is the norm for a .1. Time to move on to speculating the whens and whats of iOS5.


iOS Advanced Programming: Event Kit Framework




Your Ad Here


In today’s tutorial we are going to cover the Event Kit Framework and how to add an event to the calendar.
The Event Kit Framework is the way Apple wants us to display or create a calendar entry. With this they are not trying to replace the calendar app, they are trying to give us a way to create an appointment from our app and add it to the calendar app.
To explain this I am going to create a calendar event programmatically. The only problem is that we are not going to be able to test it in the simulator because it doesn’t have a calendar app. To test it you will have to pay $99 to Apple.
Open up Xcode and create a View-Based Application for iPhone. I’m going to call mine “EventApp”.

Adding the framework

The Event framework is not added by default. You have to right click over the “Frameworks” folder in “Groups and Files” and select Add->Existing Frameworks then find EventKit.framework and click the “Add” button.

iPhone Advance Programming | Event kit Framework | image 1

 

Writing the Code

Now in the EventAppViewController.m import EventKit.
#import
 
Now create a method that will be called when the user touches the “Create event” button that we are going to add later to our interface.

-(IBAction) newEvent {

}
 
Remember that it must also be added to the .h file as well.
First add the object that is going to give us access to the events database, an EKEventStore.

EKEventStore *eventDB = [[EKEventStore alloc] init];
 
Then we create a new event object:

EKEvent *myEvent  = [EKEvent eventWithEventStore:eventDB];
 
Now we set some basic properties:

myEvent.title     = @"New Event";
myEvent.startDate = [[NSDate alloc] init];
myEvent.endDate   = [[NSDate alloc] init];
myEvent.allDay = YES;
 
I’m setting the start date and end date the same because it is not going to last more than one day and later I it will be an all-day event.
Next we must specify a calendar for that event. I am going to set it to the default calendar.

[myEvent setCalendar:[eventDB defaultCalendarForNewEvents]];
 
Before we save the event we need to check for errors:

NSError *err;
[eventDB saveEvent:myEvent span:EKSpanThisEvent error:&err];
 
Now we test to see if there are any errors. If there are no errors we tell the user that the event was successfully created.

if (err == noErr) {
 UIAlertView *alert = [[UIAlertView alloc]
  initWithTitle:@"Event Created"
  message:@"Yay!"
  delegate:nil
  cancelButtonTitle:@"Okay"
  otherButtonTitles:nil];
 [alert show];
 [alert release];
}
 
Apple strongly recommends that you notify the user that the event was created. If you create events in the background and you don’t tell the user, Apple will probably reject your app from the App Store, so it is always a good idea to do this.
And last we release the objects:

[startDate release];
[endDate release];
[eventStore release];
 
The final look of the method should be this:

-(IBAction) newEvent {

 EKEventStore *eventDB = [[EKEventStore alloc] init];

    EKEvent *myEvent  = [EKEvent eventWithEventStore:eventDB];

 myEvent.title     = @"New Event";
    myEvent.startDate = [[NSDate alloc] init];
    myEvent.endDate   = [[NSDate alloc] init];
 myEvent.allDay = YES;

    [myEvent setCalendar:[eventDB defaultCalendarForNewEvents]];

    NSError *err;

    [eventDB saveEvent:myEvent span:EKSpanThisEvent error:&err]; 

 if (err == noErr) {
  UIAlertView *alert = [[UIAlertView alloc]
   initWithTitle:@"Event Created"
   message:@"Yay!?"
   delegate:nil
   cancelButtonTitle:@"Okay"
   otherButtonTitles:nil];
  [alert show];
  [alert release];
 }
}
 
Now let’s go to interface builder to create the GUI for our App.

Building the Interface

Open EventAppViewController.xib in the Resources folder.
Add a Round Rect Button and put some text in it.

iPhone Advance Programming | Event kit Framework | image 2

Then connect it to the newEvent method.
Save and quit Interface Builder and we are done.

Conclusion

That was easy, wasn’t it?
Apple also included a UI kit for creating events that looks exactly like the calendar app, but it is so easy that i don’t need to explain it, i’m going to leave it to you as homework.

Thursday, March 3, 2011

Code Sign Errors: profile doesn’t match any valid certificate/private key pair in the default keychain




Your Ad Here


In order to audit and test the app on device before app submission you would have to build the app with valid matching certificate and provisioning profile.
Possible causes to trigger a Code Sign error
  • switch from individual developer account to corporate developer account on the same mac
  • upgrade iPhone SDK version
  • upgrade device iPhone OS version
  • switch to another mac
  • lost public/private key
  • certificate expired
  • certificate invalid
Possible Code Sign Errors:
  • developer certificate can’t be found
  • provisioning profile can’t be found
  • profile doesn’t match any valid certificate/private key pair in the default key chain
Code sign errors will cause device errors showing orange light
I am sharing the process of removing the Code Sign errors and/or device error orange light. Keep in mind you would have to follow the Program Portal instruction for detail steps. Don’t skip any steps or mix any step orders.
Provisioning profile would throw you errors if you didn’t use the valid certificate to generate the public/private key and provisioning profiles.
What can be kept the same
  • App IDs
  • Devices
  • Provisioning
First of all you must delete the following to avoid unnecessary troubleshooting and matching conflicts on your mac in keychain, harddrive, Xcode organizer, and on your device.
  • keychain > developer certificate
  • keychain > private/public key
  • device > settings > general > profile > provisioning profile
  • xcode > organizer > devices > provisioning > provisioning profile
  • iphone configuration utility > provisioning profiles > provisioning profile
Note: iphone Configuration Utility is a FREE tool you can download. If you don’t have it you can manually delete the provisioning profile.
Revoke your certificate even it is not expired. Revoke your existing certificate and re-create your certificate again. Every step must be executed in the right order. If you forget a single step you would get the same Code Sign error.

Step 1) Generate a Certificate Signing Request (CSR)
Applications > Utilities > Keychain Access
In the Preferences menu, set Online Certificate Status Protocol (OSCP) and Certificate Revocation List (CRL) to “Off”.
Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority
Name and email address entered matches the information that was submitted when you registered as an iPhone Developer.
Select the ‘Saved to Disk’ radio button and if prompted, select ‘Let me specify key pair information’ and click ‘Continue’. No CA (Certificate Authority) Email Address is required.
If ‘Let me specify key pair’ was selected, specify a file name and click ‘Save’. In the following screen select ‘2048 bits’ for the Key Size and ‘RSA’ for the Algorithm. Click ‘Continue’.

CertificateSigningRequest.certSigningRequest CSR file on your desktop is created on desktop.

Step 2) Submitting a Certificate Signing Request for Approval
Revoke Certificate.
Certificates > Development
Request a certificate. Click the ‘Choose file’ button, select your CSR and click ‘Submit’. Request your page until you see the update status. Click the ‘Approve’ button.

Step 3) Download & Install Developing Certificates
Certificates > Distribution
Control-click the WWDR Intermediate Certificate link and select “Saved Linked File to Downloads” on desktop
Double-click the WWDR Intermediate certificate AppleWWDRCA.cer to launch Keychain Access and install
Download their certificates. Double-click the downloaded .cer file developer_identity.cer to launch Keychain Access and install your certificate.

To verify the success install, you would see 
1) Apple Worldwide Developer Relations Certfication Authority Certificate
2) iPhone Developer: Team Leader Certificate

Step 4) Update the existing Provisioning
No need to remove the provisioning profile. Click the ‘Modify’ button. Check the box on Certificates. Submit to save the Development Provisioning Profile.

Step 5) Install a Development Provisioning Profile
Download the profile developer_profile.mobileprovision

Drag the downloaded file into the ‘Organizer’ window within Xcode. This will automatically copy the .mobileprovision file to the proper directory. If the directory does not exist you will need to create it. Click on the ‘+’ button in the Provisioning section of the Organizer window to install your .mobileprovision file.

Step 6) Build & Install App on device
Launch Xcode and open your project. Select ‘Device – iPhone OS’ from the ‘Device | Debug’ drop down menu.
Highlight the project Target and select the ‘Info’ icon from the top menu bar. Navigate to the ‘Build’ pane. Click the ‘Any iPhone OS Device’ pop-up menu below the ‘Code Signing Identity’ field and select the iPhone Development Certificate/Provisioning Profile pair you wish to sign and install your code with.
Your iPhone Development certificate will be in bold with the Provisioning Profile associated with it in grey above. In the example below, ‘iPhone Developer: Team Leader’ is the Development Certificate and ‘My Development Provisioning Profile’ is the .mobileprovision file paired with it.
If the private key for your iPhone Development certificate is missing, or if your iPhone Development certificate is not included in a provisioning profile, you will be unable to select the iPhone Development Certificate/Provisioning Profile pair and you will see the following. Re-installing the private key or downloading a provisioning profile with your iPhone Development certificate included in it will correct this.

Xcode Program received signal: “EXC_BAD_ACCESS”




Your Ad Here

Program received signal: “EXC_BAD_ACCESS”

This error occurs in device not simulator.

Possible Errors:
  • access protected or non-existent memory space as result of a bad pointer
  • access memory without alloc init
  • access memory after released object
  • remove the [object release] only if you don’t use alloc/copy/retain
  • trying to access release objects
  • illegal memory access
DebuggingAutorelease

http://www.cocoadev.com/index.pl?DebuggingAutorelease
This show you how to use Cocoa’s NSZombie debugging class and the command line “malloc_history” tool to find exactly what released object has been accessed in you code.
Running Instruments and checking for leaks will not help troubleshoot EXEC_BAD_ACCESS.

Debugging Techniques
http://www.cocoadev.com/index.pl?DebuggingTechniques

NSAssert() call to validate method parameters is to track and avoid passing nils set a breakpoint on objc_exception_throw.

iPhone Server Communication




Your Ad Here


We can communicate in many ways with server. One way is using JSON, this is explained as below:

 

1. Starting Communication

[[[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:
[NSURL URLWithString:urlStr]] delegate:self startImmediately:YES] autorelease];
//urlStr is NSString ref
 
2. Start receiving data
- (void)connection:(NSURLConnection *)connection didReceiveResponse:
   (NSURLResponse *)response{
    [receivedData setLength:0];
}
//receivedData is NSMutableData ref
 
3. Get data in chunks

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    [receivedData appendData:data];
}

4. After getting complete data
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
 
    // json data retrival complete    (received data is NSMutableData)
    if(receivedData == nil || [receivedData length] == 0){
  //handle error in getting response, may be no network
        [self showAlertViewWithTitle:@"No network" andMessage:@"Could not open the
   page, because internet connection was not found." andTag:0];
        return;
    }
 
    NSString *responseBody = [[[NSString alloc] initWithData:receivedData
             encoding:NSUTF8StringEncoding] autorelease];
    ////NSLog(responseBody);
    NSError *jsonParsingError = [[[NSError alloc] init] autorelease];
    NSDictionary * jsonData = [NSDictionary dictionaryWithJSONData:
          [responseBody dataUsingEncoding:NSUTF8StringEncoding]
                error:&jsonParsingError];
 
    NSDictionary *serverError = [jsonData objectForKey:@"error"]==[NSNull null]?
 nil:[jsonData objectForKey:@"error"];
 
    if(serverError==nil || [serverError count]==0){
        // no error found else
        NSDictionary *response = [jsonData objectForKey:@"response"]==[NSNull null]?
  nil:[jsonData objectForKey:@"response"];
        NSArray *array = [response objectForKey:@"array"]==[NSNull null]?nil:
  [response objectForKey:@"array"];
// I have left the code incomplete, its just for understanding
 
5. Check for null values
Never forget to check null values from JSON data as follows:
NSString *dataValue =  [[[array objectAtIndex:i] objectForKey:@"key"]==[NSNull null]?
                         @"0":[[array objectAtIndex:i] objectForKey:@"key"] intValue]; 
 
Please let me know if you require further explanation..

Changing UIView’s Coordinate System




Your Ad Here


Some times in our app we want to change the coordinate system of UIView, mostly when we change the orientation of iPhone like Landscape Right or left, So that time, coordinate system as well as appearance of UIView should also change, so here is the code to do that…

        application.statusBarHidden=YES;
       
        [application setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:YES];
       
        myUIView = [[MyUIView alloc] initWithFrame:CGRectMake(0,0,480,320)]; //as now width and height is changed
       
        [window addSubview:myUIView];
       
        CGAffineTransform transform=CGAffineTransformIdentity;
        transform=CGAffineTransformRotate(transform, (90.0f*22.0f)/(180.0f*7.0f));
        transform=CGAffineTransformTranslate(transform, 80, 80);
        [myUIView setTransform:transform];

Now all your subview’s of myUIView will also get transformed. and you will get CGPoint’s of new coordinate system on touch events.

UIImageView AnimationImages Memory Leak Mistry Solved




Your Ad Here




UIImage Tips…




Your Ad Here






There are several things which we need to keep in mind when we use UIImage as follows:

1. UIImage has 4 class methods to create its autorelease instance and 3 instance methods to load image.

2. When you want to cache the image, so that it can be easily loaded in no time whenever required, you should use:

UIImage *tempImage=[UIImage imageNamed:"test.png"]; where test.png is the image which will get cached at the first time and will remain in the memory untill app exits. But this should be used only when the image is less in memory size, otherwise your app will have less real and virtual memory for further use. This returns autorelease instance of UIImage that means you do not need to care about its release.

3. When you want to load the big memory sized images you should use:
UIImage *tempImage=[UIImage imageWithContentOfFile:[[NSBundle mainBundle] pathForResource:@”test” ofType:@”png”]];

this will not cache the image in the memory for the next time use i.e. when you again load this image this will be loaded from file.

4. When you don’t want autorelease instance i.e you want to manage its alloc free things on your own you should use:
UIImage *tempImage=[[UIImage alloc] initWithContentOfFile:[[NSBundle mainBundle] pathForResource:@”test” ofType:@”png”]];

but you have to release it when you are done with it as follows:
[tempImage release];

5. Whenever you set the UIImage’s instance to UIImageView’s image property, the retain count of UIImage’s instance increases by 1. So never forget to nil the UIImageView’s image property when you are done with it otherwise the UIImage will remain in the virtual memory and will leak.
Example:

UIImageView *imgView=[[UIImageView alloc] initWithImage:tempImage];
//now tempImage’s retain count will get increase by one. So nil the imgView’s image property whenever you are done with tempImage like this:
imgView.image=nil;

The difference b/w autorelease and normal instance is that we have to release the normal instance by calling release method to free up the memory

Objective C class: Simple Image Cache singleton




Your Ad Here


While working on an iPhone application which makes use of remote data and remote image assets, I realised I needed an object that could quickly and easily cache remote images in the app’s local sandboxed “Documents” folder. To be honest, I kind of feel like I reinvented someone’s wheel here, but I got exactly what I wanted – a small class (singleton) that provides full remote image (not limited to images though) caching within the sandboxed environment on an iPhone.

The class is implemented as a singleton since I can’t imagine one would want multiple storage caches running around interfering with each other.

ImageCache.h

//
//  ImageCache.h
//
//  Created by Tariq Mohammad on 03/03/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import 


@interface ImageCache : NSObject 
{
 NSString *documentsDirectory;
 NSString *cacheFileUrl;
 NSMutableDictionary *dictCache;
}

@property (nonatomic, retain) NSDictionary *dictCache;

+ (ImageCache*) instance;

- (BOOL) isRemoteFileCached:(NSString*)url;
- (NSData*) getCachedRemoteFile:(NSString*)url;
- (BOOL) addRemoteFileToCache:(NSString*)url withData:(NSData*)data;

@end
ImageCache.m

//
//  ImageCache.m//
//  Created by Tariq Mohammad on 03/03/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "ImageCache.h"

#define kDefaultCacheFile @"imagecache.plist"

//////////////////////////////////////////////////////////////////////////////////////////////////

@interface ImageCache (private)
- (NSString*) makeKeyFromUrl:(NSString*)url;
@end//private ImageCache interface

//////////////////////////////////////////////////////////////////////////////////////////////////

static ImageCache *sharedInstance = nil;

//////////////////////////////////////////////////////////////////////////////////////////////////

@implementation ImageCache
@synthesize dictCache;

////////////////////////////////////////////////////////////////////////////////

- (id)init
{
 if ( (self = [super init]) )
 {
  
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  
  documentsDirectory = [paths objectAtIndex:0];
  
  // the path to the cache map
  cacheFileUrl = [documentsDirectory stringByAppendingPathComponent:kDefaultCacheFile];
  [cacheFileUrl retain];

  dictCache = [[NSDictionary alloc] initWithContentsOfFile:cacheFileUrl];
  
  if ( dictCache == nil )
  {
   dictCache = [[NSMutableDictionary alloc] init];
  }
 }
 
 return self;
}

////////////////////////////////////////////////////////////////////////////////

- (void)dealloc
{
 [cacheFileUrl release];
 [dictCache release]
 [super dealloc];
}

////////////////////////////////////////////////////////////////////////////////

+ (ImageCache*) instance
{
 @synchronized(self)
 {
  if ( sharedInstance == nil )
  {
   sharedInstance = [[ImageCache alloc] init];
  }
 }
 return sharedInstance;
}

////////////////////////////////////////////////////////////////////////////////

- (BOOL) isRemoteFileCached:(NSString*)url
{
 NSString *imageFilename = [dictCache valueForKey:[self makeKeyFromUrl:url]];
 
 return (imageFilename != nil);
}

////////////////////////////////////////////////////////////////////////////////

- (NSData*) getCachedRemoteFile:(NSString*)url
{
 NSString *imageFilename = [dictCache valueForKey:[self makeKeyFromUrl:url]];
 NSData *data = nil;
 
 if ( imageFilename != nil )
 {
  data = [NSData dataWithContentsOfFile:imageFilename];
 }
 
 return data;
}

////////////////////////////////////////////////////////////////////////////////

- (BOOL) addRemoteFileToCache:(NSString*)url withData:(NSData*)data
{
 BOOL result = NO;
 NSString *imageFilename = [url lastPathComponent];
 
 if ( imageFilename != nil )
 {
  // the path to the cached image file
  NSString *cachedImageFileUrl = [documentsDirectory stringByAppendingPathComponent:imageFilename];

  result = [data writeToFile:cachedImageFileUrl atomically:YES];
  
  if ( result == YES )
  {
   // add the cached file to the dictionary
   [dictCache setValue:cachedImageFileUrl forKey:[self makeKeyFromUrl:url]];
   [dictCache writeToFile:cacheFileUrl atomically:YES];
  }
 }
 
 return result;
}

////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Private Methods

- (NSString*) makeKeyFromUrl:(NSString*)url
{
 NSString *key = [url stringByReplacingOccurrencesOfString:@"/" withString:@"."];

 key = [key stringByReplacingOccurrencesOfString:@":" withString:@"."];
 return key;
}

@end

Let me know if you find this useful, or have suggestions on improving it.

 
Submit Express Inc.Search Engine Optimization Services