swift——從零開始本身的IM

以前舊項目裏的聊天是集成的融雲,然而有諸多不方便,不少需求都沒法正常且簡單的實現。並且使用聊天的人也並很少。。。 因此決定公司本身開發即時通信(反正用戶少=。=)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

有關即時通信理解,本人也是初探,有問題歡迎討論。互勉!服務器

轉載請註明出處,謝謝。

相關文章
相關標籤/搜索