Search form

Documentation

iOS Tutorial

Overview

This content looks at the BasicService and BasicClient sample in the SDK. This is a simple demonstration wherein the client sends two strings to the service, the service concatenates the strings sent by the client and returns a single string.

Components Used to Write a Sample

The AllJoyn™ Objective-C bindings are based on the fact that any AllJoyn Objective C-code that you write will hit the native AllJoyn library.

When you download the AllJoyn SDK for iOS and OS X, the typical structure of the SDK and its components is followed:

  • build
  • alljoyn_objc
  • services
  • LICENSE
  • README.txt
  • README.md
  • ReleaseNotes.txt

The following sections define each one of these directories and their role.

build directory

If you were to build the AllJoyn source for Darwin (iOS/OS X) by issuing a SCons command, the build directory is the build output. The following directories are located under build:

  • about-docs
  • darwin
  • docs

about-docs and docs directories

The about-docs and docs directories contain the API documentation for the native library (C++) of the AllJoyn framework and the About feature.

darwin directory

Under the darwin directory you will find four folders: arm, armv7, armv7s and x86.

arm armv7 and armv7s directories

The arm folders contain the native libraries and header files for running the AllJoyn code over the corresponding versions of iOS devices.

alljoyn_objc directory

The alljoyn_objc directory contains most of the things relevant to an iOS developer. The following sections define each item.

AllJoynCodeGenerator

The code generator tool is the best way to get started generating the glue code that makes calls inside the native AllJoyn library.

AllJoynFramework

AllJoynFramework is the actual binding code that contains the implementation of the publicly exposed Objective-C bindings.

AllJoynFramework_iOS

AllJoynFramework_iOS is the actual binding code like the AllJoyn framework, but is more specific to iOS devices.

samples directory

The samples directory contains both iOS and OS X samples that give you ideas for using the AllJoyn Objective-C bindings.

Test directory

The Test directory contains a few tests that can be used to test the AllJoyn SDK.

Overview of files in the samples

The following section is common to both BasicService and BasicClient.

We first need to understand a few of the common files that are a part of the sample code. Not all files in an AllJoyn iOS sample need to be written from scratch. We have a code generator tool, which generates much of the glue code and you end up writing very little actual AllJoyn code. To create an AllJoyn Objective-C sample from scratch, the best place to start is the AllJoyn Programming Guide for the Objective-C Language. This document shows you how to make use of the code generator to define the interface and in turn generate glue code that makes a call in the native AllJoyn library. You do not have to make calls in the native library yourself.

If we use the code generator for BasicService and BasicClient, the code generated would be four files:

File name Description
BasicObject.h This file shows you the interface implemented by the BusObject that you defined using the xml as input to the code generator.
BasicObject.m This file has the Objective-C implementation of the BasicObject.
AJNBasicObject.h The Objective-C implementation of a bus object needs to map to a C++ implementation that calls in the native AllJoyn library. The code gen tool does this for you and produces the header and source files for this mapping. You need not write the C++ code or bother about how is it done.
AJNBasicObject.mm This file contains the implementation of C++ BusObject. In the sample, the C++ BusObject is called BasicObjectImpl.

There are other files in the sample as listed below:

  • ViewController.h: This is the View Controller header file of this application.
  • ViewController.mm: This contains the implementation of the View Controller for this application.

Walkthrough of BasicService

Developers who are not familiar with app development for the iOS platform can refer to the Apple developer documentation, which has a set of rich tutorials to understand how a typical iOS app is structured. For ease of understanding, if you are new to iOS development, an iOS app is structured based on MVC architecture, which means that you will have the following:

  • A View, which is your UI and since we use Xcode, this corresponds to Storyboard.
  • A Controller, which is your ViewController.h and ViewController.mm in the case of our BasicService and BasicClient sample
  • A Model, which are BasicService.h and BasicService.mm in the case of BasicService and BasicClient.h; and BasicClient.mm in the case of BasicClient

Let us look at it from the perspective of how an app is loaded and what parts of the source code are called and what do they mean

When the BasicService app loads up, the viewDidLoad function in ViewController.mm is called:

Code 1

- (void)viewDidLoad
{
   [super viewDidLoad];
self.basicService = ];

[self.delegate didReceiveStatusUpdateMessage:[NSString
stringWithFormat:@"AllJoyn Library build info: %@\n",
[AJNVersion buildInformation]]];

The first few lines of this method print out debugging information. There are two calls that are interesting.

Notice we call didReceiveStatusUpdateMessage using the delegate that we had declared in Code 3. This updates the text view of the app with the message parameters that we send it. In the two calls above, we send the AllJoyn library version and a build info string.

Next, we create a BusAttachment and assign it to the property named bus, which we declared in BasicService.h file. See Code 5.

