Swift4中如何在NSOutlineView中展示TLV解析结果?
嘿,我来帮你搞定TLV解析结果在NSOutlineView里的展示问题,一步步来:
第一步:优化你的Node类
你现有的Node初始化方法太零散了,没法一次性创建完整的节点,而且缺少便捷的子节点管理方法。咱们调整一下,给它加个全能初始化器和addChild方法,这样构建树结构会顺手很多:
class Node: NSObject { var isConstructed = false var tag = "Tag" var length = 0 var value = "Value" var children = [Node]() weak var parent: Node? override init() { super.init() } // 一次性设置所有核心属性的初始化器 init(tag: String, length: Int, value: String, isConstructed: Bool) { self.tag = tag self.length = length self.value = value self.isConstructed = isConstructed super.init() } // 快速添加子节点,自动维护父子关系 func addChild(_ node: Node) { node.parent = self children.append(node) } func isLeaf() -> Bool { return children.isEmpty } }
第二步:从TLV解析结果构建节点树
你的解析器输出的是扁平的TLV字符串,咱们得把它转换成层级化的Node树。这里有个关键点:构造类型的Tag(比如<e1>、<ef>)它的Value里包含了其他子TLV,这些就是父节点,里面的子TLV是它们的子节点。
最优的方式是直接修改你的TLV解析器,在解析过程中就构建Node树——比如遇到构造类型的Tag时,递归解析它的Value,生成子节点并添加到当前节点下。如果暂时不想改解析器,也可以从输出的字符串反向解析(用正则匹配提取每个TLV块,再判断是否是构造节点,维护一个栈来管理父节点层级):
func buildTLVTree(from tlvOutput: String) -> [Node] { var rootNodes = [Node]() // 用正则匹配每个TLV块:<tag> length <value> guard let regex = try? NSRegularExpression(pattern: "<([a-f0-9]+)>\\s*(\\d+)\\s*<([^>]+)>", options: .caseInsensitive) else { return rootNodes } let matches = regex.matches(in: tlvOutput, range: NSRange(tlvOutput.startIndex..., in: tlvOutput)) var nodeStack = [Node]() // 用栈维护当前的父节点层级 for match in matches { // 提取Tag、Length、Value let tag = String(tlvOutput[Range(match.range(at: 1), in: tlvOutput)!]) let length = Int(String(tlvOutput[Range(match.range(at: 2), in: tlvOutput)!])) ?? 0 let value = String(tlvOutput[Range(match.range(at: 3), in: tlvOutput)!]) // 判断是否是构造节点:Value里包含其他<tag>,说明有子节点 let isConstructed = value.contains("<") let currentNode = Node(tag: tag, length: length, value: value, isConstructed: isConstructed) if isConstructed { // 构造节点作为父节点,添加到当前层级 if let currentParent = nodeStack.last { currentParent.addChild(currentNode) } else { rootNodes.append(currentNode) } nodeStack.append(currentNode) } else { // 叶子节点,添加到当前父节点下 if let currentParent = nodeStack.last { currentParent.addChild(currentNode) // 这里可以补充逻辑:当父节点的所有子节点处理完毕后,弹出栈回到上一级 // 比如根据TLV的Length校验,子节点的总字节数等于父节点的Value长度时,弹出 } else { rootNodes.append(currentNode) } } // 补充:如果当前节点是某个父节点的最后一个子节点,弹出栈(需要结合TLV规则判断,比如你解析器的输出顺序是层级化的,所以可以根据下一个节点的层级调整) } return rootNodes }
第三步:配置NSOutlineView的数据源和委托
接下来就是把Node树和NSOutlineView绑定起来,实现数据源和委托方法:
数据源方法(处理节点层级)
extension YourViewController: NSOutlineViewDataSource { func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { if let node = item as? Node { return node.children.count } // 返回根节点数量 return rootNodes.count } func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { if let node = item as? Node { return node.children[index] } return rootNodes[index] } func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { guard let node = item as? Node else { return false } return !node.isLeaf() } }
委托方法(处理单元格显示)
先在Storyboard里给NSOutlineView添加三列,分别命名为Tag、Length、Value,并给单元格设置标识符TLVCell,然后实现:
extension YourViewController: NSOutlineViewDelegate { func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { guard let node = item as? Node else { return nil } guard let cellView = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("TLVCell"), owner: self) as? NSTableCellView else { return nil } switch tableColumn?.identifier.rawValue { case "Tag": cellView.textField?.stringValue = node.tag case "Length": cellView.textField?.stringValue = "\(node.length)" case "Value": cellView.textField?.stringValue = node.value default: break } return cellView } }
最后小提示
比起从输出字符串反向解析,直接修改你的TLV解析器,在解析过程中构建Node树会更可靠——比如解析构造类型Tag时,直接递归解析它的Value生成子节点,这样能准确维护层级关系,避免字符串解析可能出现的错误。
内容的提问来源于stack exchange,提问作者Fritz
相关产品推荐
相关产品推荐

