2010/01/10

Asynchronous fetch in Core Data

Who of you Cocoa devs likes Core Data? Hands up... Whoa, I can see many hands up there. I share the same sentiment. For those of you who don't know what Core Data is don't bother reading further but here is a short introduction taken out from the docs:

The Core Data framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence. Its features include:t
- Built-in management of undo and redo beyond basic text editing
- Automatic validation of property values to ensure that individual values lie within acceptable ranges and that combinations of values make sense
- Change propagation, including maintaining the consistency of relationships among objects
- Grouping, filtering, and organizing data in memory and in the user interface
- Automatic support for storing objects in external data repositories
- Optional integration with Cocoa bindings to support automatic user interface synchronization

Fetching objects from persistent stores! That sounds nice. How does a typical fetch look like? Again taken out from the docs:

NSManagedObjectContext *context = <#Get the context#>;

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"<#Entity name#>" inManagedObjectContext:context];
[fetchRequest setEntity:entity];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"<#Sort key#>" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"<#Predicate string#>",
<#Predicate arguments#>];
[request setPredicate:predicate];

NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle error
}

[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];

Unfortunately Core Data currently doesn't have asynchronous fetch support built in. So if you have a large data set and/or have a complicated fetch request it takes time until it fetches your objects from the persistent stores. But most importantly the current thread blocks while the context executes the fetchRequest. If that thread is the main thread then your UI will hang and gives the impression to the user that the app has frozen. We don't want that, do we?

I've created IZManagedObjectContext, a subclass of NSManagedObjectContext, that extends it with asynchronous fetch feature. Let me modify the above snippet and show you have to use it:

IZManagedObjectContext *context = <#Get the context#>;

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"<#Entity name#>" inManagedObjectContext:context];
[fetchRequest setEntity:entity];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"<#Sort key#>" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"<#Predicate string#>",
<#Predicate arguments#>];
[request setPredicate:predicate];

[context executeFetchRequestAsynchronously:fetchRequest delegate:delegate];

When the fetch is complete the the delegate will be notified with
- (void)managedObjectContext:(IZManagedObjectContext *)context fetchCompletedForRequest:(NSFetchRequest *)request withResults:(NSArray *)results error:(NSError *)error;

Note that the difference between the two fetch methods is that the asynchronous one does NOT block the thread so your app looks snappy. That makes us happy.

If you are anxious to try it out and don't want to know the juicy details how it gets the job done then just head over here. I have licensed it under the BSD license.

How does it work, you ask?

It follows these guidelines. It fetches the objectIDs (which are immutable and safe to pass across thread boundaries) that satisfy the predicate of the fetch request on a separate thread using an NSOperation on a separate NSManagedObjectContext instance using the same persistentStoreCoordinator. Then it passes those objectIDs back the the main thread and issues a new fetch request with a predicate that asks for those specific objects with the objectIDs that we received. But I said it doesn't block the main thread? Well, I wasn't telling the truth. For my defense the second fetch should have only O(n) complexity if the executeFetchRequest:error: is optimized for fetching objectIDs. But in most of the cases this second fetch will execute faster than the original fetch request which had a more "complicated" predicate. And you really should be using a fetch limit when dealing with large data sets anyway.

I haven't included any example project that shows IZManagedObjectContext in action. You just have to take my word for it that it works. I use it in the latest alpha build of OpenMaps and haven't encountered any problems, yet. If you find bugs in it then please do notify me.
Reblog this post [with Zemanta]

2009/12/04

Is 8.8.8.8 Faster Than What You Are Using?

Nerds and bloggers had a huge geekgasm yesterday as Google introduced its Public DNS. Coming from Google, with a very sexy IP I must add, you are by religion obliged to switch over, right? It depends...

Let's experiment with the dig (with one g) command line utility. With help of dig you can query any DNS server for a specific domain. Just fire up your terminal and type for e.g. dig @8.8.8.8 google.com. That command queries the DNS server with IP 8.8.8.8 (the Google DNS server) to resolve google.com to an IP address. You should get something like this as output:


$ dig @8.8.8.8 google.com

; <<>> DiG 9.6.0-APPLE-P2 <<>> @8.8.8.8 google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44616
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;google.com. IN A

