swift5.0 + ASDK + socket.IO

本文简单探讨socket即时通讯及基于flex布局的高性能框架AsyncDisplayKit在项目中的应用,代码较多,简单介绍下其中的部分模块,文章持续更新中...

  • demo请私信
    swift5.0 + ASDK + socket.IO_第1张图片
    asdk.png

移动端涉及主要三方框架
Texture、Alamofire、Socket.IO-Client-Swift、SwiftyJSON、FMDB、SSZipArchive...
node端涉及主要三方框架
socket.io、mongoose、redis、compression、apn...

以下是效果
屏幕快照.png

swift5.0 + ASDK + socket.IO_第2张图片
真机下即可显示真人头像
  • 关于AsyncDisplayKit
    AsyncDisplayKit是一个UI框架,他就是对UIKit的封装,最初诞生于 Facebook,为了缓解主线程的压力,提升用户体验。它最大的特点就是"异步",将消耗时间的渲染、图片解码、布局以及其它 UI 操作等等全部移出主线程,这样主线程就可以对用户的操作及时做出反应,来达到流畅运行的目的。我们在项目中使用最多的就是tableview,接下来以tableview为例,简单介绍下ASDK在项目中的应用,更多ASDk内容 https://texturegroup.org
import AsyncDisplayKit

class HomeViewController: ASViewController {
    fileprivate var dataSource: [CharItem] = []
    private let tableNode = ASTableNode(style: .plain)
    init() {
        super.init(node: ASDisplayNode())
        node.addSubnode(tableNode)
        tableNode.dataSource = self
        tableNode.delegate = self
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "首页"
        tableNode.view.allowsMultipleSelectionDuringEditing = false
        requestData()
    }
    
    func requestData() {
        NetAlamofireTool.netWork(Constants.allUsers, parameters: ["":""], method: .get) { (result) in
            for item in result[RET_DATA]["allUser"].arrayValue {
                DLog(result, fileName: "HomeViewController", methName: "allUsers")
                let charItem = CharItem(CharItemID: item["_id"].stringValue, name: item["name"].stringValue)
                charItem content = "今天不逼自己学会72变,日后谁能代你承受81难"
                charItem.avatar = URL.init(string: item["avatar"].stringValue)
                self.dataSource.append(charItem)
            }
            self.tableNode.reloadData()
        }
    }
}

extension HomeViewController: ASTableDelegate, ASTableDataSource {
    
    func numberOfSections(in tableNode: ASTableNode) -> Int {
        return 1
    }
    
    func tableNode(_ tableNode: ASTableNode, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }
    
    func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock {
        let model = dataSource[indexPath.row]
        let block: ASCellNodeBlock = {
            return WJCellNode(model: model)
        }
        return block
    }
    
    func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) {
        tableNode.deselectRow(at: indexPath, animated: false)
        let meVC = MeViewController()
        navigationController?.pushViewController(meVC, animated: true)
    }
}

看完以上代码,会不会感觉这和tabeview 很相似,实现了和tableview的无缝连接,我们会发现少了我们常用的一个API heightForRowAt , ASDK帮我们摆脱计算高度的烦恼,基于flex布局,ASDK内部进行高度处理。关于自定义cell,ASDK是基于block回调的方式创建cell,不需要cell重用,内部已实现。

  • 接下来介绍几个常用控件:
    ASCollectionNode -> UICollectionView
    ASPagerNode -> UIPageViewController
    ASTableNode -> UITableView
    ASViewController -> UIViewController
    ASNavigationControllerv -> UINavigationController
    ASTabBarController ->UIKit的 UITabBarController
    ASImageNode ->UIImageView
    ASTextNode -> UITextView
  • 自定义cell及布局方式在项目中的应用
import AsyncDisplayKit

class WJCellNode: ASCellNode {
    
    private let avatarNode = ASNetworkImageNode()
    
    private let titleNode = ASTextNode()
    
    private let subTitleNode = ASTextNode()
    
