以前舊項目裏的聊天是集成的融雲,然而有諸多不方便,不少需求都沒法正常且簡單的實現。並且使用聊天的人也並很少。。。 因此決定公司本身開發即時通信(反正用戶少=。=)git
開發中是基於Socket.IO封裝使用,寫這篇文章時版本是13.0.0。後臺是Node.js。這是Github地址 Socket.IO-Client-Swiftgithub
切記!好的產品就別期望了!多和後臺的大兄弟交流,必定得有討論鏈接,用戶認證和消息文本格式的思惟。web
先上一個簡單的思惟導圖 數據庫
本身補的,大概就是這麼個流程。swift
//鏈接狀態
enum linkState: String {
case no_connection = "未鏈接"
case in_connection = "鏈接中"
case connected = "已鏈接"
case connection_error = "鏈接錯誤"
}
class BSIM {
/// 靜態變量(常量) static 修飾的靜態方法不能被重寫
static let shared = BSIM()
var manager:SocketManager?
var socket:SocketIOClient?
var onlineTimer:Timer?
var onlineTimerNum = 30;
/*後臺狀態保活*/
let app = UIApplication.shared
var taskID = UIBackgroundTaskIdentifier()
var backTimer:Timer?
var backTime = 0;
//APP進入後臺保活時間
var backTimeOut = 60 * 3;
// MARK:- 初始鏈接並進行認證
func initAndConnect(server:String,userid:String,random:String,result:@escaping BSIMConnectResult){
/// 一些判斷 例如
guard server != "" else {
self.connectResult!(-9999, "服務器地址不能爲空")
return
}
/// 鏈接 第一步的HTTP請求 /// 打印調試信息 使用websockets
self.manager = SocketManager(socketURL: URL(string: self.server)!, config: [.log(false), .forceWebsockets(true)])
self.socket = self.manager?.defaultSocket
/// 監聽事件
self.socket?.on("你和後臺約定好的字段", callback: { (data, ack) in
/// 記得移除 避免重複監聽
self.socket?.off("某些監聽")
/// 初始化定時器 心跳包
if(self.onlineTimer == nil){
self.onlineTimer = Timer.scheduledTimer(timeInterval: TimeInterval(self.onlineTimerNum),target:self,selector:#selector(self.onlineTimerRun),userInfo:nil,repeats:true)
}else{
self.onlineTimer?.invalidate()
self.onlineTimer = nil
self.onlineTimer = Timer.scheduledTimer(timeInterval: TimeInterval(self.onlineTimerNum),target:self,selector:#selector(self.onlineTimerRun),userInfo:nil,repeats:true)
}
/// 更改鏈接狀態
self.linkStatePush(state: linkState.connected)
/*
有關認證的邏輯代碼
*/
/// 基本設置
self.completion()
})
}
/// 初始化設置
private func completion(){
/// 移除自身通知
NotificationCenter.default.removeObserver(self)
//註冊進入後臺通知
NotificationCenter.default.addObserver(self, selector: #selector(self.EnterBackgroundNotification), name:NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
//註冊進入前臺通知
NotificationCenter.default.addObserver(self, selector: #selector(self.EnterForegroundNotification), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
self.onMessage(MsgAction: "message") { (dataString) in
let data = dataString.data(using: String.Encoding.utf8)
let js = JSON(data:data!)
/// 自定義的Model
let msgModel = MsgBaseModel.init()
/// 收到數據
if js["msgId"].stringValue != ""{
/*
解析數據
數據解析用的SwiftyJSON
本地數據存儲用的FMDB
*/
//聲音提醒
self.applicationState(pushData: ((js["sendName"].stringValue) == "" ? (js["pushData"].stringValue) : (js["sendName"].stringValue) + ": " + (js["pushData"].stringValue)))
if(self.checkedMessage(MsgId: msgModel.msgId)){
print("\(msgModel.msgId) 消息數據庫中已存在")
return
}
let ins = self.insertMsg(model: msgModel)
if(ins == false){
print("\(msgModel.msgId) 消息插入失敗")
return
}
/// 通知 更新UI界面
self.SessionListNotice?.newMessage(MsgModel: msgModel)
self.SessionNotice?.newMessage(MsgModel: msgModel)
/// 未讀消息條數
let arr = self.getSessionListData()
var number = 0
for model in arr{
number = number + model.notReadNumber
let na = NSNotification.Name(rawValue:"news")
NotificationCenter.default.post(name: na, object:(number))
}
}
}
}
/// 監聽消息
private func onMessage(MsgAction:String,cb:@escaping (_ data:String)->Void){
self.socket?.on(MsgAction, callback: { (data, ack) in
/// 在TCP/IP協議中,若是接收方成功的接收到數據,那麼會回覆一個ACK數據
ack.with(UUID().uuidString)
if data[0] is String{
cb(data[0] as? String ?? "")
}else {
//此處後期可能語音圖片等格式文件須要作相應判斷
cb(bs_String.objectToJson(object: (data[0] as? Dictionary<String,Any>) ?? [:]) ?? "")
}
})
}
@objc func EnterForegroundNotification(){
print("進入前臺")
if(self.backTime < self.backTimeOut){
self.app.endBackgroundTask(self.taskID)
}
self.backTime = 0
self.backTimer?.invalidate()
}
/// 執行進入後臺任務
@objc func EnterBackgroundNotification(){
print("進入後臺")
//建立定時任務
self.backTimer = Timer.scheduledTimer(timeInterval: 1,target:self,selector:#selector(self.backstageTiming),userInfo:nil,repeats:true)
/// 應用在被用戶切換到後臺的時候就會被系統暫停掉, 這個方法能夠延遲系統暫停你的應用,並申請額外的時間來完成未完成的任務
self.taskID = self.app.beginBackgroundTask(expirationHandler: {
/// 後臺任務到期執行,好像是10分鐘 我暫時沒設置這麼久
})
}
/// 後臺任務計時
@objc func backstageTiming(){
self.backTime += 1
if(self.backTime == self.backTimeOut){
self.backTimer?.invalidate
/// 結束掉保活
self.app.endBackgroundTask(self.taskID)
}
}
/// 定時任務心跳包
@objc private func onlineTimerRun(){
self.socket?.emitWithAck("time", "").timingOut(after: 3, callback: { (data) in
if data[0] is Dictionary<String,AnyObject> {
/// 邏輯代碼
}
})
}
}
複製代碼
以上代碼差很少是鏈接,設置(心跳包,通知等),收到消息(處理數據,更新UI)等一系列方法的去邏輯版。本地數據存儲也就是檢查下有沒有重複,不存在就插入。api
這樣就基本實現了用戶登陸,能夠接收到產品想要的自定義消息,好比系統消息,帳單消息等等。可是若是須要單聊,你就須要根據用戶id取聊天數據,而後條數多了確定須要分頁。因此要多一個 getUserMsgListPageData(userid:String, page:Int)
這種方法。 而後消息主頁,子界面的那些處理就須要本身去慢慢拓展了。bash
有關即時通信理解,本人也是初探,有問題歡迎討論。互勉!服務器