;; ANSWER SECTION:
google.com. 300 IN A 74.125.53.100
google.com. 300 IN A 74.125.67.100
google.com. 300 IN A 74.125.45.100

;; Query time: 39 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Dec  4 22:57:35 2009
;; MSG SIZE  rcvd: 76



So google.com has 74.125.45.100 74.125.67.100 and 74.125.53.100 as IPs. But most importantly what we are after is the query time: 39 msec. Is that fast? Let's compare it with other DNS servers.

In my tests I used two DNS servers from the two major ISPs from Romania, the sexy DNS server 8.8.8.8 and one of the DNS servers of OpenDNS. I looked up 3 different domains 100 times. Between the lookups I waited 30 seconds for the DNS servers' caches to clear a bit. For each domain lookup test I wrote a separate shell script. The shell script testing the DNS servers for wikipedia.org looks like:

#!/bin/bash

domain="wikipedia.org"

nameservers[0]="193.231.236.17"         # RDS
nameservers[1]="193.231.100.130"        # Romtelecom
nameservers[2]="8.8.8.8"                # Google
nameservers[3]="208.67.220.220"         # OpenDNS

iterations=100
sleeptimesecons=30

rm DNSQueryResults$domain.txt
for (( i=0;i<$iterations;i++ )); do
        echo "iteration count: $i"
        for (( j=0;j<4;j++ )); do
                echo -ne "`dig @${nameservers[$j]} $domain | grep ';; Query time:' | cut -d" " -f4`\t" >> DNSQueryResults$domain.txt
        done
        echo >> DNSQueryResults$domain.txt # newline
        sleep $sleeptimesecons
done

After formatting the data into a human readable format in an OpenOffice spreadsheet the charts are (my apologies for the colorblind):








Conclusion

Is it worth switching to Google's DNS servers?
 - yes IF your ISP provides you a very slow DNS server. Just look at RDS's DNS server response times in the charts. In case you were wondering I was on RDS's network during the testing.
- no IF your ISP provides you a decent DNS server that has a good cache policy because it will always have a better response time since you are physically closer to the DNS server (less hops till your IP packet reaches it) unless it is overladed

Bonus question: Is it worth switching over from OpenDNS to Google?
Currently OpenDNS provides a ton of extra features that Google currently doesn't e.g. content filtering at DNS level, botnet/malware protection, network shortcuts, etc. From the tests on average OpenDNS's DNS server response was a bit slower (by ~17 msec) than of Google's. Until Google coughs up some neat features for it's DNS service or it will be considerably faster than of OpenDNS my answer would be NO.

Want to reproduce the test at your machine? Just use the shell script and don't forget to post here your results in the comments.

2009/11/21

Meet IZURLConnection, the NSURLConnection with Reachability and easy Network Activity monitoring

This is my 2nd blog post on the topic of iPhone development and I am happy to share with you yet an another open source project of mine: IZURLConnection

Basically it mimics NSURLConnection, has the same delegate callbacks, and heck it uses NSURLConnection as it's back-end, BUT it has 2 nifty features:

1. Robust (uses Reachability v2.0)

Network connections on a mobile device are terrible. Or worse. Connection is spotty and if you happen to issue an NSURLConnection when the device is/goes dark (e.g. enters into a tunnel or a stargate wormhole) then you'll end up with a delegate call with an error like (all NSURLErrorDomain domain error codes): NSURLErrorNotConnectedToInternet, NSURLErrorCannotFindHost, NSURLErrorCannotConnectToHost, NSURLErrorTimedOut

In my real life experiments NSURLErrorNotConnectedToInternet is an error that is permanent and lasts longer while the others are transient. They are usually present when there's a glitch e.g. switching between network connection types (3G, EDGE, WiFi). IZURLConnection does not notify the delegate with NSURLErrorNotConnectedToInternet. It uses the Reachability class provided by Apple and waits until the host provided in the request will be reachable again and automatically retries. In the other (transient) error cases it retries by default 2 times before notifying the delegate of the error.

This takes a little bit of error handling off of your shoulders. But wait, there is more. Continue reading.

2. Simple and easy way to notify the user of network activity