    private let timeNode = ASTextNode()
    
    private let hairlineNode = ASDisplayNode()
    
    init() {
        super.init()
        
        automaticallyManagesSubnodes = true
        
        avatarNode.cornerRadius = 4.0
        avatarNode.cornerRoundingType = .precomposited
        avatarNode.image = UIImage(named: "user_me")

        titleNode.attributedText = Common.attributeTitle()
        titleNode.maximumNumberOfLines = 1
        
        timeNode.attributedText = Common.attributeTime()
        
        subTitleNode.attributedText = Common.attributeSubTitle()
        subTitleNode.maximumNumberOfLines = 1
        
        hairlineNode.backgroundColor = UIColor(white: 0, alpha: 0.15)
        hairlineNode.style.preferredSize = CGSize(width: 9, height: Constants.lineHeight)
    }
    
    override func didLoad() {
        super.didLoad()
        
    }
    
    override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
        avatarNode.style.preferredSize = CGSize(width: 48.0, height: 48.0)
        titleNode.style.flexGrow = 1.0
        subTitleNode.style.flexGrow = 1.0
        subTitleNode.style.flexShrink = 1.0
        subTitleNode.style.spacingAfter = 12
        timeNode.style.spacingAfter = 16
        
        let avatarLayout: ASLayoutSpec
        avatarLayout = ASInsetLayoutSpec(insets: UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 12), child: avatarNode)
        avatarLayout.style.preferredSize = CGSize(width: 72.0, height: 76.0)
        
        let topStack = ASStackLayoutSpec.horizontal()
        topStack.alignItems = .center
        topStack.children = [titleNode, timeNode]        
        var bottomElements: [ASLayoutElement] = []
        bottomElements.append(subTitleNode)
        let bottomStack = ASStackLayoutSpec.horizontal()
        bottomStack.children = bottomElements
        
        let stack = ASStackLayoutSpec.vertical()
        stack.style.flexGrow = 1.0
        stack.style.flexShrink = 1.0
        stack.spacing = 6
        stack.children = [topStack, bottomStack]
        
        let layout = ASStackLayoutSpec.horizontal()
        layout.alignItems = .center
        layout.children = [avatarLayout, stack]
        layout.style.preferredSize = CGSize(width: Constants.screenWidth, height: 72)
        
        let absLayout = ASAbsoluteLayoutSpec(children: [layout, hairlineNode])
        hairlineNode.style.layoutPosition = CGPoint(x: 76, y: 72 - Constants.lineHeight)
        hairlineNode.style.preferredSize = CGSize(width: Constants.screenWidth - 76, height: Constants.lineHeight)
        return absLayout
    }
}

实现的效果


swift5.0 + ASDK + socket.IO_第3张图片
屏幕快照 2019-09-29 下午4.17.54.png

Socket.IO-Client-Swift的使用方式

import UIKit
import SocketIO

public typealias success = (_ json: String) -> Void
public typealias fail = (_ json: String) -> Void
let user:User = WJDBMannager.getUser()
let manager = SocketManager(socketURL: URL(string: Constants.base_Url)!, config: [.log(true), .connectParams(["token":user.Token ?? ""])])
var socket = manager.defaultSocket

public final class CornerSocketManager {
    public static func connectWithToken(token:String,success: @escaping success,fail: @escaping fail) {
        print("++++++++++++++++++++++++++++++++++++++++++")
        print("token==",token)

        socket.on(clientEvent: .connect) {data, ack in
            success("连接成功...")
        }
        socket.connect(timeoutAfter: 15, withHandler: {
            fail("连接超时...")
        })
        socket.connect()
    }
}
  • 消息的发送
 let bodies:[String:String] = [
            "latitude":"0",
            "longitude":"0",
            "msg":"消息消息消息消息",
            "type":"txt"
        ]
        let params:[String:Any] = [
            "chat_type":"chat",
            "from_user":"xu",
            "to_user":"xu",
            "type":"0",
            "bodies":bodies
        ]
