转:Jabber Client for iOS: XMPP Setup
转自:http://mobile.tutsplus.com/tutorials/iphone/building-a-jabber-client-for-ios-xmpp-integration/
Welcome to the third installment of our series on building a Jabber client with the iOS SDK. In this tutorial, we will add XMPP functionalities to the Application Delegate. Placing the functionality in the App Delegate will enable us to access XMPP functionalities from anywhere in the application easily.
Integrating XMPP in the AppDelegate
As mentioned, inserting the XMPP functionality in the App Delegate is a great way to make the functionality easily available throughout the app. At any place in your application code, you can access the App Delegate with the following code snippet:
1
|
[[UIApplication
sharedApplication] delegate] |
The XMPP class will dispatch events by means of protocols which we will define below. This is the list of events handled by this class:
- The client connected with the server
- The client authenticated with the server
- The client received a notification of presence (e.g. a user logged in)
- The client received a message
Let’s get started by adding the some property to the application delegate. First we need to import some XMPP stuff in the header:
1
|
#import
"XMPP.h" |
This is the minimal set of classes needed to build our application. If you want to digg into something more complex you can checkout the example bundled with the XMPP library repository. Here is our first implementation of this class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@class
SMBuddyListViewController; @interface
jabberClientAppDelegate : NSObject
{ UIWindow
*window; SMBuddyListViewController
*viewController; XMPPStream
*xmppStream; NSString
*password; BOOL
isOpen; } @property
( nonatomic ,
retain) IBOutlet
UIWindow *window; @property
( nonatomic ,
retain) IBOutlet
SMBuddyListViewController *viewController; @property
( nonatomic ,
readonly )
XMPPStream *xmppStream; -
( BOOL )connect; -
( void )disconnect; @end |
XMPPStream
will be the barebone of our client-server communication system and all messages will be exchanged through it. We will also define the methods to manage connection and disconnection. The implementation of this class is pretty complex, so we will break it down into many steps. First, we need a few more accessory methods to handle client-server communications. These can be private, so we place them in the implementation of the class:
1
2
3
4
5
6
7
8
9
10
11
|
@interface
JabberClientAppDelegate() -
( void )setupStream; -
( void )goOnline; -
( void )goOffline; @end @implementation
JabberClientAppDelegate @end |
Here, the most important is setupStream
, which creates the channel to manage the exchange of messages.
1
2
3
4
|
-
( void )setupStream
{ xmppStream
= [[XMPPStream alloc] init]; [xmppStream
addDelegate: self
delegateQueue:dispatch_get_main_queue()]; } |
Just two lines of code, but behind that many things happen. The dispatch_get_main_queue()
is a function which returns a reference
to the system level asynchronous execution mechanism, to which we can sumbit tasks and receive notifications. Here we “simply” tell
that our class is the delegate for the notifications sent from the main queue, which is run in the main thread of our application. See here for more details about Grand Central Dispatch.
Offline and Online functions are able to notify other users when we are connected or not. They are defined by sending an XMPPPresence
object through the socket. The server will dispatch the notification accordingly.
1
2
3
4
5
6
7
8
9
|
-
( void )goOnline
{ XMPPPresence
*presence = [XMPPPresence presence]; [[ self
xmppStream] sendElement:presence]; } -
( void )goOffline
{ XMPPPresence
*presence = [XMPPPresence presenceWithType: @"unavailable" ]; [[ self
xmppStream] sendElement:presence]; } |
The connect
method is the most important, for it manages the login operation. It returns a boolean representing whether the connection was successful or not. At first it sets up the stream, then it uses data stored in NSUserDefaults
to decorate the stream and call a connect message. An alert view is displayed if the connection is not successful.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
-
( BOOL )connect
{ [ self
setupStream]; NSString
*jabberID = [[ NSUserDefaults
standardUserDefaults] stringForKey: @"userID" ]; NSString
*myPassword = [[ NSUserDefaults
standardUserDefaults] stringForKey: @"userPassword" ]; if
(![xmppStream isDisconnected]) { return
YES ; } if
(jabberID == nil
|| myPassword == nil )
{ return
NO ; } [xmppStream
setMyJID:[XMPPJID jidWithString:jabberID]]; password
= myPassword; NSError
*error = nil ; if
(![xmppStream connect:&error]) { UIAlertView
*alertView = [[UIAlertView alloc] initWithTitle: @"Error" message:[ NSString
stringWithFormat: @"Can't
connect to server %@" ,
[error localizedDescription]] delegate: nil cancelButtonTitle: @"Ok" otherButtonTitles: nil ]; [alertView
show]; [alertView
release]; return
NO ; } return
YES ; } |
For sake of completeness, we also implement the disconnect method which is defined as follows:
1
2
3
4
5
6
|
-
( void )disconnect
{ [ self
goOffline]; [xmppStream
disconnect]; } |
Now that we have some of the basic functions we can use them in specific cases, for example, when the application becomes active or inactive.
1
2
3
4
5
6
7
|
-
( void )applicationWillResignActive:(UIApplication
*)application { [ self
disconnect]; } -
( void )applicationDidBecomeActive:(UIApplication
*)application { [ self
connect]; } |
We are left with the core of the system, the notifications of events and related behaviors, which we implement by means of protocols.
Defining Protocols
We will define two protocols, one for chat notifications like “a buddy went offline”, and one for dispatching messages received. The first protocol includes the description of three events:
1
2
3
4
5
6
7
|
@protocol
SMChatDelegate -
( void )newBuddyOnline:( NSString
*)buddyName; -
( void )buddyWentOffline:( NSString
*)buddyName; -
( void )didDisconnect; @end |
The first two messages are related to the presences of a buddy. We will react to these by adding or removing elements to the online buddies table. The third just notifies the server when our client disconnects. The second protocol is simpler, for it manages just the event of message reception.
1
2
3
4
5
|
@protocol
SMMessageDelegate -
( void )newMessageReceived:( NSDictionary
*)messageContent; @end |
For the sake of simplicity, to represent the message we will use a dictionary with two keys, @”msg” and @”sender”, to represent the actual message and the actual sender.
Implementing Protocols
Both protocols dispatch messages from the UIApplicationDelegate
. So we extend our main class by adding two properties (one for each delegate).
1
2
3
4
5
6
7
8
9
10
11
12
|
@interface
JabberClientAppDelegate : NSObject
{ ... __weak
NSObject
*_chatDelegate; __weak
NSObject
*_messageDelegate; } @property
( nonatomic ,
assign) id
_chatDelegate; @property
( nonatomic ,
assign) id
_messageDelegate; @end |
In the implementation we should remember to synthesize these properties.
1
|
@synthesize
_chatDelegate, _messageDelegate; |
Now our main class is ready to dispatch events to delegates. But which events? Those received from the Grand Central Dispatch. If you remember,
we have setup our UIApplicationDelegate
as a delegate for stream messages. Such delegates have the following signatures. The names
are pretty self explanatory, but we added comments within to make it even clearer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
-
( void )xmppStreamDidConnect:(XMPPStream
*)sender { //
connection to the server successful } -
( void )xmppStreamDidAuthenticate:(XMPPStream
*)sender { //
authentication successful } -
( void )xmppStream:(XMPPStream
*)sender didReceiveMessage:(XMPPMessage *)message { //
message received } -
( void )xmppStream:(XMPPStream
*)sender didReceivePresence:(XMPPPresence *)presence { //
a buddy went offline/online } |
Let’s start by authentication when we connect to the server.
1
2
3
4
5
6
7
|
-
( void )xmppStreamDidConnect:(XMPPStream
*)sender { isOpen
= YES ; NSError
*error = nil ; [[ self
xmppStream] authenticateWithPassword:password error:&error]; } |
When authentication is successful, we should notify the server that we are online.
1
2
3
4
5
|
-
( void )xmppStreamDidAuthenticate:(XMPPStream
*)sender { [ self
goOnline]; } |
When we receive a presence notification, we can dispatch the message to the chat delegate.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
-
( void )xmppStream:(XMPPStream
*)sender didReceivePresence:(XMPPPresence *)presence { NSString
*presenceType = [presence type]; //
online/offline NSString
*myUsername = [[sender myJID] user]; NSString
*presenceFromUser = [[presence from] user]; if
(![presenceFromUser isEqualToString:myUsername]) { if
([presenceType isEqualToString: @"available" ])
{ [_chatDelegate
newBuddyOnline:[ NSString
stringWithFormat: @"%@@%@" ,
presenceFromUser, @"jerry.local" ]]; }
else
if
([presenceType isEqualToString: @"unavailable" ])
{ [_chatDelegate
buddyWentOffline:[ NSString
stringWithFormat: @"%@@%@" ,
presenceFromUser, @"jerry.local" ]]; } } } |
The delegate will use these events to populate the online buddies table accordingly (see below). Finally, we are left with the message received notification.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-
( void )xmppStream:(XMPPStream
*)sender didReceiveMessage:(XMPPMessage *)message { NSString
*msg = [[message elementForName: @"body" ]
stringValue]; NSString
*from = [[message attributeForName: @"from" ]
stringValue]; NSMutableDictionary
*m = [[ NSMutableDictionary
alloc] init]; [m
setObject:msg forKey: @"msg" ]; [m
setObject:from forKey: @"sender" ]; [_messageDelegate
newMessageReceived:m]; [m
release]; } |
In this case, we build a dictionary as requested by the protocol and we call the corresponding method. At this point the core of our system is ready. We just have to make the user interface components react accordingly.
Hooking Up Views and Controllers
We start by modifying the buddy list controller, which manages the first view displayed when the app is started. We add the chat delegate to the interface as follows:
1
2
3
|
@interface
SMBuddyListViewController : UIViewController <..., SMChatDelegate> { @end |
We add a few access methods to point to the application delegate and stream:
1
2
3
4
5
6
7
|
-
(JabberClientAppDelegate *)appDelegate { return
(JabberClientAppDelegate *)[[UIApplication sharedApplication] delegate]; } -
(XMPPStream *)xmppStream { return
[[ self
appDelegate] xmppStream]; } |
We also have to extend the viewDidLoad
message to set our view controller as a delegate for the chat protocol.
1
2
3
4
5
6
7
|
-
( void )viewDidLoad
{ ... JabberClientAppDelegate
*del = [ self
appDelegate]; del._chatDelegate
= self ; } |
When the view appears, if credentials have been entered already, we call the connect method of the application delegate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
-
( void )viewDidAppear:( BOOL )animated
{ [ super
viewDidAppear:animated]; NSString
*login = [[ NSUserDefaults
standardUserDefaults] objectForKey: @"userID" ]; if
(login) { if
([[ self
appDelegate] connect]) { NSLog ( @"show
buddy list" ); } }
else
{ [ self
showLogin]; } } |
Finally, we have to add or remove objects from the array of online buddies according to the events dispatched by the application delegate.
1
2
3
4
5
6
7
8
9
|
-
( void )newBuddyOnline:( NSString
*)buddyName { [onlineBuddies
addObject:buddyName]; [ self .tView
reloadData]; } -
( void )buddyWentOffline:( NSString
*)buddyName { [onlineBuddies
removeObject:buddyName]; [ self .tView
reloadData]; } |
If you run the application now and a buddy comes online, the table view gets populated with his username as in the following figure:
Important Note: Depending on the server settings, you might need to wait for some time in order to receive the “new buddy is online” notifications. This time tends to be from 20 to 60 seconds.
To start a chat with the user we have to show the chat view when the corresponding cell is tapped.
1
2
3
4
5
6
7
|
-
( void )tableView:(UITableView
*)tableView didSelectRowAtIndexPath:( NSIndexPath
*)indexPath { NSString
*userName = ( NSString
*) [onlineBuddies objectAtIndex:indexPath.row]; SMChatViewController
*chatController = [[SMChatViewController alloc] initWithUser:userName]; [ self
presentModalViewController:chatController animated: YES ]; } |
To finalize the application we need to add the implementation of the message delegate to the chat view controller. The steps to do so are similar to those applied to the buddy list controller. We add the delegate in the interface file:
1
2
3
|
@interface
SMChatViewController : UIViewController < ... , SMMessageDelegate> @end |
We add accessors to the implementation:
1
2
3
4
5
6
7
|
-
(JabberClientAppDelegate *)appDelegate { return
(JabberClientAppDelegate *)[[UIApplication sharedApplication] delegate]; } -
(XMPPStream *)xmppStream { return
[[ self
appDelegate] xmppStream]; } |
We add the implementation of initWithUser:username
:
1
2
3
4
5
6
7
8
9
10
11
|
-
( id )
initWithUser:( NSString
*) userName { if
( self
= [ super
init]) { chatWithUser
= userName; } return
self ; } |
We extend the viewDidLoad
to declare the message delegate and we also set the text field as a first responder to keyboard input:
1
2
3
4
5
6
|
-
( void )viewDidLoad
{ ... JabberClientAppDelegate
*del = [ self
appDelegate]; del._messageDelegate
= self ; [ self .messageField
becomeFirstResponder]; } |
To send a message, we need to create an xml element as required by the XMPP protocol and send it over the stream. Here is how we update the sendMessage
method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
( IBAction )sendMessage
{ NSString
*messageStr = self .messageField.text; if ([messageStr
length] > 0) { NSXMLElement
*body = [ NSXMLElement
elementWithName: @"body" ]; [body
setStringValue:messageStr]; NSXMLElement
*message = [ NSXMLElement
elementWithName: @"message" ]; [message
addAttributeWithName: @"type"
stringValue: @"chat" ]; [message
addAttributeWithName: @"to"
stringValue:chatWithUser]; [message
addChild:body]; [ self .xmppStream
sendElement:message]; self .messageField.text
= @"" ; NSString
*m = [ NSString
stringWithFormat: @"%@:%@" ,
messageStr, @"you" ]; NSMutableDictionary
*m = [[ NSMutableDictionary
alloc] init]; [m
setObject:messageStr forKey: @"msg" ]; [m
setObject: @"you"
forKey: @"sender" ]; [messages
addObject:m]; [ self .tView
reloadData]; [m
release]; } } |
We are now done! You can test the final implementation of our iOS client. We start the server, iChat and our jabber client. After awhile, both clients should receive a presence notification and recognize each other as online. On the iPhone we tap on the online buddy and the chat view shows up. Now we are ready to chat. Here is a screenshot of the final application at work.
Source Code
The complete source code for this project can be found on GitHub here.