NSStreamEventOpenCompleted

TrailRunner BonjourTrailSyncService

TrailRunner BonjourTrailSyncService

TrailRunner can communicate with an iPhone app via Wifi. The main idea is that you can send recorded tracks from the iPhone to TrailRunner and also send tracks from TrailRunner to the iPhone.

Introduction
To make this happen, the code within this project must be available and running in both apps. In TrailRunner and your iPhone app.
The rest is just delegate methods being called and individual handling on both sides.

The following two use cases give a good overview on what is going on:

Send Route from TrailRunner to iPhone:
  • In TrailRunner user clicks on iPhone toolbar Icon > Send and selects a route
  • User clicks on the send button.
  • What happens now is that TrailRunner makes a Bonjour lookup to find an iPhone running your app that is waiting for a connection
  • When the connection was established, the selected route is being encoded as a GPX string and then being transferred to your app.
  • A delegate method gets called within you app, you receive the GPX string, decode it into CLLocations (No parser provided by me) and you're done.

Send recorded track from iPhone to TrailRunner
  • In TrailRunner user clicks on iPhone toolbar Icon > Receive. TrailRunner is now waiting for a connection.
  • User starts your app on the iPhone and selects a recoded track.
  • What happens now is that the bonjour lookup again connects your app to TrailRunner that is now waiting for data.
  • User taps on a send button within your app.
  • You now encode the track as a GPX String and send it over to TrailRunner.
  • TrailRunner receives the track and displays it.

Some additional words:
  • Both use case assume that in TrailRunner > Preferences > Synchronization the user has selected your app as the iPhone Helper app (requires an individual app identifier we must agree upon, for testing use com.trailrunnerx.racebunny as the helper application identifier)
  • Sending and receiving data can be handled within your application delegate. You start the Bonjour controller and then whatever happens, delegate methods -- you must implement against a required protocol -- will be called.

Sample Project
The TrailSocket sample code within this project contains the following sources:

/BEKit this contains some basic categories and extensions I use in all of my projects.
/Shared This contains the Bonjour communication "framework". You need to add this code along with the Apple CFNetwork.framework to your project
sample.gpx A sample GPX file used for testing
/Classes A sample implementation against the BonjourTrailSyncServiceDelegate protocol.

More details about the protocol below:

As with a web-browser talking to a web-server, two communication partners must agree upon who is server and who is client. Within the context of BonjourTrailSyncService assume the iPhone is always set up as a server and TrailRunner as a client -- Even though TrailRunner may send data --

To make both apps find each other, the server announces his availability via Bonjour in the local wifi network. The name of the service must be known by the client. This is typically the application bundle identifier of the server. (see mention of com.trailrunnerx.racebunny above)

When TrailRunner detects the server via Bonjour it opens an input and output stream. At the same time the server gets connected to these streams. Both are ready to communicate. As soon as a transfer is being initiated from either side, the receiver starts collecting data packets asynchronously in the run loop. When all data packets are collected, a receiver delegate method is being called containing the full data string from the source.

BonjourTrailSyncServiceDelegate Protocol

The sample project contains a protocol implementation within the TrackExchangeViewController class. The ViewController approach enforces that you can only send and receive data while that view controller is running. You may also shift the code over to your application delegate and have your app always be ready for communication.
As the communication overhead gets disposed once a transfer is done, it should be very lightweight while in idle mode.

within the viewDidLoad or applicationDidFinishLaunching: setup you local server:
NSString *applicationIdentifier = @"";
self.streamManager = [[BonjourTrailSyncService alloc] initWithController:self serviceIdentifier:applicationIdentifier isServer:true];
make sure that the streamManager is a retained reference.

Send track from TrailRunner to iPhone (sendDataFile:):
[streamManager sendDataString:dataString];
and then for notification that sending is done, implement
- (void)syncServiceDidFinishSending:(BonjourTrailSyncService *)service

To receive a track from TrailRunner, implement:
- (void)syncService:(BonjourTrailSyncService *)service didReceiveDataString:(NSString *)dataString

Within either sending or receiving, two events may occur. First, to realize that TrailRunner and your app find or loose each other implement:
- (void)updateForNetServiceDidDisconnect
- (void)updateForNetServiceDidConnect
You can use these methods to enable and disable a send button in your interface. Only between those two events TrarilRunner is really listening to you.

and during the course of data transfer which may last several seconds, you get notified within these methods:
- (void)syncServiceDidStartTransfer:(BonjourTrailSyncService *)service
- (void)syncService:(BonjourTrailSyncService *)service didFailWithError:(NSError *)error
whereas the events on success are already mentioned above.

to successfully get rid of the BonjourTrailSyncService instance, do the following in your closing method:
self.streamManager = nil;

And that´s it!

> Download Sample Code