From fd522d612f24ee813c80b1a1b0f6bd311b2735c3 Mon Sep 17 00:00:00 2001 From: relikd Date: Thu, 27 Nov 2025 20:54:15 +0100 Subject: [PATCH] ref: move StringPool cache into StringPool --- Sources/AndroidXML/StringPool.swift | 15 +++++++++++++++ Sources/AndroidXML/Xml_Parser.swift | 16 +++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Sources/AndroidXML/StringPool.swift b/Sources/AndroidXML/StringPool.swift index 1b86770..4cabae1 100644 --- a/Sources/AndroidXML/StringPool.swift +++ b/Sources/AndroidXML/StringPool.swift @@ -53,6 +53,7 @@ public struct StringPool { let dataString: Index let dataStyle: Index } + private var _lookupTable: [StringPoolRef: String] = [:] init(_ idx: Index, _ bytes: RawBytes) throws { self.init(try ChunkHeader(idx, bytes, expect: .StringPool)) @@ -81,6 +82,9 @@ public struct StringPool { /// Retrieve string for given `StringPoolRef` index public func getString(_ idx: StringPoolRef) -> String { precondition(idx < stringCount, "Index out of bounds for string-pool string[\(idx) > \(stringCount)]") + if let cached = _lookupTable[idx] { + return cached + } let offset = header._bytes.int32(_offset.indicesString + Index(idx) * 4) let dataOffset = _offset.dataString + Index(offset) return flags.contains(.Utf8) @@ -95,4 +99,15 @@ public struct StringPool { let dataOffset = _offset.dataStyle + Index(offset) return StringPoolSpan(dataOffset, header._bytes) } + + /// Performs `getString()` and caches the results in a lookup-table. + /// For memory-efficiency you should use this only on keys which are expected to repeat. + public mutating func getStringCached(_ index: StringPoolRef) -> String { + if let cached = _lookupTable[index] { + return cached + } + let val = self.getString(index) + _lookupTable[index] = val + return val + } } diff --git a/Sources/AndroidXML/Xml_Parser.swift b/Sources/AndroidXML/Xml_Parser.swift index 1d8a4da..5b4b6cc 100644 --- a/Sources/AndroidXML/Xml_Parser.swift +++ b/Sources/AndroidXML/Xml_Parser.swift @@ -95,15 +95,9 @@ public struct Xml_Parser { /// Iterate over whole tree and call `start` and `end` blocks for each element. /// Each `start` block contains the `tag` name and its attribute `(name, value)` pairs. public func iterElements(_ start: (_ startTag: String, _ attributes: AttributesDict) throws -> Void, _ end: (_ endTag: String) throws -> Void) throws { - // cache StringPool lookups for faster access - var _lookupTable: [StringPoolRef: String] = [:] + var pool = stringPool func lookup(_ index: StringPoolRef) -> String { - if let cached = _lookupTable[index] { - return cached - } - let val = stringPool.getString(index) - _lookupTable[index] = val - return val + pool.getStringCached(index) } // cache namespace prefix strings var namespaces: [StringPoolRef: String] = [:] @@ -120,7 +114,7 @@ public struct Xml_Parser { case .XmlStartNamespace: let ns = XmlNamespace(chunk) - namespaces[ns.uri] = lookup(ns.prefix) + namespaces[ns.uri] = pool.getStringCached(ns.prefix) nsOnNextTag = ns.uri case .XmlEndNamespace: @@ -131,10 +125,10 @@ public struct Xml_Parser { let elem = XmlStartElement(chunk) let attrs = AttributesDict(elem, stringPool, lookupFn: lookup(_:), namespaces: namespaces, nsNeedsAppend: nsOnNextTag) nsOnNextTag = nil - try start(lookup(elem.name), attrs) + try start(pool.getStringCached(elem.name), attrs) case .XmlEndElement: - try end(lookup(XmlEndElement(chunk).name)) + try end(pool.getStringCached(XmlEndElement(chunk).name)) default: throw AXMLError("Dont know how to handle \(chunk.type) (offset: \(chunk.index(.startOfChunk)))")