UPDATE: Get a free trial for Windows Azure Websites here.
A few weeks ago, I was talking with one of my teammates, Brady, about server to client communication and he introduced me to SignalR. SignalR is a library to facilitate asynchronous communication from a server to a client (as opposed to from a client to a server). If you haven’t heard about SignalR yet, I would highly recommend watching Brady’s presentation from a DevCamp in Arizona. If you’d rather skip the video, we can just say that the goal of SignalR is to make persistent HTTP connections super easy. What that means is that instead of just one way communications from a client to the server, the server would be able to communicate with the client really easily. Today we’ll look at how to create the server side component in .NET and how to consume it from either JavaScript or, more importantly, Objective-C.
An Example Hub
So let’s take a look at this in action. If you navigate to http://msdpe-signalrconnect.azurewebsites.net/HitCounter.html you’ll see an example of SignalR in action. I’ll warn you, at the time of writing, SignalR runs a little slow in Windows Azure Websites (as opposed to Cloud Services or a VM) but remember that all of this stuff is in preview / pre-release. So open that URL in a few different browser windows. If you want the full affect, then cascade each browser window so you can see the text update as you refresh one of them. This site is just a simple hit counter that keeps track of how many times the web page has been hit. Hit it once and it will say “This site has had 1 hit.” Hit it again and that updates to 2. If you’ve cascaded a few browsers with that page open, then you’ll see that when you refresh one of them, it will update them all. The code for this is actually incredibly simple and can be found on GitHub. This is actually the sample code that Brady made for the presentation mentioned above. Opening this (in Visual Studio 2012 Beta), you will see several solution folders. The only thing we’re concerned with is the Basics/BasicExamples project. There are actually two “endpoints” here: UserCounterConnection and HitCounterHub. Today we’re just going to look at the hit counter. If you open up Hubs/HitCounterHub.cs you’ll see the following code:
[HubName("hitCounter")] public class HitCounterHub : Hub { static int _hitCount; public void addHit() { _hitCount += 1; Clients.showHitCount(_hitCount); } }
Once you’ve included the proper libraries, that’s all you need to do to set up a SignalR Hub. This hub has one method that clients can call on it: addHit. That method adds to an internal counter and then calls showHitCount on all of the connected clients. Now all we have to do is implement a client that uses this. Let’s start with JavaScript.
The JavaScript Client
If you open up HitCounter.html, you’ll see how amazingly simple it is to connect to the hub:
<div id="currentHitCount"></div> <script type="text/javascript"> $(function () { var hub = $.connection.hitCounter; $.extend(hub, { showHitCount: function (hitCount) { if (hitCount > 1) { $('#currentHitCount') .html("This site has had " + hitCount + " hits."); } else { $('#currentHitCount') .html("This site has had " + hitCount + " hit."); } } }); $.connection.hub.start(function () { hub.addHit(); }); }); </script>
First you get an instance of the hitCounter hub. Then you extend that hub to handle the showHitCount method. That method just updates the currentHitCount div which is above the javascript. Last, you start the hub and call addHit on the hub once it’s finished starting. There really isn’t much more to say about this but that it’s easy.
For the purposes of this demo, I created a new website in the Windows Azure portal. Once that site is provisioned, you just need to publish the BasicsExample project to the website. For an example on setting up a new Windows Azure Website and pushing a site up to it, check out this walkthrough I posted a couple weeks ago. The walkthrough uses GIT to push the code to the server, however, since this is a .NET site, you could more easily do it using Web Deploy (look for “Deploy the application to Windows Azure”). With that done, we can get into the iOS stuff now.
The iOS Client
Now if you had to implement the SignalR backend in Objective-C, you wouldn’t be reading this article because I wouldn’t have written it. Thankfully, someone has already gone through the hard work of doing that for us. Released by a team from a company named DyKnow, SignalR-ObjC is an active project on GitHub that facilitates making SignalR connections from an iOS or OSX client. The documentation on the Git site does leave something to be desired but you should be able to stumble your way through connecting SignalR to a project. Or you can just follow these steps. First, install Cocoapods. This can be done from the command line by executing this:
$ [sudo] gem install cocoapods $ pod setup
Now that Cocoapods is installed, you need to add a podfile to your project directory. Navigate to the root of your XCode project and create a new file named podfile and fill it with the following contents:
platform :ios, '5.0' pod 'SignalR-ObjC'
If you leave off the “, ‘5.0’” and try to install the pods, you’ll end up getting this error:
[!] SignalR-ObjC (0.5.2) is not compatible with iOS 4.3.
I’m not exactly sure why that is. If you create a new project in XCode (today) it isn’t set to compile for 4.3. There must be some specification in the pod that doesn’t like how a default project is set up. Fixing this is as easy as leaving the “, ‘5.0’” in your podfile as part of the platform. After that, execute this command in the terminal (after you’ve navigated to your root directory):
$pod install
You should see something like this for the output:
Updating spec repo `master' Installing AFNetworking (1.0RC1) Installing SignalR-ObjC (0.5.2) Generating support files [!] From now on use `projectname.xcworkspace'. -> Integrating `libPods.a' into target `projectname' of Xcode project `projectname.xcodeproj'.
This installs both the SIgnalR-ObjC dependency and the AFNetworking library which the SignalR one depends on. Also, note that it has created a new workspace (in this case named projectname.xcworkspace) and instructs you to use that from now on. If you had your project open in XCode, close that and open the newly generated workspace. That workspace will contain a Pods project and your original project. For my sample project, I’ve just created a simple Single View Application. To the storyboard for my single view controller, I’ve added a label and a button:
After adding your UI elements, you’ll need to tie the button to an IBAction and the label to an IBOutlet. After doing so, here’s the code in my ViewController.h:
@interface ViewController : UIViewController
- (IBAction)tapAddHit:(id)sender;
@property (weak, nonatomic) IBOutlet UILabel *txtHitCount;
@end
Flipping over to the ViewController.m you’ll need to add an import for SignalR:
#import "SignalR.h"
After the txtHitCount has been synthesized, we’ll add an instance of SRHubProxy to keep track of our hub:
@implementation ViewController @synthesize txtHitCount; SRHubProxy *myHub;
Now in the viewDidLoad method, we’ll create a new connection, get a hub for the hit counter, specify handlers for client side methods and then start the connection:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. SRHubConnection *connection = [SRHubConnection connectionWithURL: @"http://msdpe-signalrconnect.azurewebsites.net"]; myHub = [connection createProxy:@"HitCounter"]; [myHub on:@"showHitCount" perform:self selector:@selector(notificationReceived:)]; connection.started = ^{ [myHub invoke:@"addHit" withArgs:nil]; }; [connection start]; }
Notice that the connection just specifies the root URL of our site. The hub proxy then specifies the hub name. Note the capitalization here as it’s a little different than the javascript in the html page (that had “hitCount” and starts with a lower letter). The on call in between createProxy and start is how well tell it what method should be called whenever showHitCount is called by the server. Finally, we set the connection’s started property to be a block that will be called when start is finished. Last, we call start on the connection. Now we need to implement that notificationReceived method that will be called whenever showHitCount is called by the server:
- (void)notificationReceived:(id)message { //do something with the message NSLog(@"%@",message); txtHitCount.text = [@"There have been " stringByAppendingFormat:@"%@ views", message]; }
All we’re doing is logging what is sent by the server and then using the same value to set the text of our label. The server just sends a number over when it calls showHitCount so we’ll do a little formatting and show it. Now we could be done, but so you can see things in action a little bit better, let’s make tapping the “Add Hit” button add another hit to the server:
- (IBAction)tapAddHit:(id)sender {
[myHub invoke:@"addHit" withArgs:nil];
}
Now when your app runs, after you tap the button, you should see the hit count increase in the app as well as in any browsers that are connected (it may take a few seconds):
And that’s it. Now we have a simple iOS app connecting to a server side hub with SignalR. I completely glazed over discussing any of the internals of how SignalR works, so if you’d like to know more about that, I’d start by reading this article by Scott Hanselman. The guys working on SignalR have some pretty ambitious plans for the technology (think 100k persistent connections when they release 1.0) so its bound to get faster and better at doing what it does. I’m hoping to follow this up with a more advanced sample in the next few weeks.
You can download the iOS client source code from this article here.
- .Net (34) ,
- Azure (83) ,
- Javascript (14) ,
- Objective-C (55) ,
- Phone (44) ,
- Web (39) ,
- XCode (50) ,
- iOS (71)
11 Comments
Tom McKearney
I am curious if this can, in any way, act as a replacement for Push Notifications on iOS. What would it NOT be able to do?
Chris
Provided the app is running on the device when you send the information, it certainly could replace the need for push notifications. However, when the app is in the background, you wouldn't be able to deliver information to it unless your listener could somehow run in a background thread that wouldn't be killed when your app was backgrounded. At this time, I don't think that's possible (AFAIK).
Yunas Qazi
libPods.a is missing in your sample project...
and why do we have to go through all the steps to create libPods ourselves ?
Are they compulsory for to be included in every project we create ?
Thanks
Chris
The instructions for installing the pod are located near "The iOS Client" header. I believe you would need to either install the pod or manually include the files for each project you create that you want to use it in.
yunas
Thank you, I run your project and it worked well. But, when I try to do the same with my server (Chat).
SRHubConnection *connection = [SRHubConnection connectionWithURL:
myServerUrl];
myHub = [connection createProxy:@"Join"];
[connection start];
the app crashes with following log:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Incompatible Protocol Version'
I would like to know the possible reasons of this failure.
Maximilian Alexander
Would the app retrieved messages that were sent while it was in background. That is, on reentering, would the missed message be retrieved?
Chris
These messages would be missed. There is no queuing mechanism in iOS to deliver the messages after the app is reopened. You might be able to catch this on the server / hub side and try to redeliver them later on if they don't get through successfully.
Maximilian Alexander
Yikes. I guess it would be a really important to mention in the documentation to call "GetAll" when reentering from the background...
Chris
I guess I assumed otherwise but your point is valid. I'll try to get this added in if i have a chance.
jbabu
hi Chris can define Clients.showHitCount what is role of this method
Chris
Clients.showHitCount tells the hub to call all of the listening clients and run their showHitCount method.