// 消息发送 根据发送的参数type类型确定是文本消息、定位、图片、视频流以及发送方
socket.emitWithAck("chat", params).timingOut(after: 20) {data in
         print("结果返回==",data)      
 }        
  • node端消息模块的处理
var chat = function (io) {
        io.on('connection', function (socket) {
            var token = socket.handshake.query.auth_token;
            var userName = vfglobal.token_Map[token];
            socket.broadcast.emit('onArr', {'user': userName});
            vfglobal.socket_Map[userName] = socket;
            vfGroupUser.findAll(userName, function (data) {
                data.findIndex(function (T, number, data) {
                    socket.join(T.group_name);
                });
                
            });
            socket.on('chat', function (message, callback) {
                
                var messageBody = message.bodies;
                if (messageBody.type == 'image') {
                    var imageBuffer = messageBody.fileData;
                    console.log('imageBuffer==',imageBuffer)
                    var imageName = messageBody.fileName;
                    var imageType = path.extname(imageName);
                    var uuid = vfglobal.util.generateUUID();
                    var newImageName = uuid + imageType;
                    var savePath = "./public/images/chatImages/" + newImageName;
                    imageScale = 1;
                    var width = 200;
                    var height = 300;
                    
                    fs.writeFile(savePath, imageBuffer, function(err) {
                        if(err) {vfglobal.MyLog(err)}
                        else {
                            message.bodies.size = {'width' : 200, 'height' : 300};
                            message.bodies.fileRemotePath = "images/chatImages/" + newImageName;
                            message.bodies.fileData = null;
                            if (imageScale == 1) {
                                message.bodies.thumbnailRemotePath = message.bodies.fileRemotePath;
                                sendMessage(message, callback, imageBuffer);
                            }
                            else  {
                                var index = imageName.indexOf('.');
                                var length = imageName.length;
                                var type = imageName.substring(index + 1, length);
                                gm(imageBuffer)
                                    .resize(width, height)
                                    .toBuffer(type, function (err, buffer) {
                                        fs.writeFile("./public/images/chatImages/s_" + newImageName, buffer, function (err) {
                                            
                                            if (!err) {
                                                message.bodies.thumbnailRemotePath = "images/chatImages/s_" + newImageName;
                                                sendMessage(message, callback, buffer);
                                            }
                                        })
                                    });
                            }
                        }
                    });
                }
                else if (messageBody.type == 'video') {
                    var audioBuffer = messageBody.fileData;
                    var audioName = messageBody.fileName;
                    var audioType = path.extname(audioName);
                    var uuid = vfglobal.util.generateUUID();
                    var newAudioName = uuid + audioType;
                    fs.writeFile("./public/video/" + newAudioName, audioBuffer, function (err) {
                        if (err){} else {
                            message.bodies.fileRemotePath = "video/" + newAudioName;
                            message.bodies.fileData = null;
                            sendMessage(message, callback);
                        }
                    })
                }
                else if(messageBody.type == 'location') {
                    var url = '高德地图API'
                    request.GetRequest(url, function (err, res) {
                        if (err) {} else {
                            var imgData = '';
                            var contentType = res.headers['content-type'];
                            var isBackImg = contentType.indexOf("image") >= 0;
                            res.on("data", function (chunk) {
                                imgData += chunk;
                            });
                            
                            res.on("end", function () {
                                if (isBackImg) {
                                    var uuid = vfglobal.util.generateUUID();
                                    var imageFileName = uuid + '.PNG';
                                    fs.writeFile("./public/images/mapImages/" + imageFileName, imgData, "binary", function(err){
                                        if(err){}else {
                                            message.bodies.fileRemotePath = "images/mapImages/" + imageFileName;
                                            sendMessage(message, callback);
                                        }
                                        
                                    });
                                }
                            });
                        }
                    });
                }
            });
        });
    }
    

你可能感兴趣的:(swift5.0 + ASDK + socket.IO)