底层始于XMPP
首先构筑底层,底层为上层服务,也就是我们对XMPP框架提供接口编程的应用。
我是在AppDelegate中写的,可是为什么要在AppDelegate中写?
Nice question! Cuz the demo which the author gave was written in AppDelegate.
好吧,开个玩笑,不过按照我的理解,创建在AppDelegate中原因只是app的生命周期内,我们也只需要创建一个单例,一个xmppStream,然后进行接收传递等数据都靠这同一个流。
事实上完全可以在别的地方写,但是引用起来可能差强人意了,因为还是要引用同一个XMPPStream,怎样获取是个问题。
先看一下AppDelegate.h文件:
//
// AppDelegate.h
// Habber
//
// Created by Sunny on 12/15/15.
// Copyright © 2015 Nine. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <XMPP.h>
#import "Statics.h"
#import "HabberMessageDelegate.h"
#import "HabberChatDelegate.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate, XMPPStreamDelegate> {
//是否连接状态
BOOL isOpen;
}
@property (strong, nonatomic) UIWindow *window;
//用于传输xmpp协议数据的封装流。
@property (nonatomic, readonly) XMPPStream *xmppStream;
//密码
@property (nonatomic, strong) NSString *password;
@property (nonatomic, strong) id<HabberChatDelegate> chatDelegate;
@property (nonatomic, strong) id<HabberMessageDelegate> messageDelegate;
//XMPPStream的初始化
- (void)setupStream;
//连接功能
- (BOOL)connect;
- (void)disconnect;
//控制上下线
- (void)goOnline;
- (void)goOffline;
@end
可以看到它的主要功能就是这些了,继承XMPPStreamDelegate与服务器进行交互,并返回状态,剩下的交给代理(HabberChatDelegate、HabberMessageDelegate)去做。
这里isOpen这个在我给的代码中不用写也可以运行,本来想用来控制服务器是否连接状态的,
不过程序中没有用上。但是应该有一个对服务器判断的,减少不必要的开销和消除bug
接下来看看AppDelegate.m中做了什么:
//
// AppDelegate.m
// Habber
//
// Created by Sunny on 12/15/15.
// Copyright © 2015 Nine. All rights reserved.
//
#import "AppDelegate.h"
@interface AppDelegate ()
@property (strong, nonatomic) UIImageView *splashView;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//设置界面显示等,标题栏颜色,状态栏颜色,字体大小等
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
[[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:36.0/255 green:36.0/255 blue:36.0/255 alpha:0.9]];
[[UINavigationBar appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]}];
[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
[[UIBarButtonItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont boldSystemFontOfSize:13], NSFontAttributeName, nil] forState:UIControlStateNormal];
//程序打开自动连接服务器
[self connect];
[NSThread sleepForTimeInterval:1.5];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
}
- (void)applicationWillTerminate:(UIApplication *)application {
[self disconnect];
}
//XMPPStream初始化
- (void)setupStream {
_xmppStream = [XMPPStream new];
//设置线程
// [_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];
//像上面这样,放到其它线程中,那么代理和通知修改界面的时候就会出现问题,至于放到主线程中来,反正它里面集成的多线程操作可以应付消息传递了。
[_xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
//发送连接服务器请求
- (BOOL)connect {
[self setupStream];
//从本地取得用户名密码和服务器地址
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *userId = [defaults stringForKey:USERID];
NSString *pass = [defaults stringForKey:PASS];
NSString *server = [defaults stringForKey:SERVER];
//已经连接就不用再连接了
if ([_xmppStream isConnected]) {
return YES;
}
//没有用户名密码我也不去连接
if (userId == nil || pass == nil) {
return NO;
}
//设置用户
[_xmppStream setMyJID:[XMPPJID jidWithString:userId]];
//密码
_password = pass;
//设置服务器
[_xmppStream setHostName:server];
//连接服务器
NSError *error = nil;
if (![_xmppStream connectWithTimeout:5.0 error:&error]) {
return NO;
}
[_chatDelegate didConnect];
return YES;
}
- (void)disconnect {
[self goOffline];
[_xmppStream disconnect];
[_chatDelegate didDisconnect];
}
- (void)acceptFriendRequest {
}
//控制上下线
- (void)goOnline {
//发送在线状态
XMPPPresence *presence = [XMPPPresence presence];
[[self xmppStream] sendElement:presence];
}
- (void)goOffline {
//发送下线状态
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
[[self xmppStream] sendElement:presence];
}
#pragma mark - XMPPStreamDelegate实现
- (void)xmppStreamDidConnect:(XMPPStream *)sender {
isOpen = YES;
NSError *error = nil;
//验证密码
[[self xmppStream] authenticateWithPassword:_password error:&error];
}
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error {
[[NSNotificationCenter defaultCenter] postNotificationName:@"authenticateFail" object:nil];
}
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
[self goOnline];
[[NSNotificationCenter defaultCenter] postNotificationName:@"hasAuthenticated" object:nil];
}
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error {
isOpen = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:@"connectServerFailed" object:nil];
}
//收到消息后把消息传递给代理
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
//封装好的 文字 + 发送人
NSString *msg = [[message elementForName:@"body"] stringValue];
NSString *img = [[message elementForName:@"image"] stringValue];
if (!img) {
img = @"";
}
NSString *voice = [[message elementForName:@"voice"] stringValue];
NSString *voiceTime = [[[message elementForName:@"voice"] attributeForName:@"voiceTime"] stringValue];
if (!voice) {
voice = @"";
voiceTime = @"";
}
NSString *from = [[message attributeForName:@"from"] stringValue];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:msg forKey:@"msg"];
[dict setObject:img forKey:@"photo"];
[dict setObject:voice forKey:@"voice"];
[dict setObject:voiceTime forKey:@"voiceTime"];
[dict setObject:from forKey:@"sender"];
[_messageDelegate newMessageReceived:dict];
}
//收到好友状态
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence {
//取得好友状态
NSString *presenceType = [presence type];
//我的id
NSString *userId = [[sender myJID] user];
//对方状态(用user也就相当于强制类型转换成NSString)
NSString *presenceFromUser = [[presence from] user];
//如果在列表中把“我”过滤掉
if (![presenceFromUser isEqualToString:userId]) {
if ([presenceType isEqualToString:@"available"]) {
[_chatDelegate newBuddyOnline:[NSString stringWithFormat:@"%@@%@", presenceFromUser, @"thinkdifferent.local"]];
}
if ([presenceType isEqualToString:@"unavailable"]) {
[_chatDelegate buddyWentOffline:[NSString stringWithFormat:@"%@@%@", presenceFromUser, @"thinkdifferent.local"]];
}
//收到好友请求
if ([presenceType isEqualToString:@"subscribe"]) {
[_chatDelegate receivedFriendRequest:[NSString stringWithFormat:@"%@@%@", presenceFromUser, @"thinkdifferent.local"]];
}
}
}
@end
注释中已经写的很详细了,根据服务器传回的消息进行发送消息通知,而设置的代理则用于传输接收到的传递回来的xml数据解析后封装成的字典。
如果对通知机制与代理机制不太熟悉请恶补一下这方面的内容,真的是非常好用!
不禁要感叹只有当自己去做程序的时候才能明白其真正的作用。
两个代理
HabberChatDelegate
#import <Foundation/Foundation.h>
@protocol HabberChatDelegate <NSObject>
//传递上线人的名字
- (void)newBuddyOnline:(NSString *)buddyName;
//传递下线人的名字
- (void)buddyWentOffline:(NSString *)buddyName;
//发送与服务器断开连接的信息
- (void)didDisconnect;
//发送与服务器连接的信息
- (void)didConnect;
//传送好友申请信息
- (void)receivedFriendRequest:(NSString *)presenceFrom;
@end
HabberMessageDelegate
#import <Foundation/Foundation.h>
@protocol HabberMessageDelegate <NSObject>
//就只负责聊天数据的传送
- (void)newMessageReceived:(NSDictionary *)messageContent;
@end
Model的设置
Statics.h
#import <Foundation/Foundation.h>
//这里有三个,专门用于当做存储userDefaults的键值使用
static NSString *USERID = @"userId";
static NSString *PASS= @"pass";
static NSString *SERVER = @"server";
@interface Statics : NSObject
//获得当前时间
+ (NSString *)getCurrentTime;
@end
Statics.m
#import "Statics.h"
@implementation Statics
+ (NSString *)getCurrentTime {
NSDate *nowUTC = [NSDate date];
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setTimeZone:[NSTimeZone localTimeZone]];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
return [dateFormatter stringFromDate:nowUTC];
}
@end
其实关于这个静态方法+ (NSString *)getCurrentTime,作用是获得系统时间显示在聊天cell上的,但是由于已经用了UUChatTableView这个框架,这个方法放在这里没什么意义了,仅供参考。