如何从XML plist字典提取有序Key数组?规避NSDictionary无序且不修改服务器
解决方法:从Plist XML中提取有序Key数组
你遇到的核心问题是NSDictionary本身是无序集合——系统的PropertyListSerialization解析Plist时,会自动把dict转成NSDictionary,自然就丢失了原XML里key的顺序。要在不改服务器的前提下解决,直接绕开plist转dict的流程,用XML解析器直接读取<key>标签的内容并按顺序保存即可。
下面是具体实现方案,分Swift和Objective-C两种常用场景:
Swift 实现
自定义一个XML解析器代理类,专门收集根级dict里的key:
class OrderedPlistKeyParser: NSObject, XMLParserDelegate { private(set) var orderedKeys: [String] = [] private var currentKey: String? private var inRootDict = false // 跟踪是否处于根级dict中 func parse(xmlData: Data) -> [String] { let parser = XMLParser(data: xmlData) parser.delegate = self parser.parse() return orderedKeys } // 开始解析标签时触发 func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { if elementName == "dict" { // 标记进入根级dict(第一个dict标签) if !inRootDict { inRootDict = true } } else if elementName == "key" && inRootDict { // 开始读取key内容 currentKey = "" } } // 读取标签内的字符内容 func parser(_ parser: XMLParser, foundCharacters string: String) { currentKey?.append(string) } // 结束解析标签时触发 func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { if elementName == "dict" && inRootDict { // 离开根级dict inRootDict = false } else if elementName == "key" && inRootDict, let key = currentKey?.trimmingCharacters(in: .whitespacesAndNewlines), !key.isEmpty { // 收集处理后的key orderedKeys.append(key) currentKey = nil } } } // 使用方式 if let xmlString = """ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>key1</key> <array> <string></string> <string>title1</string> <string>disabled</string> </array> <key>key2</key> <array> <string></string> <string>title2</string> <string>disabled</string> </array> <key>myKey3</key> <array> <string></string> <string>title3</string> </array> <key>key4</key> <array> <string></string> <string>title4</string> <string>disabled</string> </array> <key>key5</key> <array> <string></string> <string>title5</string> <string>disabled</string> </array> </dict> </plist> """, let xmlData = xmlString.data(using: .utf8) { let parser = OrderedPlistKeyParser() let orderedKeys = parser.parse(xmlData: xmlData) print(orderedKeys) // 输出 ["key1","key2","myKey3","key4","key5"] }
Objective-C 实现
同样的思路,用NSXMLParser来实现:
@interface OrderedPlistKeyParser : NSObject <NSXMLParserDelegate> @property (nonatomic, strong, readonly) NSMutableArray<NSString *> *orderedKeys; @property (nonatomic, strong) NSMutableString *currentKey; @property (nonatomic, assign) BOOL inRootDict; @end @implementation OrderedPlistKeyParser - (instancetype)init { self = [super init]; if (self) { _orderedKeys = [NSMutableArray array]; _inRootDict = NO; } return self; } - (NSArray<NSString *> *)parseXMLData:(NSData *)xmlData { NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData]; parser.delegate = self; [parser parse]; return [self.orderedKeys copy]; } - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict { if ([elementName isEqualToString:@"dict"]) { if (!self.inRootDict) { self.inRootDict = YES; } } else if ([elementName isEqualToString:@"key"] && self.inRootDict) { self.currentKey = [NSMutableString string]; } } - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { [self.currentKey appendString:string]; } - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { if ([elementName isEqualToString:@"dict"] && self.inRootDict) { self.inRootDict = NO; } else if ([elementName isEqualToString:@"key"] && self.inRootDict) { NSString *trimmedKey = [self.currentKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (trimmedKey.length > 0) { [self.orderedKeys addObject:trimmedKey]; } self.currentKey = nil; } } @end // 使用方式 NSString *xmlString = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n<key>key1</key>\n<array>\n<string></string>\n<string>title1</string>\n<string>disabled</string>\n</array>\n<key>key2</key>\n<array>\n<string></string>\n<string>title2</string>\n<string>disabled</string>\n</array>\n<key>myKey3</key>\n<array>\n<string></string>\n<string>title3</string>\n</array>\n<key>key4</key>\n<array>\n<string></string>\n<string>title4</string>\n<string>disabled</string>\n</array>\n<key>key5</key>\n<array>\n<string></string>\n<string>title5</string>\n<string>disabled</string>\n</array>\n</dict>\n</plist>"; NSData *xmlData = [xmlString dataUsingEncoding:NSUTF8StringEncoding]; OrderedPlistKeyParser *parser = [[OrderedPlistKeyParser alloc] init]; NSArray *orderedKeys = [parser parseXMLData:xmlData]; NSLog(@"%@", orderedKeys); // 输出 ["key1","key2","myKey3","key4","key5"]
补充说明
- 这个方案直接解析XML结构,完全保留原文件中
<key>标签的出现顺序,不需要修改服务器端的Plist内容。 - 代码里加入了
inRootDict判断,只会收集根级dict的key,避免嵌套dict(比如某个array里的子dict)的key干扰结果。如果你的Plist没有嵌套dict,也可以去掉这个判断,简化代码。
内容的提问来源于stack exchange,提问作者JP Aquino
相关产品推荐
相关产品推荐