Code 5

self.bus = ;

In the end, we perform cleanup by unregistering the bus object, releasing the lock on the condition property and deallocating the bus property by setting it to nil.

Code 17

[self.bus unregisterBusObject:self.basicObject];
[self.lostSessionCondition unlock];
self.bus = nil;

We have a several callback methods that are a part of three different listeners. A brief description of each one grouped by listener category is mentioned in the sections that follow.

AJNBusListener methods

Code 18

(void)listenerDidRegisterWithBus:(AJNBusAttachment*)busAttachment

This is invoked when registerBusListener is completed successfully and the bus notifies the application about it.

Code 19

(void)listenerDidUnregisterWithBus:(AJNBusAttachment*)busAttachment

This is invoked when the BusListener unregisters from the bus.

Code 20

(void)nameOwnerChanged:(NSString*)name to:(NSString*)newOwner
from:(NSString*)previousOwner

This is called when there is a change in the ownership of a name on the bus. The name could be a unique name or a well-known name.

Code 21

(void)busWillStop

This is called when a BusAttachment, with which a listener is registered, is stopping.

Code 22

(void)busDidDisconnect

This is called when a BusAttachment, with which this listener is registered, has become disconnected from the bus.

AJNSessionPortListener methods

Only the session host uses this category of listener since it will not have any effect on the client side.

####Code 23

(BOOL)shouldAcceptSessionJoinerNamed:(NSString*)joiner
onSessionPort:(AJNSessionPort)sessionPort
withSessionOptions:(AJNSessionOptions*)options

This is one of the most important callback functions that almost every service will have. When a peer initiates a join session for the session hosted by a service this callback is invoked so that the service may accept or reject the join session request initiated by the client. It gives us the unique name of the joiner who initiated the join session request, the port of the session used and the session options that the client wishes to use for the life of this session.

Code 24

(void)didJoin:(NSString*)joiner inSessionWithId:(AJNSessionId)sessionId
onSessionPort:(AJNSessionPort)sessionPort

If a service accepts a join session request sent by a client, the client joins the session and the service is notified about the successful joining of the client through this callback. This means that the session is fully up and running. It indicates the unique name of the joiner, the session id that it is a part of and the session port which was used for joining the session.

AJNSessionListener methods

Code 25

- (void)sessionWasLost:(AJNSessionId)sessionId

The bus invokes this callback when an existing session becomes disconnected and is no longer valid.

Code 26

- (void)didAddMemberNamed:(NSString*)memberName
toSession:(AJNSessionId)sessionId

This callback indicates that a member with unique name memberName was added to the session with session id of the second argument.

Code 27

- (void)didRemoveMemberNamed:(NSString*)memberName
fromSession:(AJNSessionId)sessionId

This callback indicates that a member with unique name memberName is no longer a part of the session with session id having value sessionId.

This ends the description of code found under BasicService.

Walkthrough of BasicClient

The BasicClient app starts with a button being displayed, which says "Call Service". When the button is pressed, the didTouchCallServiceButton method is called.

Code 28

- (IBAction)didTouchCallServiceButton:(id)sender
{
   self.basicClient = ;

      if (self.sessionId) {
         self.foundServiceName = name;

         NSLog(@"Client joined session %d", self.sessionId);
         [self.delegate didReceiveStatusUpdateMessage:[NSString
stringWithFormat:@"JoinSession SUCCESS (Session id=%d)\n", self.sessionId]];
      }
      else {
         [self.delegate didReceiveStatusUpdateMessage:@"JoinSession
failed\n"];
      }

      [self.joinedSessionCondition signal];
   }
}

Let's see what is happening in here. We first compare if the name that we found is the same as what we were looking for or has the name we were looking for as its prefix. If we have already received the name, we return.

