Cocoa in the Shell

Automatic Network Activity Indicator with NSURLConnection

It’s very common to use networking APIs on mobile devices because of their nature. On iOS I’m sure every programmer is familiar with NSURLConnection and the network activity indicator, which we are suppose to show when we do networking, and managing it is a pain in the ass. Let’s correct this.

Keeping the Network Activity Indicator in sync

I’m pretty sure you encountered this problem, how can you keep the indicator synced in a multithreaded application ?
Showing it when an operation starts is easy, but how do we know when to hide it ?
We can easily solve this problem with a category on UIApplication that counts the number of active connections and show the indicator when this number is positive.

So let’s begin with the header

@interface UIApplication (NYX_NetworkActivityIndicator)
-(void)nyx_pushNetworkActivity;
-(void)nyx_popNetworkActivity;
@end

We just declare two methods, one to increase the counter and the other to decrease it.

Now the implementation.

#import "UIApplication+NetworkActivity.h"

static NSInteger __nyxNetworkActivityCount = 0;

@implementation UIApplication (NYX_NetworkActivityIndicator)

-(void)nyxRefreshNetworkActivityIndicator
{
    if (![NSThread isMainThread])
    {
        [self performSelectorOnMainThread:@selector(nyxRefreshNetworkActivityIndicator) withObject:nil waitUntilDone:NO];
        return;
    }
    self.networkActivityIndicatorVisible = (__nyxNetworkActivityCount > 0);
}

-(void)nyx_pushNetworkActivity
{
    @synchronized(self)
    {
        __nyxNetworkActivityCount++;
    }
    [self nyxRefreshNetworkActivityIndicator];
}

-(void)nyx_popNetworkActivity
{
    @synchronized(self)
    {
        if (__nyxNetworkActivityCount > 0)
            __nyxNetworkActivityCount--;
        else
            __nyxNetworkActivityCount = 0;
        [self nyxRefreshNetworkActivityIndicator];
    }
}

@end

First we need to declare a static variable to hold the count. Then, the push and pop methods just increment or decrement this variable and call a private method that shows or hides the network activity indicator accordingly.
Add the header in your Prefix.pch so that you can access these methods everywhere in your project.

Now before starting a NSURLConnection you call

[[UIApplication sharedApplication] nyx_pushNetworkActivity];

and when you are done

[[UIApplication sharedApplication] nyx_popNetworkActivity];

No more trouble managing this activity indicator across multiple threads, but wouldn’t it be great if the indicator just showed up when a NSURLConnection start, without the need for us to do it ?

Automatically showing the Network Activity Indicator when a connection start

This part is a bit more tricky, in order to achieve this we are gonna use method swizzling, if you are not familiar with this you can read this great post by Mike Ash.

We are gonna swizzle two NSURLConnection’s method :

  • initWithRequest:delegate:startImmediately: to start the indicator.
  • dealloc to stop the indicator. ARC forbids us to use a selector on dealloc, so we must trick it with NSSelectorFromString().

Now, create a new category on NSURLConnection, there is nothing in the header so we skip directly to the implementation.

First we include the proper header and then we just take the Swizzle function in Mike’s post.

#import "NSURLConnection+AutomaticIndicator.h"
#import <objc/runtime.h>

void swizzle_instance_method(Class c, SEL origSEL, SEL overrideSEL)
{
    Method origMethod = class_getInstanceMethod(c, origSEL);
    Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
    if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)))
        class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, overrideMethod);
}

Then we need to create our two custom methods.

-(id)nyxInitWithRequest:(NSURLRequest*)request delegate:(id)delegate startImmediately:(BOOL)startImmediately
{
    [[UIApplication sharedApplication] nyx_pushNetworkActivity];
    return [self nyxInitWithRequest:request delegate:delegate startImmediately:startImmediately];
}

-(void)nyxDealloc
{
    [[UIApplication sharedApplication] nyx_popNetworkActivity];
    [self nyxDealloc];
}

Finally in the load class method we swizzle.

+(void)load
{
    swizzle_instance_method(self, @selector(initWithRequest:delegate:startImmediately:), @selector(nyxInitWithRequest:delegate:startImmediately:));
    swizzle_instance_method(self, NSSelectorFromString(@"dealloc"), @selector(nyxDealloc));
}

Now just try to use a NSURLConnection with the method we just swizzled, you will see that the indicator shows up and disappears as you expect.