When it starts or finishes loading it posts a NSNotification instance. If there are network connections out there then you are safe to assume that your network indicator on the device should be spinning and you should switch it on. Take a look at the following code snippet in the appdelegate:

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    
    networkConnections = 0;
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(IZURLConnectionDidStart:) 
                                                 name:IZURLConnectionConnectionDidStartNotification 
                                               object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(IZURLConnectionDidFinish:) 
                                                 name:IZURLConnectionConnectionDidFinishNotification 
                                               object:nil];     
    
    // Override point for customization after app launch    
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
}

- (void)updateNetworkActivityIndicator
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = (networkConnections != 0);
}

- (void)IZURLConnectionDidStart:(NSNotification *)notification
{
    // Only increment networkConnections if the request is a network protocol
    // Assuming that NSURLConnection handles the following protocols: ftp, http, https, file
    // Assuming that file is the only protocol that is not a network protocol
    IZURLConnection *connection = (IZURLConnection *)[notification object];
    if ([connection.request.URL.scheme isEqualToString:@"file"] == NO)
    {
        networkConnections++;
        [self updateNetworkActivityIndicator];
    }
}

- (void)IZURLConnectionDidFinish:(NSNotification *)notification
{
    // Only decrement networkConnections if the request is a network protocol
    // Assuming that NSURLConnection handles the following protocols: ftp, http, https, file
    // Assuming that file is the only protocol that is not a network protocol
    IZURLConnection *connection = (IZURLConnection *)[notification object];
    if ([connection.request.URL.scheme isEqualToString:@"file"] == NO)
    {
        networkConnections--;
        [self updateNetworkActivityIndicator];
    }
}

3. All your current NSURLConnection code will work if you refactor to IZURLConnection

Well, this isn't entirely true. IZURLConnection currently doesn't implement NSURLConnection's Runloop Scheduling methods:

- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode

If you wish to extend it though, go ahead. At the current state it gets the job done. I've never used runloop scheduling with NSURLConnection anyway.
Important note: Be prepared to receive multiple delegate messages as IZURLConnection may retry multiple times.

That's it. Take it away.

iPhone & "Growl" Notifications

Probably this is not a a good way to start a blog since I haven't introduced myself. And the short bio under my profile picture hardly says anything about me. Anyway, I have more important things to share with you right now and I can promise you that the mandatory introduction post will follow.

Ever wanted to notify the user of your iPhone application of certain events gracefully, like the desktop Growl does? Tweetdeck for the iPhone does it, in a way. Well, now you can too. Hereby I release the source code for IZGrowlManager under BSD license which allows you to do that.

So how does it look like? Here's a screenshot of it in action:




Pretty neat, huh?

You may ask why would someone use something like this to notify the user, there's UIAlertView for this. Growl project leader @boredzo was thinking the same when I asked him if there was a Growl framework for the iPhone which I could use. I must disagree. The main difference between the 2 notification approaches is that the UIAlertView disrupts completely the user's workflow because it doesn't let him/her continue until he/she pushes a button while the Growl approach can be dismissed very quickly also without any user interaction since the bubbles disappear after a while.

I'm sure he misunderstood me.

So, how can you use IZGrowlManager:

When you reach to a point where you want to notify the user of an event then create an IZGrowlNotification instance and pass it to the shared IZGrowlManager instance which takes care of the rest.


IZGrowlNotification *notification = [[IZGrowlNotification alloc] initWithTitle:@"Tip" description:@"Shake the device to reset the route" image:[UIImage imageNamed:@"information-symbol.png"] context:nil delegate:nil];
[[IZGrowlManager sharedManager] postNotification:notification];
[notification release];


If you want to track if the user tapped on the bubble belonging to the notification then set a delegate for the IZGrowlNotification object and implement the (only) delegate method

- (void)didSingleTapOnNotification:(IZGrowlNotification *)notification;

That's it. There are a few tweeks, just read the header file for more info.

Some constraints:
  • it's not a framework to interact with any other application on the device or on the desktop. It's a simple notification framework for you to notify the user of your app as if Growl would magically run in the background
  • currently there is only this 'Smoke' style available with no ability to modify it via the framework.
  • fixed sized notification bubbles only
  • 3 notification bubbles at a time. If new notification will come in they will be enqueued and posted when there will be a free spot available
  • bubbles will be appearing starting from the bottom right corner
  • image will be resized to 30x30

Here's the link again to the source on GitHub if you missed it in the first paragraph.