Code 38

   BOOL shouldReturn;
      @synchronized(self) {
         shouldReturn = self.wasNameAlreadyFound;
         self.wasNameAlreadyFound = true;
      }

      if (shouldReturn) {
         NSLog(@"Already found an advertised name, ignoring this name
%@...", name);
         return;
      }

Next, let us assume that we saw this name for the first time, which means that we can join the session.

Code 39

// Since we are in a callback we must enable concurrent
callbacks before calling a synchronous method.
//
   [self.bus enableConcurrentCallbacks];

The call to enableConcurrentCallbacks is in place so that we can make a synchronous call inside an asynchronous method.

Code 40

self.sessionId = [self.bus joinSessionWithName:name
onPort:kBasicClientServicePort withDelegate:self options:;

      if (self.sessionId) {
         self.foundServiceName = name;

         NSLog(@"Client joined session %d", self.sessionId);

         [self.delegate didReceiveStatusUpdateMessage:[NSString
stringWithFormat:@"JoinSession SUCCESS (Session id=%d)\n", self.sessionId]];
      }
      else {
         [self.delegate didReceiveStatusUpdateMessage:@"JoinSession
failed\n"];
      }

Next, we called joinSessionWithName. The arguments here are:

  • Name of the session we want to join
  • Port on which the session host is listening
  • A delegate class which will handle session related callbacks
  • Options to specify session parameters
  • Type of traffic that will go over the session
  • Boolean indicating if the session we are joining is a point-to-point or a multipoint session
  • Proximity type
  • Transport mask specifying the transport over which we want to join this session

Code 41

[self.joinedSessionCondition signal];

And, in the end, we signal the joinSessionCondition so that we can go ahead and make a method call on the service.

Going forward, in order to make a method call on the BasicService, we first need to create a proxy bus object, which will represent the Basic service object. Calling introspect makes us aware of the interfaces it implements so that we can make a correct method call.

The creation of proxy bus object requires the bus attachment, name of the service for which we are creating the proxy bus object, the object path of the remote object and the session id.

After creating the proxy bus object we need to know the interfaces that it contains so that we can call the desired method. For this purpose, we call the introspectRemoteObject method.

Finally, after introspecting the remote object we call the method concatenateString:withString:.

If the method call goes through successfully, we should have the concatenated value in the string "result".

Code 42

if ([self.joinedSessionCondition waitUntilDate:[NSDate
dateWithTimeIntervalSinceNow:60]]) {

      // once joined to a session, use a proxy object to make
the function call

//
      self.basicObjectProxy = [[BasicObjectProxy alloc]
initWithBusAttachment:self.bus serviceName:self.foundServiceName
objectPath:kBasicClientServicePath sessionId:self.sessionId];

      // get a description of the interfaces implemented by the
remote object before making the call
      //
      [self.basicObjectProxy introspectRemoteObject];

      // now make the function call
      //
      NSString *result = [self.basicObjectProxy concatenateString:@"Code "
withString:@"Monkies!!!!!!!"];

      if (result) {
         NSLog(@"[%@] %@ concatenated string.", result,
[result compare:@"Code Monkies!!!!!!!"] == NSOrderedSame ?
@"Successfully":@"Unsuccessfully");
         [self.delegate didReceiveStatusUpdateMessage:@"Successfully called

method on remote object!!!\n"];
      }

      self.basicObjectProxy = nil;

   }
   else {
      NSLog(@"Timed out while attempting to join a session with
BasicService...");
      [self.delegate didReceiveStatusUpdateMessage:@"Timed out while
attempting to join a session with BasicService..."];
   }

We looked at the flow the program when the user presses the 'Call Service' button on the UI of BasicClient. There is another button on the UI on BasicClient named 'Check Presence'. Let us take a look at the flow for that: objc

Code 43

- (void)sendPing
{
   dispatch_queue_t pingQueue =
dispatch_queue_create("org.alljoyn.basic-service.pingQueue",NULL);
   dispatch_async( pingQueue, ^{ [self ping];
   });
}

Code 43 is identical to the behavior we saw when we called the function run. Let us take a look at the function ping below.

Code 44

- (void)ping
{
   if objc(self.bus == NULL) {
      return;
   }
   QStatus status = [self.bus pingPeer:kBasicClientServiceName withTimeout:5];

   if (status == ER_OK) {
      [self.delegate didReceiveStatusUpdateMessage:@"Ping returned
Successfully"];
   } else {
      [self.delegate didReceiveStatusUpdateMessage:@"Ping Failed"];
   }
}

The function ping calls the public API pingPeer: withTimeout: inside AJNBusAttachment. It passes the well-known name of the BasicService and the timeout value that reflects how much time will the ping call wait before returning. On successfully ping, the text section on the UI should indicate that the ping call was successful.

This ends the description of BasicClient.

The two samples are in place to illustrate the most simplistic manner in which AllJoyn Objective-C bindings can be used. Using the AllJoyn framework, not only can you write apps that run on separate devices and talk to each other, but you can also write apps that communicate with each other on the same AllJoyn-enabled device. Moreover, the AllJoyn cross-platform support allows you to easily write your app for Android, Windows, and other platforms without having to worry about the problems that the AllJoyn framework solves.

The AllJoyn concepts that we discussed in this document remain consistent when we move to a different platform like Android so that the developer spends more time on his actual app rather than worry about the peer-to-peer piece. This gives you the ability to quickly write apps that work on different platforms and talk to each other, which is a very important thing in today's mobile application development ecosystem.

For future news and updates, please follow or visit https://t.co/MKr1Z9NPji
3 months 1 week ago